【前端】手机滚动-如何实现惯性滚动
手机惯性滚动时应该怎么计算速度,以及之后速度怎么递减等问题
因为要做一个手机端的滚动插件,所以想模拟出手机原生的惯性滚动,就是手指快速滑动后,手机页面在手指抬起来之后,依然会滚动一段距离的效果。
现在我尝试了两种方法
1、在手指start与end之间时间少于300ms时,根据手指移动距离得出需要继续滚动的长度,在end之后通过transition与top实现动画效果
2、在手指end之后通过手指的移动距离与使用时间计算平局速度,如果使用时间少于300ms,则通过requestAnimationFrame循环调用一个每次都递减速度的函数,当速度很小时跳出函数循环。在函数中每一次都设置一次top值,因为requestAnimationFrame间隔很短,大概之后16ms左右,以实现效果
第一种方法代码
<!DOCTYPE html><html>
<head>
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalabel=no"/>
<meta charset="utf-8"/>
<title>移动手指事件滚动</title>
<style>
* {
margin: 0;
padding: 0;
}
.container {
position: relative;
margin: 50px;
width: calc(100% - 100px);
height: 400px;
border: 1px solid green;
box-sizing: border-box;
overflow: hidden;
}
.container .scroll {
position: absolute;
top: 0;
left: 0;
width: 100%;
transition: all 160ms ease-out;
}
.container p {
margin: 0 auto 10px;
width: 80%;
height: 80px;
border: 1px solid yellow;
}
.container .scroll:after {
content: '';
display: block;
clear: both;
}
.container .barBox {
position: absolute;
right: 2px;
top: 2px;
width: 3px;
height: calc(100% - 4px);
background: rgba(100, 200, 100, 0.3);
}
.container .barBox .bar {
position: absolute;
top: 0;
right: 0;
height: 0;
width: 100%;
background: #ccc;
transition: all 160ms ease-out;
}
</style>
</head>
<body>
<div class="container" id="container">
<div class="scroll">
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
</div>
<div class="barBox" id="barBox">
<div class="bar"></div>
</div>
</div>
<script>
var oContainer = document.getElementById('container');
var oScroll = oContainer.children[0];
var oBarBox = document.getElementById('barBox');
var oBar = oBarBox.children[0];
// 盒子尺寸
var iWrapperH = numberPx(getStyle(oContainer, 'height'));
var iScrollH = numberPx(getStyle(oScroll, 'height'));
var iBarBoxH = numberPx(getStyle(oBarBox, 'height'));
var iBarH = iWrapperH / iScrollH * iBarBoxH;
// 初始状态
setStyle(oBar, {
height: iBarH + 'px'
})
// 参数
var startTime = 0;
var endTime = 0;
var isMove = false;
var iScrollTop = 0;
var iBarTop = 0;
var endTimeout = null;
oContainer.addEventListener('touchstart', function (e) {
startTime = new Date().getTime();
var startY = e.targetTouches[0].clientY;
var startTop = numberPx(getStyle(oScroll, 'top'));
var endY = startY;
var endTop = startTop;
setStyle(oScroll, {
top: startTop + 'px'
})
setStyle(oBar, {
top: -startTop / iScrollH * iBarBoxH + 'px'
})
var moveFn = function (e) {
isMove = true;
var nowY = e.targetTouches[0].clientY;
var nowTop = startTop;
endY = nowY;
var moveY = nowY - startY;
nowTop += moveY;
endTop = nowTop;
iScrollTop = nowTop > 0 ? 0 : (nowTop < iWrapperH - iScrollH ? iWrapperH - iScrollH : nowTop);
iBarTop = -iScrollTop / iScrollH * iBarBoxH;
setStyle(oScroll, {
top: iScrollTop + 'px'
})
setStyle(oBar, {
top: iBarTop + 'px'
})
}
var endFn = function (e) {
endTime = new Date().getTime();
if ( endTime - startTime <= 200 && isMove ) {
var moveY = endY - startY;
var iScale = 1;
var iTime = 300;
if ( Math.abs(moveY) <= 100 ) {
iScale = 1;
iTime = 300;
} else if ( Math.abs(moveY) <= 200 ) {
iScale = 2;
iTime = 500;
} else {
iScale = 3;
iTime = 700;
}
moveY = moveY * iScale;
endTop += moveY;
iScrollTop = endTop > 0 ? 0 : (endTop < iWrapperH - iScrollH ? iWrapperH - iScrollH : endTop);
iBarTop = -iScrollTop / iScrollH * iBarBoxH;
setStyle(oScroll, {
transition: 'all ' + iTime + 'ms ease-out',
top: iScrollTop + 'px'
})
setStyle(oBar, {
transition: 'all ' + iTime + 'ms ease-out',
top: iBarTop + 'px'
})
clearTimeout(endTimeout);
endTimeout = setTimeout(function () {
setStyle(oScroll, {
transition: 'all 160ms ease-out'
})
setStyle(oBar, {
transition: 'all 160ms ease-out',
})
}, 500);
}
isMove = false;
document.documentElement.removeEventListener('touchmove', moveFn);
oContainer.removeEventListener('touchend', endFn);
}
document.documentElement.addEventListener('touchmove', moveFn);
oContainer.addEventListener('touchend', endFn);
})
function getStyle (obj, name) {
if(window.getComputedStyle) {
return getComputedStyle(obj, null)[name];
} else {
return obj.currentStyle[name];
}
}
function setStyle (obj, oStyle) {
for(var i in oStyle) {
obj.style[i] = oStyle[i];
}
}
function numberPx (num) {
return Number(num.split('px')[0]);
}
</script>
</body>
</html>
第二种代码
<!DOCTYPE html><html>
<head>
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalabel=no"/>
<meta charset="utf-8"/>
<title>移动手指事件滚动使用速度</title>
<style>
* {
margin: 0;
padding: 0;
}
.container {
position: relative;
margin: 50px;
width: calc(100% - 100px);
height: 400px;
border: 1px solid green;
box-sizing: border-box;
overflow: hidden;
}
.container .scroll {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.container p {
margin: 0 auto 10px;
width: 80%;
height: 80px;
border: 1px solid yellow;
}
.container .scroll:after {
content: '';
display: block;
clear: both;
}
.container .barBox {
position: absolute;
right: 2px;
top: 2px;
width: 3px;
height: calc(100% - 4px);
background: rgba(100, 200, 100, 0.3);
}
.container .barBox .bar {
position: absolute;
top: 0;
right: 0;
height: 0;
width: 100%;
background: #ccc;
}
</style>
</head>
<body>
<div class="container" id="container">
<div class="scroll">
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
</div>
<div class="barBox" id="barBox">
<div class="bar"></div>
</div>
</div>
<script>
var oContainer = document.getElementById('container');
var oScroll = oContainer.children[0];
var oBarBox = document.getElementById('barBox');
var oBar = oBarBox.children[0];
// 盒子尺寸
var iWrapperH = numberPx(getStyle(oContainer, 'height'));
var iScrollH = numberPx(getStyle(oScroll, 'height'));
var iBarBoxH = numberPx(getStyle(oBarBox, 'height'));
var iBarH = iWrapperH / iScrollH * iBarBoxH;
// 初始状态
setStyle(oBar, {
height: iBarH + 'px'
})
// 参数
var startTime = 0;
var endTime = 0;
var pastTime = 0; // 过去一次的时间
var nowTime = 0; // 当前移动的时间
var isMove = false;
var iBarTop = 0;
var timer = true;
oContainer.addEventListener('touchstart', function (e) {
startTime = new Date().getTime();
pastTime = startTime;
var startY = e.targetTouches[0].clientY;
var startTop = numberPx(getStyle(oScroll, 'top'));
var endY = startY;
var endTop = startTop;
timer = false;
var moveFn = function (e) {
isMove = true;
var nowY = e.targetTouches[0].clientY;
var nowTop = startTop;
endY = nowY;
var moveY = nowY - startY;
nowTop += moveY;
endTop = nowTop = getPos(nowTop);
setStyle(oScroll, {
top: nowTop + 'px'
})
setStyle(oBar, {
top: getBarTop(nowTop) + 'px'
})
}
var endFn = function (e) {
endTime = new Date().getTime();
var speed = (endY - startY) / (endTime - startTime);
if ( endTime - startTime <= 300 && isMove ) {
speed *= 16;
var f = 0,
top = endTop;
timer = true;
show();
function show () {
timer && requestAnimationFrame(show);
f = Math.min(Math.abs(speed) / 10, 0.5); //重点
if( speed > 0.2 ) {
speed -= f
} else if(speed < -0.2){
speed += f
} else {
timer = false
speed = 0
return
}
top += speed;
if ( top > 0 || top < iWrapperH -iScrollH ) {
timer = false
speed = 0
setStyle(oScroll, {
top: getPos(top) + 'px'
})
setStyle(oBar, {
top: getBarTop(getPos(top)) + 'px'
})
return
}
setStyle(oScroll, {
top: top + 'px'
})
setStyle(oBar, {
top: getBarTop(top) + 'px'
})
}
}
isMove = false;
document.documentElement.removeEventListener('touchmove', moveFn);
document.documentElement.removeEventListener('touchend', endFn);
}
document.documentElement.addEventListener('touchmove', moveFn);
document.documentElement.addEventListener('touchend', endFn);
})
function getPos (num) {
return num >= 0 ? 0 : (num <= iWrapperH - iScrollH ? iWrapperH - iScrollH : num);
}
function getBarTop (num) {
return -num / iScrollH * iBarBoxH;
}
function getStyle (obj, name) {
if(window.getComputedStyle) {
return getComputedStyle(obj, null)[name];
} else {
return obj.currentStyle[name];
}
}
function setStyle (obj, oStyle) {
for(var i in oStyle) {
obj.style[i] = oStyle[i];
}
}
function numberPx (num) {
return Number(num.split('px')[0]);
}
</script>
</body>
</html>
可以看看原生的效果
<!DOCTYPE html><html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalabel=no">
<title>苹果移动效果</title>
<style>
* {
margin: 0;
padding: 0;
}
.container {
position: relative;
margin: 50px;
width: calc(100% - 100px);
height: 400px;
border: 1px solid green;
box-sizing: border-box;
overflow: auto;
}
.container p {
margin: 0 auto 10px;
width: 80%;
height: 80px;
border: 1px solid yellow;
}
</style>
</head>
<body>
<div class="container" id="container">
<div class="scroll">
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
<p></p><p></p><p></p>
</div>
</div>
</body>
</html>
虽然现在看起来可以滚动了,但是感觉距离原生的惯性滚动、以及iScroll、better-scroll等的效果差的还是挺远的。看他们的源码,真是找不到哪儿是哪儿的东西,希望懂的大佬们,能够指点指点我!!!
回答
主要是算法(运动曲线)的问题,再配合tranform进行移动
好的曲线,可以让你动的更加自然
应该是运动曲线太干瘪。。。推荐一个运动曲线算法吧。
我之前用过这个曲线算法做过一段动画,感觉挺简单的,效果也不错。
使用方法
前半段是介绍requestAnimationFrame的,后半段说了这个东西应该怎么用。
以上是 【前端】手机滚动-如何实现惯性滚动 的全部内容, 来源链接: utcz.com/a/79434.html