css视口中盒子边界碰撞回弹效果,定时器方式和requestAnimationFrame方式,如漂浮公告场景

看完本文的收获

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

需求描述

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

代码实现------requestAnimationFrame方式

  • 建议复制粘贴运行一下,理解更加直观
  • 相关的逻辑见注释...
html 复制代码
<!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

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

代码实现------定时器方式

html 复制代码
<!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>

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

附录

  • 关于这个盒子边界碰撞回弹效果,在一些网站上也有这个需求,如汇金公司网站:www.huijin-inv.cn/
  • 关于requestAnimationFrame的优势特色的进一步理解,可参见笔者的这篇文章:juejin.cn/post/719072...
  • 笔者空闲时间,也会整理点css动画,参见笔者的这篇文章,代码会在github仓库中一点点更新:juejin.cn/post/717253...
相关推荐
JUNAI_Strive_ving16 分钟前
番茄小说逆向爬取
javascript·python
看到请催我学习25 分钟前
如何实现两个标签页之间的通信
javascript·css·typescript·node.js·html5
twins35201 小时前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky1 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
哪 吒1 小时前
华为OD机试 - 几何平均值最大子数(Python/JS/C/C++ 2024 E卷 200分)
javascript·python·华为od
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
Q_w77422 小时前
一个真实可用的登录界面!
javascript·mysql·php·html5·网站登录
昨天;明天。今天。2 小时前
案例-任务清单
前端·javascript·css
一丝晨光2 小时前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby