前面的四篇文章分别实现了匀速运动、缓冲运动和弹性运动,本文继续,实现最后一个常见运动——碰撞运动。
碰撞运动
碰撞运动是两个物体接触后速度反向的运动。物体在某个容器内运动,当碰到容器的边缘后,速度反向。
去掉外层容器中间的分割线,恢复到最初始的布局:
#par{
width: 600px;
height: 300px;
border: 1px solid #333;
margin:50px auto;
position: relative;
text-align: center;
padding-top: 10px;
}
#inner{
width: 100px;
height: 100px;
background: orange;
position: absolute;
left: 0;
top: calc(50% - 50px);
text-align: center;
line-height: 100px;
opacity: 1;
}
碰撞运动初始布局.png
碰撞运动在容器内部进行,因此具有两个速度:水平方向的速度 speedX 和垂直方向的速度 speedY。
修改 animate 函数,实现基本的碰撞效果:
function animate(ele = null,config = {
speedX:10,
speedY:10,
container:null,
}){
// 清除定时器
clearInterval(ele.timer);
let { speedX, speedY, container } = config;
speedX = speedX || 10;
speedY = speedY || 10;
return new Promise((resolve)=>{
ele.timer = setInterval(()=>{
let left = parseInt(getCurrentStyle(ele,"left")) + speedX;
let top = parseInt(getCurrentStyle(ele,"top")) + speedY;
// 判断运动是否过界
// 判断左右边界
if(left<= 0){
speedX *= -1;
left = 0;
}else if(left + ele.offsetWidth >= container.clientWidth){
speedX *= -1;
left = container.clientWidth - ele.offsetWidth;
}
// 判断上下边界
if(top <= 0){
speedY *= -1;
top = 0;
}else if(top + ele.offsetHeight >= container.clientHeight){
speedY *= -1;
top = container.clientHeight - ele.offsetHeight;
}
// 设置速度
ele.style.left = left + "px";
ele.style.top = top + "px";
},30);
});
}
调用运动函数:
...
const ele = document.getElementById("inner");
async function start(){
await animate(ele,{
container:document.getElementById("par"),
speedX:15,
speedY:20
});
}
...
效果如图所示:
简易碰撞运动.gif在碰撞运动的过程中,需要对触底进行判断,当小滑块在运动中会触底(或者超出底边时),将小滑块拉回底边位置并设置速度反向。
带有重力的碰撞运动
和弹性运动的摩擦力一样,我们可以使用一个重力系数来模拟重力:
function animate(ele = null,config = {
speedX:10,
speedY:10,
gravity:5,
coeff:0.8,
container:null,
}){
// 清除定时器
clearInterval(ele.timer);
let { speedX, speedY, container,gravity,coeff } = config;
speedX = speedX || 10;
speedY = speedY || 10;
gravity = gravity || 5;
coeff = coeff || 0.8;
return new Promise((resolve)=>{
ele.timer = setInterval(()=>{
// 生活常识告诉俺们,每次下落时,垂直速度都比上一次快
speedY += gravity;
let left = parseInt(getCurrentStyle(ele,"left")) + speedX;
let top = parseInt(getCurrentStyle(ele,"top")) + speedY;
// 判断运动是否过界
// 判断左右边界
if(left<= 0){
speedX *= -coeff;
speedY *= coeff;
left = 0;
}else if(left + ele.offsetWidth >= container.clientWidth){
speedX *= -coeff;
speedY *= coeff;
left = container.clientWidth - ele.offsetWidth;
}
// 判断上下边界
if(top <= 0){
speedY *= -coeff;
speedX *= coeff;
top = 0;
}else if(top + ele.offsetHeight >= container.clientHeight){
speedY *= -coeff;
speedX *= coeff;
top = container.clientHeight - ele.offsetHeight;
}
// 设置速度
ele.style.left = left + "px";
ele.style.top = top + "px";
},30);
});
}
清除定时器
当碰撞运动的水平速度(speedX)和垂直速度(speedY)都为 0 ,并且小滑块处于容器底部时,碰撞运动停止,此时我们就该清除定时器了。同时,也需要对 speedX 和 speedY 进行浮点数处理,以及重力系数的合法值检验。
修改 animate 函数:
function animate(ele = null,config = {
speedX:0,
speedY:0,
gravity:5,
coeff:0.8,
container:null,
}){
// 清除定时器
clearInterval(ele.timer);
let { speedX, speedY, container,gravity,coeff } = config;
speedX = speedX || 0;
speedY = speedY || 0;
gravity = gravity || 5;
// 错误处理
if(coeff > 1){
throw new Error("碰撞系数参数错误!");
}
coeff = coeff || 0.8;
return new Promise((resolve)=>{
ele.timer = setInterval(()=>{
// 生活常识告诉俺们,每次下落时,垂直速度都比上一次快
speedY += gravity;
let left = parseInt(getCurrentStyle(ele,"left")) + speedX;
let top = parseInt(getCurrentStyle(ele,"top")) + speedY;
// 判断运动是否过界
// 判断左右边界
if(left<= 0){
speedX *= -coeff;
speedY *= coeff;
left = 0;
}else if(left + ele.offsetWidth >= container.clientWidth){
speedX *= -coeff;
speedY *= coeff;
left = container.clientWidth - ele.offsetWidth;
}
// 判断上下边界
if(top <= 0){
speedY *= -coeff;
speedX *= coeff;
top = 0;
}else if(top + ele.offsetHeight >= container.clientHeight){
speedY *= -coeff;
speedX *= coeff;
top = container.clientHeight - ele.offsetHeight;
}
// 处理小数
if(Math.abs(speedX) < 1){
speedX = 0;
}
if(Math.abs(speedY) < 1){
speedY = 0;
}
// 运动停止条件
if(!speedX && !speedY && top === container.clientHeight - ele.offsetHeight){
clearInterval(ele.timer);
resolve()
}else{
// 设置速度
ele.style.left = left + "px";
ele.style.top = top + "px";
}
},30);
});
}
调用运动函数:
const ele = document.getElementById("inner");
async function start(){
await animate(ele,{
container:document.getElementById("par"),
});
}
最终效果如图:
最终效果.gif总结
本文介绍了碰撞运动的实现,下边是一些要点:
- 碰撞运动拥有水平和垂直两个方向的速度
- 碰撞运动需要进行上下左右四个边界的检测
- 带有重力的碰撞运动,每次碰撞之后,都要乘以碰撞系数,以减小速度
- 带重力的碰撞运动停止条件:水平速度和垂直速度都为0
- 当水平速度或垂直速度的绝对值趋近于 0 时,进行精度处理,直接将此速度设置为 0
- 碰撞运动停止条件:水平速度(speedX)和垂直速度(speedY)都为 0,并且物体处于容器底部
碰撞运动至此就写完了,下篇文章中,将在此基础上,介绍一个基于拖拽的碰撞运动。
完。
网友评论