原生JS写全屏滚动插件教程

第三节

  • 增加移动端触屏滑动事件
  • 增加一些动画元素
  • 控制动画入屏播放
  • 控制动画元素延迟播放
  • 控制动画元素手动播放

增加移动端触屏滑动事件

要实现跨平台的全屏滚动,自然少不了适配移动端的触摸滑动,这样子也能在移动端有更好的体验。

我们要实现一个简单的判定用户上下滑的操作,浏览器没有提供 slideUpslideDown这样的事件,而只是提供 touchstarttouchmovetouchend三个最基本的事件,所以我们要用这三个最基本的事件来完成上划下划判定。

                        
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 = {};
}
                        
                    

这样就实现了移动端上下滑动全屏滚动了,虽然有些迂回,但是通过touchstarttouchmovetouchend这三个事件还可以实现更多触控行为,比如移动端的点按操作、手势滑动、甚至多指触控的交互(留意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的原型链、作用域链、设计模式等高级语法,请认真理解并查阅文档资料学习。

本节到此结束

下一节:插件的诞生