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...
相关推荐
熊的猫37 分钟前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
别拿曾经看以后~2 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死2 小时前
导航栏及下拉菜单的实现
前端·css·css3
川石课堂软件测试2 小时前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
JerryXZR3 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
problc3 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
Gavin_9153 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼4 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
待磨的钝刨5 小时前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
前端青山10 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js