【前端】手机滚动-如何实现惯性滚动

手机惯性滚动时应该怎么计算速度,以及之后速度怎么递减等问题

因为要做一个手机端的滚动插件,所以想模拟出手机原生的惯性滚动,就是手指快速滑动后,手机页面在手指抬起来之后,依然会滚动一段距离的效果。

现在我尝试了两种方法
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的,后半段说了这个东西应该怎么用。
【前端】手机滚动-如何实现惯性滚动

github地址

以上是 【前端】手机滚动-如何实现惯性滚动 的全部内容, 来源链接: utcz.com/a/79434.html

回到顶部