• 原生 JS 实现移动端 Touch 滑动反弹


    什么是 Touch滑动?就是类似于 PC端的滚动事件,但是在移动端是没有滚动事件的,所以就要用到 Touch事件结合 js去实现,效果如下:

    1. 准备工作

    什么是移动端的 Touch事件?在移动端 Touch事件可以细分成三种,分别是: touchstarttouchmovetouchend,并且 touch事件必须要用 addEventListener去监听。

    • touchStart当手指触碰到屏幕的时候触发

    • touchmove当手指在屏幕上不断移动的时候触发

    • touchend当手指离开屏幕的时候触发

    Touch 事件触发的 Event 对象

    // 手指触碰到屏幕时触发
    element.addEventListener('touchstart',function (e){
    // 打印的事件对象
        console.log(e);
    })

    changedTouchestargetTouchestouches都是伪数组,里面装的是手指列表。

    三种返回对象的区别

    其实这三种返回的对象,都是表示用户触摸事件时的手指信息,之所以是一个伪数组,是因为有可能出现多指同时触摸,但是在实际工作中一般不去考虑多指的情况。而它们唯一的区别就是在 touchstarttouchmove事件的时候, changedTouchestargetTouchestouches都能获取到手指的信息,但是在 touchend事件的时候, targetTouchestouches对象是不能返回离开屏幕时的手指信息,只有 changedTouches对象能返回。

    有哪些手指信息?

    我们可以看下上面的图片,在 changedTouche[0]中,有些值:

    clientX:74  // 触摸点相对于浏览器的 viewport 左边缘的 x 坐标,不会包括左边的滚动离。
    clientY:73    // 触摸点相对于浏览器的 viewport 上边缘的 Y 坐标,不会包括上边的滚动离。
    screenX:2202    // 触摸点相对于屏幕左边缘的 x 坐标。
    screenY:327    // 触摸点相对于屏幕上边缘的 Y 坐标。
    pageX:65     // 触摸点相对于 document 的左边缘的 x 坐标,包括左边的滚动距离
    pageY:18      // 触摸点相对于 document 的上边缘的 Y 坐标,包括左边的滚动距离

    2. 基本结构

    此案例模拟的是移动端的一种滑动菜单效果。

    HTML部分

    <aside class="main">
        <div class="draw" id="draw">
            <ul>
                <li style="background:orange">列表一</li>
                <li style="background:yellowgreen">列表二</li>
                <li style="background:yellow">列表三</li>
                <li style="background:cyan">列表四</li>
                <li style="background:orangered">列表五</li>
                <li style="background:pink">列表六</li>
                <li style="background:red">列表七</li>
                <li style="background:purple">列表八</li>
                <li style="background:violet">列表九</li>
                <li style="background:brown">列表十</li>
            </ul>
        </div>
    </aside>

    css部分

    在列表的父盒子上设定一个 overflow:hidden属性,使超出盒子部分的列表暂时隐藏掉,后面会通过 js去实现滑动。

    * {
        margin:0;
        padding:0
    }
    html,body {
        width:100 %
    }
    aside {
        height:100 %;
        width:100 %
    }
    . draw {
        width:60px;
        height:500px;
        border:2px solid #ccc;
        overflow:hidden;
        position:fixed;
        left:10px;
        top:50 %;
        transform:translateY(- 50 %)
    }
    ul:after {
        content:"";
        display:block;
        visibility:hidden;
        height:0;
        clear:both
    }
    ul {
        zoom:1
    }
    li {
        list - style:none;
        float:left;
        width:60px;
        height:60px;
        line - height:60px;
        text - align:center
    }

    效果图

    3. 首次滑动

    手指触摸到列表向下滑动的时候,列表应该跟着向下滑动,当手指离开屏幕的时候,列表应该停在滑动的位置。这里就会用到上面准备阶段的知识点了,不明白的可以参考上面的概念。

    实现原理

    1. touchstart的时候,获取手指触摸的落点 A,通过这个点对象里面的 clientY属性,获取距离顶部可视区的距离;

    2. touchmove的时候,获取手指的点 B,同样的获取移动时距离顶部可视区的距离;

    3. touchmove的时候,还要做另一件事情,就是获取两点的差值( B.clientY-A.clientY),将这个差值动态赋值给 ul, ul只需要设置向 Y轴方向偏移这个距离,就能实现列表随手指滑动

    先来张示意图,怎么通过 js 让列表滑动起来

    示例代码

    var draw = document.querySelector('#draw');
    var ul = draw.children[0];
    // touchstart 时,记录手指在 Y 轴上的落点距离可视顶部距离
    var startY =0 ul.addEventListener('touchstart',function(e){    
        startY = e.changedTouches[0].clientY;
    })
    // touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离
    ul.addEventListener('touchmove',function(e){
        // 获取差值
        var dy = e.changedTouches[0].clientY - startY;
        // 设置 ul 在 Y 轴上的偏移
        ul.style.transform ='translateY('+ dy +'px)';
    })

    效果图

     

    4. 再次滑动

    上面的效果图,细心的朋友可能已经发现了问题,在第一次的时候,得到了我们想要的效果,但是在第二次的时候,我们继续向下移动了一段距离,但是 ul并没有接着第一次的位置继续向下,而是瞬间跳了上去。

    问题分析

    虽然第二次是继续向下移动了一段距离,但是触摸结束后,最终是将此时的差值,重新赋值给了 ulY轴偏移,所以视觉上“跳了上去”。

    解决方法:

    每一次滑动结束之后,都应该记录下此次滑动的距离,与之前的进行累加,待下一次滑动的时候, ulY轴的偏移值应该是之前的距离加上本次滑动的距离。

    • 新增 touchend事件,在该事件里同样的可以获取到本次滑动的距离,将它与上一次的距离相加,赋值给一个全局变量;

    • 在 touchmove事件里有点小改动,就是在给 ul设置偏移值的时候,除了本次滑动的差值还要加上这个上一次的值;

    示意图:

    示例代码

    var draw=document.querySelector('#draw');
    var ul=draw.children[0];
    
    var startY=0// 刚触碰到屏幕的时的手指信息
    var centerY=0// 用来记录每次触摸时上一次的偏移距离
    
    // touchstart 时,记录手指在 Y 轴上的落点距离可视顶部距离
    ul.addEventListener('touchstart',function(e){
        startY=e.changedTouches[0].clientY;
    })
    
    // touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离
    ul.addEventListener('touchmove',function(e){
    // 获取差值
        var dy=e.changedTouches[0].clientY-startY;
    // 上次的滑动距离加上本次的滑动距离
        var tempY=centerY+dy;
    // 设置 ul 在 Y 轴上的偏移
        ul.style.transform='translateY('+tempY+'px)';
    })
    
    // touchend 时,记录此时手指在 Y 轴上的落点距离可视顶部距离
    ul.addEventListener('touchend',function(e){
        // 获取差值
      var dy=e.changedTouches[0].clientY-startY;
        // 记录移动的距离
      centerY=centerY+dy;
    })

    效果图

    5. 限制滑动区间

    到上面一步,我们已经可以实现列表的滑动了,但是也存在一个问题,就是向上或者向下的时候没有限制,上下可以无限的滑动,甚至再用点力,就看不到列表了。为了美观和实用,这样肯定不行的,需要给它设定一个区间,设定向上或者向下最多只能留白多少。

    限制向下滑动最大区间

    设定向下最大区间的值比较简单,直接设定一个值,当上一次滑动的距离加上本次滑动的距离大于这个值的时候,就不让它再继续往下滑了,让他直接等于这个设定的值。

    示例代码

    var maxDown = 50; // 设定一个最大向下滑动的距离
    
    // touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离
    
    ul.addEventListener('touchmove', function (e) {
    
     // 获取差值
    
     var dy = e.changedTouches[0].clientY - startY;
    
     // 上次的滑动距离加上本次的滑动距离
    
     var tempY = centerY + dy;
    
     // 当上次滑动的距离加上本次滑动的距离 大于 设定的最大向下距离的时候
    
     if (tempY > maxDown) {
    
       // 直接让偏移的值 等于这个设定值
    
       tempY = maxDown;
    
     }
    
     // 设置 ul 在 Y 轴上的偏移
    
     ul.style.transform = 'translateY(' + tempY + 'px)';
    
    })

    限制向上滑动最大区间

    向上滑动时,当 ul的底部距盒子底部的距离大于设定值的时候,不让其继续向上滑动,关键是这个值怎么去判断?

    求出向上滑动最大值

     

    注意:因为 ul是向上滑动的,所以求得的距离前面要加上一个负号( -

    示例代码

    // 设定一个最大向下滑动的距离
    
    var maxDown = 50;
    
    // 求得一个最大向上滑动的距离
    
    var maxUp = -(ul.offsetHeight - draw.offsetHeight + maxDown);
    
    // touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离
    
    ul.addEventListener('touchmove', function (e) {
    
     // 获取差值
    
     var dy = e.changedTouches[0].clientY - startY;
    
     // 上次的滑动距离加上本次的滑动距离
    
     var tempY = centerY + dy;
    
     // 当上次滑动的距离加上本次滑动的距离 大于 设定的最大向下距离的时候
    
     if (tempY > maxDown) {
    
       tempY = maxDown;
    
     }
    
     // 当上次滑动的距离加上本次滑动的距离 小于 设定的最大向上距离的时候
    
     else if (tempY < maxUp) {
    
       // 直接让偏移的值 等于这个设定值
    
       tempY = maxUp;
    
     }
    
     // 设置 ul 在 Y 轴上的偏移
    
     ul.style.transform = 'translateY(' + tempY + 'px)';
    
    })

    效果图

     

    认真观察上图,虽然成功的设置了最大滑动区间,但是你有没有发现,一直往一个方向滑动的时候,虽然列表不会继续往下滑动,但是接着往相反方向滑动的时候,感觉列表滑不动,需要滑一段距离后,列表才会跟着走,这是为什么呢?因为滑动的过程 centerY是一直变的,列表虽然视觉上不动了,但是在 touchend事件的时候,它的 centerY值一直在累加。解决方法请往下看。

    6. 设定反弹区间

    “滑动反弹”,这里的反弹是本篇文章的最后一步,上面说到的问题,就在这里解决。因为每一次触发 touchend事件的时候, centerY值就累加一次,所以需要在 touchend事件里做判断。我们设定一个反弹区间,就是当 centerY的值大于或者小于某个值的时候,让它触发反弹。

    设定向上反弹值

    向上的值比较简单,设置成“ 0”。为什么是“ 0”呢?我们限定只要手指离开时,上一次的滑动距离加上本次的距离 >0的时候,就让它触发反弹,并且反弹回 0点的位置,也就是两次滑动的距离和 =0

    示例代码

    // 向上反弹
    
    var maxUpBounce = 0;
    
    // touchend 时,记录此时手指在 Y 轴上的落点距离可视顶部距离
    
    ul.addEventListener('touchend', function (e) {
    
     // 获取差值
    
     var dy = e.changedTouches[0].clientY - startY;
    
     // 记录移动的距离
    
     centerY = centerY + dy;
    
     // 两次滑动的距离 大于 设定的 向上 反弹值时
    
     if (centerY > maxUpBounce) {
    
       // 让两次滑动的距离 等于 设置的值
    
       centerY = maxUpBounce;
    
       // 添加过渡
    
       ul.style.transition = 'transform .5s';
    
       ul.style.transform = 'translateY(' + centerY + 'px)';
    
     }
    
    })

    设定向下反弹值

    向下的值其实跟之前求滑动区间差不多,我们参考下图,当列表向上滑动,滑动到列表底部的时候,只要此时再向上滑动,就让它向下反弹。向下反弹值就是 -(ul.offsetHeight-draw.offsetHeight),只要滑动的差值小于这个设定值,就让它向下反弹,并且反弹回设定值的位置。

    // 向上反弹值
    
    var maxUpBounce = 0;
    
    // 向下反弹值
    
    var maxDownBounce = -(ul.offsetHeight - draw.offsetHeight);
    
    // touchend 时,记录此时手指在 Y 轴上的落点距离可视顶部距离
    
    ul.addEventListener('touchend', function (e) {
    
     // 获取差值
    
     var dy = e.changedTouches[0].clientY - startY;
    
     // 记录移动的距离
    
     centerY = centerY + dy;
    
     // 两次滑动的距离 大于 设定的 向上 反弹值时
    
     if (centerY > maxUpBounce) {
    
       // 让两次滑动的距离 等于 设置的值
    
       centerY = maxUpBounce;
    
       // 添加过渡
    
       ul.style.transition = 'transform .5s';
    
       ul.style.transform = 'translateY(' + centerY + 'px)';
    
     }
    
     // 两次滑动的距离 小于 设定的 向下 反弹值时
    
     else if (centerY < maxDownBounce) {
    
       // 让两次滑动的距离 等于 设置的值
    
       centerY = maxDownBounce;
    
       // 添加过渡
    
       ul.style.transition = 'transform .5s';
    
       ul.style.transform = 'translateY(' + centerY + 'px)';
    
     }
    
    })

    注意:在 touchend事件的时候,给列表添加了 transition属性才会有反弹的效果,但是,下一次滑动的时候, touchmove事件的时候,这个属性还存在,所以就会出现滑动的时候有顿挫感,所以在 touchmove事件的时候,一进来就清一下过渡 ul.style.transition='none';

    完成后效果图

    7. 完整代码

    <!DOCTYPE html>
    
    <html lang="en">
    
    <head>
    
     <meta charset="UTF-8">
    
     <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
    
     <title>移动端 Touch 滑动反弹</title>
    
     <style>
    
       /* 样式初始化 */
    
       * {
    
         margin: 0;
    
         padding: 0;
    
       }
    
       html,
    
       body {
    
         width: 100%;
    
       }
    
       aside {
    
         height: 100%;
    
         width: 100%;
    
       }
    
       /* 列表的父盒子,限制宽高 */
    
       /* 注意设置overflow: hidden;样式后,超出这个盒子的ul将不会显示 */
    
       .draw {
    
         width: 60px;
    
         height: 500px;
    
         border: 2px solid #ccc;
    
         overflow: hidden;
    
         position: fixed;
    
         left: 10px;
    
         top: 50%;
    
         transform: translateY(-50%);
    
       }
    
       /* li 设置了浮动, 所以 ul 要清除浮动 */
    
       ul:after {
    
         content: "";
    
         display: block;
    
         visibility: hidden;
    
         height: 0;
    
         clear: both;
    
       }
    
       ul {
    
         zoom: 1;
    
       }
    
       li {
    
         list-style: none;
    
         float: left;
    
         width: 60px;
    
         height: 60px;
    
         line-height: 60px;
    
         text-align: center;
    
       }
    
     </style>
    
    </head>
    
    <aside class="main">
    
     <div class="draw" id="draw">
    
       <ul>
    
         <li style="background:orange">列表一</li>
    
         <li style="background:yellowgreen">列表二</li>
    
         <li style="background:yellow">列表三</li>
    
         <li style="background:cyan">列表四</li>
    
         <li style="background:orangered">列表五</li>
    
         <li style="background:pink">列表六</li>
    
         <li style="background:red">列表七</li>
    
         <li style="background:purple">列表八</li>
    
         <li style="background:violet">列表九</li>
    
         <li style="background:brown">列表十</li>
    
       </ul>
    
     </div>
    
    </aside>
    
    <body>
    
     <script>
    
       var draw = document.querySelector('#draw');
    
       var ul = draw.children[0];
    
       var startY = 0; // 刚触碰到屏幕的时的手指信息
    
       var centerY = 0; // 用来记录每次触摸时上一次的偏移距离
    
       var maxDown = 50; // 设定一个最大向下滑动的距离
    
       var maxUp = -(ul.offsetHeight - draw.offsetHeight + maxDown); // 求得一个最大向上滑动的距离
    
       var maxUpBounce = 0; // 向上反弹值
    
       var maxDownBounce = -(ul.offsetHeight - draw.offsetHeight); // 向下反弹值
    
       // touchstart 时,记录手指在 Y 轴上的落点距离可视顶部距离
    
       ul.addEventListener('touchstart', function (e) {
    
         startY = e.changedTouches[0].clientY;
    
       })
    
       // touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离
    
       ul.addEventListener('touchmove', function (e) {
    
         // 清除过渡
    
         ul.style.transition = 'none';
    
         // 获取差值
    
         var dy = e.changedTouches[0].clientY - startY;
    
         // 上次的滑动距离加上本次的滑动距离
    
         var tempY = centerY + dy;
    
         // 当上次滑动的距离加上本次滑动的距离 大于 设定的最大向下距离的时候
    
         if (tempY > maxDown) {
    
           tempY = maxDown;
    
         }
    
         // 当上次滑动的距离加上本次滑动的距离 小于 设定的最大向上距离的时候
    
         else if (tempY < maxUp) {
    
           tempY = maxUp;
    
         }
    
         // 设置 ul 在 Y 轴上的偏移
    
         ul.style.transform = 'translateY(' + tempY + 'px)';
    
       })
    
       // touchend 时,记录此时手指在 Y 轴上的落点距离可视顶部距离
    
       ul.addEventListener('touchend', function (e) {
    
         // 获取差值
    
         var dy = e.changedTouches[0].clientY - startY;
    
         // 记录移动的距离
    
         centerY = centerY + dy;
    
         // 两次滑动的距离 大于 设定的 向上 反弹值时
    
         if (centerY > maxUpBounce) {
    
           // 让两次滑动的距离 等于 设置的值
    
           centerY = maxUpBounce;
    
           // 添加过渡
    
           ul.style.transition = 'transform .5s';
    
           ul.style.transform = 'translateY(' + centerY + 'px)';
    
         }
    
         // 两次滑动的距离 小于 设定的 向下 反弹值时
    
         else if (centerY < maxDownBounce) {
    
           // 让两次滑动的距离 等于 设置的值
    
           centerY = maxDownBounce;
    
           // 添加过渡
    
           ul.style.transition = 'transform .5s';
    
           ul.style.transform = 'translateY(' + centerY + 'px)';
    
         }
    
       })
    
     </script>
    
    </body>
    
    </html>
  • 相关阅读:
    BZOJ4008: [HNOI2015]亚瑟王
    BZOJ4260: Codechef REBXOR
    BZOJ4408: [Fj Winter Camp 2016]神秘数
    BZOJ4010: [HNOI2015]菜肴制作
    ccc2016
    BZOJ3884: 上帝与集合的正确用法
    BZOJ1017: [JSOI2008]魔兽地图DotR
    BZOJ1011: [HNOI2008]遥远的行星
    BestCoder Round #73
    hdu4035(概率dp)
  • 原文地址:https://www.cnblogs.com/xiaoqi2018/p/10293470.html
Copyright © 2020-2023  润新知