手机滚动-如何实现惯性滚动

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

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

现在我尝试了两种方法
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 style="border-color: green;"></p><p style="border-color: yellow;"></p><p style="border-color: #932132;"></p>
            <p style="border-color: #00007f;"></p><p style="border-color: red;"></p><p style="border-color: #432892;"></p>
            <p style="border-color: #007f00;"></p><p style="border-color: blue;"></p><p style="border-color: green;"></p>
            <p style="border-color: #7f0000;"></p><p style="border-color: #003947;"></p><p style="border-color: #cccc33;"></p>
            <p style="border-color: green;"></p><p style="border-color: yellow;"></p><p style="border-color: #932132;"></p>
            <p style="border-color: #00007f;"></p><p style="border-color: red;"></p><p style="border-color: #432892;"></p>
            <p style="border-color: #007f00;"></p><p style="border-color: blue;"></p><p style="border-color: green;"></p>
            <p style="border-color: #7f0000;"></p><p style="border-color: #003947;"></p><p style="border-color: #cccc33;"></p>
            <p style="border-color: green;"></p><p style="border-color: yellow;"></p><p style="border-color: #932132;"></p>
            <p style="border-color: #00007f;"></p><p style="border-color: red;"></p><p style="border-color: #432892;"></p>
            <p style="border-color: #007f00;"></p><p style="border-color: blue;"></p><p style="border-color: green;"></p>
            <p style="border-color: #7f0000;"></p><p style="border-color: #003947;"></p><p style="border-color: #cccc33;"></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 style="border-color: green;"></p><p style="border-color: yellow;"></p><p style="border-color: #932132;"></p>
            <p style="border-color: #00007f;"></p><p style="border-color: red;"></p><p style="border-color: #432892;"></p>
            <p style="border-color: #007f00;"></p><p style="border-color: blue;"></p><p style="border-color: green;"></p>
            <p style="border-color: #7f0000;"></p><p style="border-color: #003947;"></p><p style="border-color: #cccc33;"></p>
            <p style="border-color: green;"></p><p style="border-color: yellow;"></p><p style="border-color: #932132;"></p>
            <p style="border-color: #00007f;"></p><p style="border-color: red;"></p><p style="border-color: #432892;"></p>
            <p style="border-color: #007f00;"></p><p style="border-color: blue;"></p><p style="border-color: green;"></p>
            <p style="border-color: #7f0000;"></p><p style="border-color: #003947;"></p><p style="border-color: #cccc33;"></p>
            <p style="border-color: green;"></p><p style="border-color: yellow;"></p><p style="border-color: #932132;"></p>
            <p style="border-color: #00007f;"></p><p style="border-color: red;"></p><p style="border-color: #432892;"></p>
            <p style="border-color: #007f00;"></p><p style="border-color: blue;"></p><p style="border-color: green;"></p>
            <p style="border-color: #7f0000;"></p><p style="border-color: #003947;"></p><p style="border-color: #cccc33;"></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 style="border-color: green;"></p><p style="border-color: yellow;"></p><p style="border-color: #932132;"></p>
            <p style="border-color: #00007f;"></p><p style="border-color: red;"></p><p style="border-color: #432892;"></p>
            <p style="border-color: #007f00;"></p><p style="border-color: blue;"></p><p style="border-color: green;"></p>
            <p style="border-color: #7f0000;"></p><p style="border-color: #003947;"></p><p style="border-color: #cccc33;"></p>
            <p style="border-color: green;"></p><p style="border-color: yellow;"></p><p style="border-color: #932132;"></p>
            <p style="border-color: #00007f;"></p><p style="border-color: red;"></p><p style="border-color: #432892;"></p>
            <p style="border-color: #007f00;"></p><p style="border-color: blue;"></p><p style="border-color: green;"></p>
            <p style="border-color: #7f0000;"></p><p style="border-color: #003947;"></p><p style="border-color: #cccc33;"></p>
            <p style="border-color: green;"></p><p style="border-color: yellow;"></p><p style="border-color: #932132;"></p>
            <p style="border-color: #00007f;"></p><p style="border-color: red;"></p><p style="border-color: #432892;"></p>
            <p style="border-color: #007f00;"></p><p style="border-color: blue;"></p><p style="border-color: green;"></p>
            <p style="border-color: #7f0000;"></p><p style="border-color: #003947;"></p><p style="border-color: #cccc33;"></p>
        </div>
    </div>
</body>
</html>

虽然现在看起来可以滚动了,但是感觉距离原生的惯性滚动、以及iScroll、better-scroll等的效果差的还是挺远的。看他们的源码,真是找不到哪儿是哪儿的东西,希望懂的大佬们,能够指点指点我!!!

阅读 4.3k
2 个回答

主要是算法(运动曲线)的问题,再配合tranform进行移动
好的曲线,可以让你动的更加自然

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题