3

看完本文的收获

  1. CV一份碰撞回弹动画代码(可封装成组件用在工作项目中)
  2. 进一步理解requestAnimationFrame相较于setInterval的‘丝滑’优势
  3. 觉得mouseenter和mouseleave(约等于css中的hover)mouseover和mouseout好用

需求描述

  • 公司首页网站上,要加一个漂浮公告功能
  • 就是一个醒目的盒子在来回漂浮
  • 遇到边界再碰撞一下
  • 就是重要信息的提醒
  • 如下效果图

代码实现——requestAnimationFrame方式

  • 建议复制粘贴运行一下,理解更加直观
  • 相关的逻辑见注释...
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        body {
            background-color: #e9e9e9;
        }

        #box {
            position: absolute;
            width: 120px;
            height: 120px;
            background-color: #baf;
            line-height: 120px;
            text-align: center;
        }
    </style>
</head>

<body>
    <div id="box">
        <a href="http://ashuai.work" target="_blank">公告!速点!</a>
    </div>
    <script>
        let box = document.getElementById('box');
        let xSpeed = 4; // x轴方向移动的速度
        let ySpeed = 2.4; // y轴方向移动的速度(类似于向量)
        let animationFrameId;
        // 兼容性的浏览器视口高度和宽度
        let width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
        let height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
        // 动画运行函数
        function run() {
            let rect = box.getBoundingClientRect(); // 返回盒子的位置对象信息
            // 下一瞬/帧 要修改盒子的left或top的值,等于当前位置加速度
            let newX = rect.left + xSpeed;
            let newY = rect.top + ySpeed;
            // 位置判断,到达边界,碰撞更改
            if (newX + box.offsetWidth >= width) {
                console.log('到达右边界');
                newX = width - box.offsetWidth; // 右边界位置是视口宽度去除自身宽度
                xSpeed = -xSpeed; // 移动方向颠倒过来
            } else if (newX <= 0) {
                console.log('到达左边界');
                newX = 0; // 左边界位置是起始位置是0
                xSpeed = -xSpeed; // 移动方向颠倒过来
            }
            // Y轴同理不赘述
            if (newY + box.offsetHeight >= height) {
                console.log('到达下边界');
                newY = height - box.offsetHeight;
                ySpeed = -ySpeed;
            } else if (newY <= 0) {
                console.log('到达上边界');
                newY = 0;
                ySpeed = -ySpeed;
            }
            // 更改位置即为移动dom元素
            box.style.left = `${newX}px`;
            box.style.top = `${newY}px`;
            // 再次run
            animationFrameId = requestAnimationFrame(run);
        }
        // 开始动画
        run();
        /**
         * 添加事件监听器,使用mouseenter和mouseleave
         *                即为鼠标hover效果
         * */
        box.addEventListener('mouseenter', () => {
            console.log('移入暂停');
            cancelAnimationFrame(animationFrameId); // 取消动画帧
        });
        box.addEventListener('mouseleave', () => {
            console.log('移出继续');
            run();
        });

    </script>
</body>

</html>

注意,想要hover效果,就用mouseenter和mouseleave

/**
 * 不要使用mouseover和mouseout,因为其内部子元素也会触发这个事件
 * 把上面的两个事件绑定注释掉,把下面的解开,当鼠标在box的子元素a上
 * 轻轻来回划过(要在box内操作)会出现动画抖动现象,即为触发了事件的暂停和启动
 * */ 
// box.addEventListener('mouseover', () => {
//     console.log('移入暂停');
//     cancelAnimationFrame(animationFrameId); // 取消当前等待执行的动画帧
// });
// box.addEventListener('mouseout', () => {
//     console.log('移出继续');
//     run();
// });

代码实现——定时器方式

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        body {
            background-color: #e9e9e9;
        }

        #box {
            position: absolute;
            width: 120px;
            height: 120px;
            background-color: #baf;
            line-height: 120px;
            text-align: center;
        }
    </style>
</head>

<body>
    <div id="box">
        <a href="http://ashuai.work" target="_blank">公告!速点!</a>
    </div>
    <script>
        let box = document.getElementById('box');
        let xSpeed = 4; // x轴方向移动的速度
        let ySpeed = 2.4; // y轴方向移动的速度(类似于向量)
        let timer;
        // 兼容性的浏览器视口高度和宽度
        let width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
        let height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
        // 定时器运行函数
        function run() {
            let rect = box.getBoundingClientRect(); // 返回盒子的位置对象信息
            // 下一瞬/帧 要修改盒子的left或top的值,等于当前位置加速度
            let newX = rect.left + xSpeed;
            let newY = rect.top + ySpeed;
            // 位置判断,到达边界,碰撞更改
            if (newX + box.offsetWidth >= width) {
                newX = width - box.offsetWidth;
                xSpeed = -xSpeed;
            } else if (newX <= 0) {
                newX = 0;
                xSpeed = -xSpeed;
            }
            if (newY + box.offsetHeight >= height) {
                newY = height - box.offsetHeight;
                ySpeed = -ySpeed;
            } else if (newY <= 0) {
                newY = 0;
                ySpeed = -ySpeed;
            }
            // 更改位置即为移动dom元素
            box.style.left = `${newX}px`;
            box.style.top = `${newY}px`;
        }
        // 开始动画
        timer = setInterval(run, 16.7); // 大约每秒60帧
        // 添加事件监听器
        box.addEventListener('mouseenter', () => {
            clearInterval(timer); // 鼠标悬停时,清除定时器
        });
        box.addEventListener('mouseleave', () => {
            timer = setInterval(run, 16.7); // 鼠标离开时,重新设置定时器
        });
    </script>
</body>

</html>
大家可以进一步把逻辑抽离,封装成一个组件,通过传参配置化调用,从而达到我们想要的效果。比如盒子回弹两个方向的移动速度,比如盒子的相关样式等... 这里不赘述,本文主打一个抛砖引玉,引起思考...

附录


水冗水孚
1.1k 声望588 粉丝

每一个不曾起舞的日子,都是对生命的辜负