H5 左右滑屏切页原理

采用HTML5 触摸事件(Touch) 和 CSS3动画(Transform,Transition)来实现的,效果图如下所示,本文简单说一下其实现原理和主要思路。

实现原理

  • 手指在屏幕上滑动时,页面跟随移动

  • 当手指离开屏幕时,计算手指在屏幕上停留的时间

  • 如果停留时间小于300ms,则认为是快速滑动切换,页面切换到下一页

  • 如果停留时间大于300ms,则认为是慢速滑动,慢速滑动按如下规则处理:

如果滑动距离小于屏幕宽度的50%,则回退到上一页

如果滑动距离大于屏幕宽度的50%,则切换到下一页

 对于多手指触摸操作:  

  • 第一个手指触摸时,正常滑动  
  • 第二个手指按下时,不做任何响应操作,继续原有的滑动  
  • 当任意一个手指离开屏幕时,结束滑动,剩余的手指操作不做任何处理  
  • 当第二个手指再次按下时,触发新的滑动开始,但由于获取的是touches[0],因此,第一个手指移动才会引起页面的滑动  
  • 支持多手指同时按下时进行滑动

假设有4个页面,每个页面占屏幕100%宽,则创建一个容器,将其宽度(width) 设置为400%,并让这4个页面平分整个容器,最后将容器的默认位置设置为0,overflow设置为hidden,这样屏幕就默认显示第一个页面。

  <article class="slide-content">
      <section class="slide-item" data-index="0">
           <h3>页面-1</h3>
      </section>
      <section class="slide-item" data-index="1">
           <h3>页面-2</h3>
      </section>
      <section class="slide-item" data-index="2">
           <h3>页面-3</h3>
      </section>
      <section class="slide-item" data-index="3">
           <h3>页面-4</h3>
      </section>
</article>

css样式:

     .slide-content,
     .slide-item {
        height: 100%;
      }

      .slide-content {
        width: 400%;
        display: -webkit-box;
        overflow: hidden;
        -webkit-transform: translate3d(0, 0, 0);
        transform: translate3d(0, 0, 0);
        backface-visibility: hidden;
      }

      .slide-item {
        -webkit-box-flex: 1;
        width: 0;
      }

主要思路

注册touchstart,touchmove和touchend事件,当手指在屏幕上滑动时,使用CSS3的transform来实时设置slide-content的位置,在这里采用translate3d来代替translateX,translate3d可以主动开启手机GPU加速渲染,页面滑动更流畅。

从手指放在屏幕上、滑动操作、再到离开屏幕是一个完整的操作过程,对应的操作会触发如下事件:

手指放在屏幕上:ontouchstart

手指在屏幕上滑动:ontouchmove

手指离开屏幕:ontouchend

我们需要捕获触摸事件的这三个阶段来完成页面的滑动:

ontouchstart: 初始化变量, 记录手指所在的位置,记录当前时间

        // 手指放在屏幕上
         scrollContainer.addEventListener("touchstart", function (e) {
              e.preventDefault();
          
              // 单手指触摸或者多手指同时触摸,禁止第二个手指延迟操作事件
              if (e.touches.length === 1 || isTouchEnd) {
                  var touch = e.touches[0];
                  startX = touch.pageX;
                  startY = touch.pageY;
                  initialPos = currentPosition; // 本次滑动前的初始位置
                  scrollContainer.style.webkitTransition = ""; // 取消动画效果
                  startT = +new Date(); // 记录手指按下的开始时间
                  isMove = false; // 是否产生滑动
                  isTouchEnd = false; // 当前滑动开始
              }
          });              

ontouchmove: 获得当前所在位置,计算手指在屏幕上的移动差量deltaX,然后使页面跟随移动

        //手指在屏幕上滑动,页面跟随手指移动
          scrollContainer.addEventListener("touchmove", function (e) {
            e.preventDefault();

            // 如果当前滑动已结束,不管其他手指是否在屏幕上都禁止该事件
            if (isTouchEnd) return;

            var touch = e.touches[0];
            var deltaX = touch.pageX - startX;
            var deltaY = touch.pageY - startY;

            var translate = initialPos + deltaX; // 当前需要移动到的位置

            // 如果translate>0 或 < maxWidth,则表示页面超出边界,即滑动范围是[maxWidth, 0]
            if (translate > 0) {
              //最左边是0
              translate = 0;
            }
            if (translate < maxWidth) {
              //最右边是maxWidth,负值
              translate = maxWidth;
            }
            deltaX = translate - initialPos; //滑动到左右边界时,按照滑动范围计算本次水平能滑动的最大距离(超出不算)
            transform.call(scrollContainer, translate);
            isMove = true;
            moveLength = deltaX;
            direction = deltaX > 0 ? "right" : "left"; // 判断手指滑动的方向
          });

ontouchend:手指离开屏幕时,计算屏幕最终停留在哪一页。首先计算手指在屏幕上的停留时间deltaT,如果deltaT<300ms,则认为是快速滑动,相反则是慢速滑动,快速滑动和慢速滑动的处理是不同的:

  • 如果是快速滑动,则让当前页面完整的停留在屏幕中央(需要计算当前页面还有多少需要滑动)
  • 如果是慢速滑动,还需要判断手指在屏幕上滑动的距离,如果滑动的距离没有超过屏幕宽度50%,则要回退到上一页,相反则要停留在当前页面 
         //手指离开屏幕时,计算最终需要停留在哪一页
          scrollContainer.addEventListener("touchend", function (e) {
            e.preventDefault();

            var translate = 0;
            var deltaT = +new Date() - startT; // 计算手指在屏幕上停留的时间
            if (isMove && !isTouchEnd) {
              // 发生了滑动,并且当前滑动事件未结束
              isTouchEnd = true; // 标记当前完整的滑动事件已经结束
              // 使用动画过渡让页面滑动到最终的位置
              scrollContainer.style.webkitTransition =
                "0.3s ease -webkit-transform";
              if (deltaT < 300) {
                // 如果停留时间小于300ms,则认为是快速滑动,无论滑动距离是多少,都停留到下一页
                if (currentPosition === 0 && translate === 0) {
                  //没有滑动页面
                  return;
                }
                //不管滑动多少,最终只滑动一页
                translate =
                  direction === "left"
                    ? currentPosition - (pageWidth + moveLength)
                    : currentPosition + pageWidth - moveLength;

                // 如果最终位置超过边界位置,则停留在边界位置
                translate = translate > 0 ? 0 : translate; // 左边界
                translate = translate < maxWidth ? maxWidth : translate; // 右边界
              } else {
                // 如果滑动距离小于屏幕的50%,则退回到上一页
                if (Math.abs(moveLength) / pageWidth < 0.5) {
                  //currentPosition为当前滑动到的距离,即之前划过的页码数的距离+本次滑动的距离,而moveLength是本次滑动的距离=》之差就是之前划过的页码数的距离
                  //若当前为未滑动过,那么初始translateY为0,currentPosition等于moveLength滑动的距离,之差为0及translateY为0不会切换页面
                  //若滑动到第二页,那么初始translateY为-pageWidth,则在此页码上移动currentPosition为-pageWidth+moveLength(在原有滑动基础上滑动了moveLength),所以之差仍为currentPosition,即不会切换页面
                  translate = currentPosition - moveLength;
                } else {
                  // 如果滑动距离大于屏幕的50%,则滑动到下一页
                  //currentPosition - moveLength可只是保留上次滑动的距离,那么+pageWidth和-pageWidth就是向右或向左滑动一页
                  translate =
                    direction === "left"
                      ? currentPosition - (pageWidth + moveLength)
                      : currentPosition + pageWidth - moveLength;

                  translate = translate > 0 ? 0 : translate;
                  translate = translate < maxWidth ? maxWidth : translate;
                }
              }
              transform.call(scrollContainer, translate); // 执行滑动,让页面完整的显示到屏幕上

              pageNow = Math.round(Math.abs(translate) / pageWidth) + 1; // 计算当前的页码
              setTimeout(function () {
                setPageNow(); // 设置页码,DOM操作需要放到异步队列中,否则会出现卡顿
              }, 100);
            }
          });

 除此之外,还要计算当前页面是第几页,并设置当前页码

//计算当前的页码
pageNow = Math.round(Math.abs(translate) / pageWidth) + 1;

setTimeout(function(){
    //设置页码,DOM操作需要放到子线程中,否则会出现卡顿
    this.setPageNow();
}.bind(this),100);

具体实例:https://camillezj.github.io/Swiper.html(用手机模拟器或手机查看效果)

代码:https://github.com/camilleZJ/camillezj.github.io/blob/master/Swiper.html

 

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值