为了书写方便,用了es6语法
为了简化代码的显示,keyframe 以及 animation 都没有写前缀

一、需求

最近一个网站很多地方需要用到滚动侦测,在某一个div出现在视野的时候,赋予动画出现。

一般有几个同样的动画,比如从从左右两侧进入,或者上下两侧进入。

涉及的内容:

  1. 滚动侦测
  2. CSS3动画

不使用javascirpt的动画,没有加载jquery,因此也没有使用jquery 的animate。

动画使用css3 animation实现。

二、实现

1、理论

基本的理论很简单,现在也有很多做的。

无非是当box进入视野区域的时候,开始加载动画,图片懒加载也是这样的道理。

2、getBoundingClientRect() 判断是否在视野区域内

滚动我直接用了 window.onscoill()来操作。

判断一个div出现在视野中有很多方式,网上最多的无非是使用 jqueryoffset().top ,我起初想用原生javascript封装一个 offset() ,但是因为这个东西,margin-top/padding-top 等都有影响,而且需要递归处理每一级的offsetTop,所以就放弃了。

判断div是否在视野区域内,使用的是Element.getBoundingClientRect()

这个方法可以看一下MDN的说明:

除了x/y两个属性,它支持的top/left/height/width基本上不存在兼容性的问题(因为是CSS3动画,所以最低IE9)。

之前有人网上说这个方法在IOS不适用,现在已经不存在这个问题,亲测好用。

getBoundingClientRect()返回的对象中的top准确的距离窗口顶部的值,而且这个值是实时变化的

1.png

因此只要这个top值小于窗口的可视高度时,div就进入了可视区域。

滚动的时候只需要和窗口可视高度比较即可。

function isInSign(elem) {
    const elemRect = elem.getBoundingClientRect(),
        windowHeight = window.innerHeight || document.body.clientHeight;
    if (elemRect.top < windowHeight) {
        return true;
    }
    return false;
}

3、动画的添加使用class

前面提到,动画是使用css来做的,比如从左侧移入,使用的是translateX();

动画的 class 为 move, 所以css中需要对 xxx.move 进行动画控制

#middle.move ,#bottom.move {
    animation: movefromleft 2s;
    animation-fill-mode: forwards;
}
@keyframes movefromleft {
    from {
        opacity: 0;
        transform: translateX(-300px);
    }
    to {
        opacity: 1;
        transform: translateX(0px);
    }
}

4、总体实现

文档加载完后,如果在滚动过程中,div出现在了可视视野中,则增加move class, 如果没有class 已经存在 move了,则不增加,所以动画只会执行一次,就是在出现的时候。

为了move不重复,可以使用某种特别的模式声明 比如 from-left-move 这样的名字,不过一般不会再class里面使用 move 名字的。

三、整体示例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport"  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        div {
            margin: 0 auto;
            max-width: 100%;
            width: 600px;
        }

        #top {
            height: 1200px;
            background-color: #aaaaaa;
        }

        #middle {
            margin-top:200px;
            opacity: 0;
            height: 400px;
            background-color: #000000;
        }

        #bottom {
            height: 300px;
            background-color: #333;
        }

        #middle.move ,#bottom.move {
            animation: movefromleft 2s;
            animation-fill-mode: forwards;
        }

        @keyframes movefromleft {
            from {
                opacity: 0;
                transform: translateX(-300px);
            }
            to {
                opacity: 1;
                transform: translateX(0px);
            }
        }
    </style>
</head>

<body>
    <div id="top"></div>
    <div id="middle"></div>
    <div id="bottom"></div>
</body>
<script>
    window.onload = (event) => {
        const middle = document.getElementById('middle'),
        bottom = document.getElementById('bottom');
        window.onscroll = () => {
            listenAnimationClass(middle);
            listenAnimationClass(bottom); 
        }
        function listenAnimationClass(elem){
            if (isInSign(elem)) {
                if (!elem.className.includes('move')) {
                    elem.className = elem.className + ' move';
                }
            }
        }
    }
    function isInSign(elem) {
        const elemRect = elem.getBoundingClientRect(),
            windowHeight = window.innerHeight || document.body.clientHeight;
        if (elemRect.top < windowHeight) {
            return true;
        }
        return false;
    }

</script>

</html>