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

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

1. 准备工作

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

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

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

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

Touch 事件触发的 Event 对象

 
  1. // 手指触碰到屏幕时触发

  2. element.addEventListener('touchstart', function (e) {

  3.    // 打印的事件对象

  4.    console.log(e);

  5. })

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

三种返回对象的区别

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

有哪些手指信息?

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

 
  1. clientX:74    // 触摸点相对于浏览器的 viewport 左边缘的 x 坐标,不会包括左边的滚动距离。

  2. clientY:73    // 触摸点相对于浏览器的 viewport 上边缘的 Y 坐标,不会包括上边的滚动距离。

  3. screenX:2202  // 触摸点相对于屏幕左边缘的 x 坐标。

  4. screenY:327   // 触摸点相对于屏幕上边缘的 Y 坐标。

  5. pageX:65      // 触摸点相对于 document 的左边缘的 x 坐标,包括左边的滚动距离

  6. pageY:18      // 触摸点相对于 document 的上边缘的 Y 坐标,包括左边的滚动距离

2. 基本结构

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

HTML部分

 
  1. <aside class="main">

  2.  <div class="draw" id="draw">

  3.    <ul>

  4.      <li style="background:orange">列表一</li>

  5.      <li style="background:yellowgreen">列表二</li>

  6.      <li style="background:yellow">列表三</li>

  7.      <li style="background:cyan">列表四</li>

  8.      <li style="background:orangered">列表五</li>

  9.      <li style="background:pink">列表六</li>

  10.      <li style="background:red">列表七</li>

  11.      <li style="background:purple">列表八</li>

  12.      <li style="background:violet">列表九</li>

  13.      <li style="background:brown">列表十</li>

  14.    </ul>

  15.  </div>

  16. </aside>

css部分

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

 
  1. /* 样式初始化 */

  2. * {

  3.  margin: 0;

  4.  padding: 0;

  5. }

  6.  

  7. html,

  8. body {

  9.  width: 100%;

  10.  

  11. }

  12.  

  13. aside {

  14.  height: 100%;

  15.  width: 100%;

  16. }

  17. /* 列表的父盒子,限制宽高 */

  18. /* 注意设置overflow: hidden;样式后,超出这个盒子的ul将不会显示 */

  19. .draw {

  20.  width: 60px;

  21.  height: 500px;

  22.  border: 2px solid #ccc;

  23.  overflow: hidden;

  24.  position: fixed;

  25.  left: 10px;

  26.  top: 50%;

  27.  transform: translateY(-50%);

  28. }

  29.  

  30. /* li 设置了浮动, 所以 ul 要清除浮动 */

  31. ul:after {

  32.  content: "";

  33.  display: block;

  34.  visibility: hidden;

  35.  height: 0;

  36.  clear: both;

  37. }

  38.  

  39. ul {

  40.  zoom: 1;

  41. }

  42.  

  43. li {

  44.  list-style: none;

  45.  float: left;

  46.  width: 60px;

  47.  height: 60px;

  48.  line-height: 60px;

  49.  text-align: center;

  50. }

效果图

3. 首次滑动

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

实现原理

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

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

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

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

示例代码

 
  1. var draw = document.querySelector('#draw');

  2. var ul = draw.children[0];

  3.  

  4. // touchstart 时,记录手指在 Y 轴上的落点距离可视顶部距离

  5. var startY = 0

  6. ul.addEventListener('touchstart', function (e) {

  7.  startY = e.changedTouches[0].clientY;

  8. })

  9.  

  10. // touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离

  11. ul.addEventListener('touchmove', function (e) {

  12.  // 获取差值

  13.  var dy = e.changedTouches[0].clientY - startY;

  14.  // 设置 ul 在 Y 轴上的偏移

  15.  ul.style.transform = 'translateY(' + dy + 'px)';

  16. })

效果图

4. 再次滑动

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

问题分析

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

解决方法:

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

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

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

示意图:

示例代码

 
  1. var draw = document.querySelector('#draw');

  2. var ul = draw.children[0];

  3.  

  4. var startY = 0 // 刚触碰到屏幕的时的手指信息

  5. var centerY = 0 // 用来记录每次触摸时上一次的偏移距离

  6.  

  7. // touchstart 时,记录手指在 Y 轴上的落点距离可视顶部距离

  8. ul.addEventListener('touchstart', function (e) {

  9.  startY = e.changedTouches[0].clientY;

  10. })

  11.  

  12. // touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离

  13. ul.addEventListener('touchmove', function (e) {

  14.  // 获取差值

  15.  var dy = e.changedTouches[0].clientY - startY;

  16.  // 上次的滑动距离加上本次的滑动距离

  17.  var tempY = centerY + dy;

  18.  // 设置 ul 在 Y 轴上的偏移

  19.  ul.style.transform = 'translateY(' + tempY + 'px)';

  20. })

  21.  

  22. // touchend 时,记录此时手指在 Y 轴上的落点距离可视顶部距离

  23. ul.addEventListener('touchend', function (e) {

  24.  // 获取差值

  25.  var dy = e.changedTouches[0].clientY - startY;

  26.  // 记录移动的距离

  27.  centerY = centerY + dy;

  28. })

效果图

5. 限制滑动区间

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

限制向下滑动最大区间

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

示例代码

 
  1. var maxDown = 50; // 设定一个最大向下滑动的距离

  2.  

  3. // touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离

  4. ul.addEventListener('touchmove', function (e) {

  5.  // 获取差值

  6.  var dy = e.changedTouches[0].clientY - startY;

  7.  

  8.  // 上次的滑动距离加上本次的滑动距离

  9.  var tempY = centerY + dy;

  10.  

  11.  // 当上次滑动的距离加上本次滑动的距离 大于 设定的最大向下距离的时候

  12.  if (tempY > maxDown) {

  13.    // 直接让偏移的值 等于这个设定值

  14.    tempY = maxDown;

  15.  }

  16.  

  17.  // 设置 ul 在 Y 轴上的偏移

  18.  ul.style.transform = 'translateY(' + tempY + 'px)';

  19. })

限制向上滑动最大区间

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

求出向上滑动最大值

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

示例代码

 
  1. // 设定一个最大向下滑动的距离

  2. var maxDown = 50;

  3. // 求得一个最大向上滑动的距离

  4. var maxUp = -(ul.offsetHeight - draw.offsetHeight + maxDown);

  5.  

  6. // touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离

  7. ul.addEventListener('touchmove', function (e) {

  8.  // 获取差值

  9.  var dy = e.changedTouches[0].clientY - startY;

  10.  

  11.  // 上次的滑动距离加上本次的滑动距离

  12.  var tempY = centerY + dy;

  13.  

  14.  // 当上次滑动的距离加上本次滑动的距离 大于 设定的最大向下距离的时候

  15.  if (tempY > maxDown) {

  16.    tempY = maxDown;

  17.  }

  18.  // 当上次滑动的距离加上本次滑动的距离 小于 设定的最大向上距离的时候

  19.  else if (tempY < maxUp) {

  20.    // 直接让偏移的值 等于这个设定值

  21.    tempY = maxUp;

  22.  }

  23.  

  24.  // 设置 ul 在 Y 轴上的偏移

  25.  ul.style.transform = 'translateY(' + tempY + 'px)';

  26. })

效果图

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

6. 设定反弹区间

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

设定向上反弹值

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

示例代码

 
  1. // 向上反弹

  2. var maxUpBounce = 0;

  3.  

  4. // touchend 时,记录此时手指在 Y 轴上的落点距离可视顶部距离

  5. ul.addEventListener('touchend', function (e) {

  6.  // 获取差值

  7.  var dy = e.changedTouches[0].clientY - startY;

  8.  // 记录移动的距离

  9.  centerY = centerY + dy;

  10.  

  11.  // 两次滑动的距离 大于 设定的 向上 反弹值时

  12.  if (centerY > maxUpBounce) {

  13.    // 让两次滑动的距离 等于 设置的值

  14.    centerY = maxUpBounce;

  15.    // 添加过渡

  16.    ul.style.transition = 'transform .5s';

  17.    ul.style.transform = 'translateY(' + centerY + 'px)';

  18.  }

  19. })

设定向下反弹值

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

示例代码

 
  1. // 向上反弹值

  2. var maxUpBounce = 0;

  3. // 向下反弹值

  4. var maxDownBounce = -(ul.offsetHeight - draw.offsetHeight);

  5.  

  6. // touchend 时,记录此时手指在 Y 轴上的落点距离可视顶部距离

  7. ul.addEventListener('touchend', function (e) {

  8.  // 获取差值

  9.  var dy = e.changedTouches[0].clientY - startY;

  10.  // 记录移动的距离

  11.  centerY = centerY + dy;

  12.  

  13.  // 两次滑动的距离 大于 设定的 向上 反弹值时

  14.  if (centerY > maxUpBounce) {

  15.    // 让两次滑动的距离 等于 设置的值

  16.    centerY = maxUpBounce;

  17.    // 添加过渡

  18.    ul.style.transition = 'transform .5s';

  19.    ul.style.transform = 'translateY(' + centerY + 'px)';

  20.  }

  21.  // 两次滑动的距离 小于 设定的 向下 反弹值时

  22.  else if (centerY < maxDownBounce) {

  23.    // 让两次滑动的距离 等于 设置的值

  24.    centerY = maxDownBounce;

  25.    // 添加过渡

  26.    ul.style.transition = 'transform .5s';

  27.    ul.style.transform = 'translateY(' + centerY + 'px)';

  28.  }

  29. })

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

完成后效果图

7. 完整代码

 
  1. <!DOCTYPE html>

  2. <html lang="en">

  3.  

  4. <head>

  5.  <meta charset="UTF-8">

  6.  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">

  7.  <meta http-equiv="X-UA-Compatible" content="ie=edge">

  8.  <title>移动端 Touch 滑动反弹</title>

  9.  <style>

  10.    /* 样式初始化 */

  11.    * {

  12.      margin: 0;

  13.      padding: 0;

  14.    }

  15.  

  16.    html,

  17.    body {

  18.      width: 100%;

  19.  

  20.    }

  21.  

  22.    aside {

  23.      height: 100%;

  24.      width: 100%;

  25.    }

  26.  

  27.    /* 列表的父盒子,限制宽高 */

  28.    /* 注意设置overflow: hidden;样式后,超出这个盒子的ul将不会显示 */

  29.    .draw {

  30.      width: 60px;

  31.      height: 500px;

  32.      border: 2px solid #ccc;

  33.      overflow: hidden;

  34.      position: fixed;

  35.      left: 10px;

  36.      top: 50%;

  37.      transform: translateY(-50%);

  38.    }

  39.  

  40.    /* li 设置了浮动, 所以 ul 要清除浮动 */

  41.    ul:after {

  42.      content: "";

  43.      display: block;

  44.      visibility: hidden;

  45.      height: 0;

  46.      clear: both;

  47.    }

  48.  

  49.    ul {

  50.      zoom: 1;

  51.    }

  52.  

  53.    li {

  54.      list-style: none;

  55.      float: left;

  56.      width: 60px;

  57.      height: 60px;

  58.      line-height: 60px;

  59.      text-align: center;

  60.    }

  61.  </style>

  62. </head>

  63. <aside class="main">

  64.  <div class="draw" id="draw">

  65.    <ul>

  66.      <li style="background:orange">列表一</li>

  67.      <li style="background:yellowgreen">列表二</li>

  68.      <li style="background:yellow">列表三</li>

  69.      <li style="background:cyan">列表四</li>

  70.      <li style="background:orangered">列表五</li>

  71.      <li style="background:pink">列表六</li>

  72.      <li style="background:red">列表七</li>

  73.      <li style="background:purple">列表八</li>

  74.      <li style="background:violet">列表九</li>

  75.      <li style="background:brown">列表十</li>

  76.    </ul>

  77.  </div>

  78. </aside>

  79.  

  80. <body>

  81.  <script>

  82.    var draw = document.querySelector('#draw');

  83.    var ul = draw.children[0];

  84.  

  85.    var startY = 0; // 刚触碰到屏幕的时的手指信息

  86.    var centerY = 0; // 用来记录每次触摸时上一次的偏移距离

  87.    var maxDown = 50; // 设定一个最大向下滑动的距离

  88.    var maxUp = -(ul.offsetHeight - draw.offsetHeight + maxDown); // 求得一个最大向上滑动的距离

  89.    var maxUpBounce = 0; // 向上反弹值

  90.    var maxDownBounce = -(ul.offsetHeight - draw.offsetHeight); // 向下反弹值

  91.  

  92.    // touchstart 时,记录手指在 Y 轴上的落点距离可视顶部距离

  93.    ul.addEventListener('touchstart', function (e) {

  94.      startY = e.changedTouches[0].clientY;

  95.    })

  96.  

  97.    // touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离

  98.    ul.addEventListener('touchmove', function (e) {

  99.      // 清除过渡

  100.      ul.style.transition = 'none';

  101.      // 获取差值

  102.      var dy = e.changedTouches[0].clientY - startY;

  103.  

  104.      // 上次的滑动距离加上本次的滑动距离

  105.      var tempY = centerY + dy;

  106.  

  107.      // 当上次滑动的距离加上本次滑动的距离 大于 设定的最大向下距离的时候

  108.      if (tempY > maxDown) {

  109.        tempY = maxDown;

  110.      }

  111.      // 当上次滑动的距离加上本次滑动的距离 小于 设定的最大向上距离的时候

  112.      else if (tempY < maxUp) {

  113.        tempY = maxUp;

  114.      }

  115.  

  116.      // 设置 ul 在 Y 轴上的偏移

  117.      ul.style.transform = 'translateY(' + tempY + 'px)';

  118.    })

  119.  

  120.    // touchend 时,记录此时手指在 Y 轴上的落点距离可视顶部距离

  121.    ul.addEventListener('touchend', function (e) {

  122.      // 获取差值

  123.      var dy = e.changedTouches[0].clientY - startY;

  124.      // 记录移动的距离

  125.      centerY = centerY + dy;

  126.  

  127.      // 两次滑动的距离 大于 设定的 向上 反弹值时

  128.      if (centerY > maxUpBounce) {

  129.        // 让两次滑动的距离 等于 设置的值

  130.        centerY = maxUpBounce;

  131.        // 添加过渡

  132.        ul.style.transition = 'transform .5s';

  133.        ul.style.transform = 'translateY(' + centerY + 'px)';

  134.      }

  135.      // 两次滑动的距离 小于 设定的 向下 反弹值时

  136.      else if (centerY < maxDownBounce) {

  137.        // 让两次滑动的距离 等于 设置的值

  138.        centerY = maxDownBounce;

  139.        // 添加过渡

  140.        ul.style.transition = 'transform .5s';

  141.        ul.style.transform = 'translateY(' + centerY + 'px)';

  142.      }

  143.    })

  144.  </script>

  145. </body>

  146.  

  147. </html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值