原生JS写全屏滚动插件教程
第三节
增加移动端触屏滑动事件
要实现跨平台的全屏滚动,自然少不了适配移动端的触摸滑动,这样子也能在移动端有更好的体验。
我们要实现一个简单的判定用户上下滑的操作,浏览器没有提供 slideUp或 slideDown这样的事件,而只是提供 touchstart、touchmove和touchend三个最基本的事件,所以我们要用这三个最基本的事件来完成上划下划判定。
var touchPoint = {}; // 首先我们需要一个全局变量来记录滑动位置。
//然后在initEvent函数里加入绑定三个触膜事件
function initEvent() {
。。。
touchPoint = {
startpoint: 0,
endpoint: 0
}
pageContainer.addEventListener('touchstart', touchStart);
pageContainer.addEventListener('touchmove', touchMove);
pageContainer.addEventListener('touchend', touchEnd);
}
然后实现这三个事件函数
function touchStart(e) {
// 当手指开始触摸屏幕时记录起始位置
touchPoint.startpoint = e.targetTouches[0].clientY;
}
function touchMove(e) {
// 手指滑动时禁止默认的行为(比如微信从顶部往下滑会整个网页拉下,或者内容超过容器长度时会有滚动条正常滚动,这都属于默认行为)
e.preventDefault();
// 每像素滑动都会记录最新的结束点。
touchPoint.endpoint = e.targetTouches[0].clientY;
}
function touchEnd(e) {
if (!touchPoint.endpoint) {
return false;
}
// 取纵向Y轴的结束点与起始点对比,结束点比起始点小60,视为往上滑,结束点比起始点大60视为往下滑。
// 60这个值视情况而定,有时候用户只是点按操作,起始点与结束点还是有细微的差距的,所以这个值不能太小。
if ((touchPoint.endpoint - touchPoint.startpoint) < -60) {
canSlide && canNext && slideNext();
} else if ((touchPoint.endpoint - touchPoint.startpoint) > 60) {
canSlide && canPrev && slidePrev();
}
// 完成一次滑屏后重置
touchPoint = {};
}
这样就实现了移动端上下滑动全屏滚动了,虽然有些迂回,但是通过touchstart、touchmove和touchend这三个事件还可以实现更多触控行为,比如移动端的点按操作、手势滑动、甚至多指触控的交互(留意e.targetTouches[0]是个数组)。
你可以用浏览器的移动端调试面板来测试,或者点击 生成可访问的页面 将你现有的代码生成页面再用手机访问生成后的链接地址测试。
增加一些动画元素
回顾一下开篇里展示的效果网页,里面每一屏还有不同的动画效果展示,是不是很像市面上那些微信H5推广页面一样,每滑出一屏会有各种元素迸发出来。
动画的实现我们可以用第三方的动画库animate.css,在html面板里的head标签内加入引用动画库
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@3.5.2/animate.min.css">
然后我们添加几个动画元素,在前面两屏加入下面的动画元素,
<!-- 每一屏滚动的容器-page1 -->
<div class="page-item page1">
<h2>Page1</h2>
<div class="step1 animated fadeIn"></div>
<div class="step2 animated fadeIn"></div>
</div>
<!-- 每一屏滚动的容器-page2 -->
<div class="page-item page2">
<h2>Page2</h2>
<div class="step1 animated bounceIn"></div>
<div class="step2 animated flipInX"></div>
<div class="step3 animated rollIn"></div>
</div>
还需要添加一下每一屏的动画元素的位置和形状大小
.page1 .step1 {
position: absolute;
top: 100px;
left: 19%;
text-align: center;
width: 100px;
height: 100px;
background-color: rgb(178, 116, 57);
border-radius: 50%;
}
.page1 .step2 {
position: absolute;
bottom: 100px;
right: 19%;
text-align: center;
width: 100px;
height: 100px;
background-color: rgb(116, 178, 9);
}
.page2 .step1 {
position: absolute;
top: 100px;
right: 5%;
text-align: center;
width: 100px;
height: 100px;
background-color: rgb(82, 110, 178);
border-radius: 50%;
}
.page2 .step2 {
position: absolute;
top: 100px;
left: 19%;
text-align: center;
width: 100px;
height: 100px;
background-color: rgb(116, 178, 9);
}
.page2 .step3 {
position: absolute;
bottom: 60px;
left: 19%;
text-align: center;
width: 150px;
height: 150px;
background-color: rgb(178, 79, 54);
}
更多关于animate.css动画库的用法请查看文档animate.css
可以看一下效果如何,运行后可以看到第一屏出现了动画元素,但是滚到第二屏却没有播放动画,实际上是运行的时候第二屏已经在播放动画了,所以当我们翻到第二屏的时候是已经播放完的状态了,这可怎么办呢?接着往下看。
控制动画入屏播放
我们要控制入屏时才能播放动画,首先全屏滚动是只有js知道什么时候入屏,所以控制动画播放应该交给js来做。
首先我们要对所有动画元素标个记号让js知道哪些是动画元素,在所有动画元素的class里加入step
<!-- 每一屏滚动的容器-page1 -->
<div class="page-item page1">
<h2>Page1</h2>
<div class="step step1 animated fadeIn"></div>
<div class="step step2 animated fadeIn"></div>
</div>
<!-- 每一屏滚动的容器-page2 -->
<div class="page-item page2">
<h2>Page2</h2>
<div class="step step1 animated bounceIn"></div>
<div class="step step2 animated flipInX"></div>
<div class="step step3 animated rollIn"></div>
</div>
然后在js做初始化时播放第一屏动画,隐藏其他屏动画
function initAnimation() {
var steps = pageContainer.querySelectorAll('.step');
if (steps.length > 0) {
for (var element of Array.from(steps)) {
// 将所有step元素隐藏起来
element.style.display = 'none';
}
}
// 然后播放第一屏动画
runAnimation(page - 1);
...
}
接着我们编写播放动画的函数
function runAnimation(index) {
var steps = pageItems[index].querySelectorAll('.step');
for (var element of Array.from(steps)) {
// 去除display即为显示元素,显示元素便自动播放动画。
element.style.display = '';
}
}
刚才只是初始化的操作,别忘了入屏播放动画,在slideNext和slidePrev函数里加入一句 runAnimation(page - 1);
// 滑动下一屏
function slideNext() {
pageItems[page - 1].style.transform = 'translate3d(0, -100%, 0)';
pageItems[page].style.transform = 'translate3d(0, 0, 0)';
page++;
runAnimation(page - 1);
slideCtrl()
}
// 滑动上一屏
function slidePrev() {
pageItems[page - 2].style.transform = 'translate3d(0, 0, 0)';
pageItems[page - 1].style.transform = 'translate3d(0, 100%, 0)';
page--;
runAnimation(page - 1);
slideCtrl()
}
再查看效果,这时滚动到第二屏看得到动画了,但是由于开始滚动就播放动画了,而全屏滚动的过渡是需要一点时间的,所以肉眼看到的动画好像是少了一点起始动画的样子,所以我们要给动画加点延迟播放。继续往下看。
控制动画元素延迟播放
我不只是要肉眼看到入屏的完整动画,我还想要自由控制不同的动画元素出现的时间不一样,这该怎么做呢?
自由的控制不同动画元素的出场顺序,js只能统一处理所有的动画元素,不能涉及太多具体的业务场景,所以决定这个出场顺序的时间可以放在元素本身,让js获取到出场时间统一处理。
为需要自定义出场顺序的动画元素加上我们自定义的data-delay属性,并设置毫秒为单位的延迟时间
<!-- 每一屏滚动的容器-page1 -->
<div class="page-item page1">
<h2>Page1</h2>
<div class="step step1 animated fadeIn" data-delay="1000"></div>
<div class="step step2 animated fadeIn"></div>
</div>
<!-- 每一屏滚动的容器-page2 -->
<div class="page-item page2">
<h2>Page2</h2>
<div class="step step1 animated bounceIn" data-delay="1000"></div>
<div class="step step2 animated flipInX" data-delay="1500"></div>
<div class="step step3 animated rollIn" data-delay="2000"></div>
</div>
然后在js里的播放动画函数里实现延迟播放
function runAnimation(index) {
var steps = pageItems[index].querySelectorAll('.step');
for (var element of Array.from(steps)) {
triggerAnim(element)
}
function triggerAnim(element) {
var delay = element.getAttribute('data-delay') || 100; //默认100毫秒的延迟
var timer = setTimeout(function () {
element.style.display = '';
clearTimeout(timer);
}, delay);
}
}
仔细思考一下为什么不直接在for循环里写triggerAnim函数里的代码呢?
控制动画元素手动播放
有时候我想要让用户有点交互,比如让用户点击某个按钮才执行动画。
这种动画我称它为惰性动画,需要被动唤醒才会执行的。所以我给它加个lazy 的标识,还需要加多个按钮唤醒惰性动画
<!-- 每一屏滚动的容器-page1 -->
<div class="page-item page1">
<h2>Page1</h2>
<div class="step step1 animated fadeIn" data-delay="1000"></div>
<div class="lazy step2 animated fadeIn"></div>
<button onclick="wakeup()" class="wakeup">唤醒动画</button>
</div>
稍微修一下按钮的样式
.wakeup {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
width: 80px;
height: 80px;
border-radius: 50%;
background-color: rgb(47, 95, 178);
color: #fff;
border: 0;
outline: 0;
}
js实现控制动画元素手动播放
initAnimation: function (items, index) {
var lazys = pageContainer.querySelectorAll('.lazy');
if (lazys.length > 0) {
for (var element of Array.from(lazys)) {
// 将所有lazy元素隐藏起来
element.style.display = 'none';
}
}
....
}
// 动画播放函数
function runAnimation(index, is_lazy) {
vvar steps = pageItems[index].querySelectorAll(is_lazy ? '.lazy' : '.step');
for (var element of Array.from(steps)) {
triggerAnim(element)
}
function triggerAnim(element) {
var delay = element.getAttribute('data-delay') || 100; //默认100毫秒的延迟
var timer = setTimeout(function () {
element.style.display = '';
clearTimeout(timer);
}, delay);
}
}
// wakeup唤醒动画
function wakeup() {
runAnimation(page - 1, true)
}
查看效果
编辑器区域太小可以点击 全屏展示
写了这么多js代码是不是感觉很乱而且所有变量方法全都是在全局作用域下的,这并不是我希望看到的插件的样子,我希望能够全部放到一个类里面,通过实例化产生一个控制全屏滚动的对象,并在对象中暴露一些方法,而有些不想暴露的方法变量是无法调用到的。下一节将会教大家如何封装代码,并以更清真的方式去调用,做成一个真正的插件。
下节内容会涉及到javascript的原型链、作用域链、设计模式等高级语法,请认真理解并查阅文档资料学习。
本节到此结束
下一节:插件的诞生
如有疑问或建议请发送到此邮箱反馈lipten@foxmail.com