看完本文的收获
- CV一份碰撞回弹动画代码(可封装成组件用在工作项目中)
- 进一步理解
requestAnimationFrame
相较于setInterval
的'丝滑'优势- 觉得
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...