采用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