前言
MoYu有点无聊,突发奇想做个能够弹射星星的黑洞会不会很酷?
结果做下来发现,实际上就是实现了最基本的碰撞检测,好像也没啥好玩的,想拓展一下貌似也想不到有啥好拓展的了。
虽然也没啥好玩的,不过发出来给有兴趣的人康康,兴许还能得到一些大佬的建议可以学习学习,还也能开拓一下创意眼界。
效果展示
星星的生成
四个字,随机坐标。额外再随机加点CSS样式即可。
这里利用了transform: translate(X,Y)进行定位,而不是left、top,原因很简单,性能更高
首先为了方便用JS创建更多的星星,我们可以在html里写一个星星的原型,display设为none(当然要用纯JS写也行,但JS调样式比较麻烦,就不推荐了)
<div class="holePhantom" style="display: none"></div>
接着是创建星星:
const newStar = star.cloneNode(true); // 从星星原型拷贝新节点作为星星
// 随机生成星星的坐标
let left = Math.random() * document.body.offsetWidth;
let top = Math.random() * document.body.offsetHeight;
// 利用translate移动,因为transform比单纯的left和right性能更佳
newStar.style.transform = `translate(${left}px, ${top}px)`;
newStar.style.display = "block";
const starCore = newStar.getElementsByClassName("star")[0];
document.body.appendChild(newStar);
黑洞
黑洞的生成就很简单了,只是一个单纯的html元素,只是多了点样式,样式详情可以看效果展示里的代码,就不赘述了。
JS用到的定量
后面的代码中会用到的一些定量,先罗列出来,以免看不懂了。
const holePhantomNum = 100; // Hole的幻影拖尾数量
const holePhantomD = 10; // Hole幻影直径
const holePhantomR = 5; // Hole幻影半径
const StarNum = 100; // 星星数量
const star = document.getElementsByClassName("starBox")[0];
const Hole = document.getElementsByClassName("holeBox")[0];
const HoleD = 100; // Hole直径
const HoleR = HoleD / 2; // Hole半径
const InitHoleX = Hole.offsetLeft + HoleR; // 初始Hole位置 X坐标
const InitHoleY = Hole.offsetTop + HoleR; // 初始Hole位置 Y坐标
const HoleShadow = 20; // Hole的阴影宽度
const StarScale = 0.6; // 星星的缩放大小,同 .star 保持一致
const StarWidth = 10; // 星星的宽度
const StarHeight = 10; // 星星的高度
const otherXDist = StarWidth + HoleShadow; // 除星星与Hole的坐标距离外,额外的X轴距离
const otherYDist = StarHeight + HoleShadow; // 除星星与Hole的坐标距离外,额外的Y轴距离
const starArr = []; // 存储星星Dom的数组
批量生成星星
从简单的单个星星生成,到批量星星生成,只是单纯多了个for循环罢了。
但其实出了单纯的生成外,还要注意我们现在要做的是能够弹射星星的黑洞,那么默认初始化的时候,黑洞内部也不应该有星星才对,因此需要对星星的初始坐标进行规避黑洞范围的计算,完整的代码如下:
// 生成星星
for (let i = 0; i < StarNum; i++) {
const newStar = star.cloneNode(true); // 从星星原型拷贝新节点作为星星
const randomShining = `shining ${
0.5 + Math.random() * 0.7
}s linear infinite`;
// 随机生成星星的坐标
let left = Math.random() * document.body.offsetWidth;
let top = Math.random() * document.body.offsetHeight;
// 计算当前星星与Hole的距离:√((HoleX - StarX)^2 + (HoleY - StarY)^2)
const holeDist = Math.abs(
Math.sqrt(Math.pow(InitHoleX - left, 2) + Math.pow(InitHoleY - top, 2))
);
// 星星与Hole重叠,则根据将生成的坐标进行计算定位于Hole外
if (holeDist < HoleR) {
// 计算公式:
// NewX = (HoleR / Dist) * HoleX + (StarX - HoleX)
// NewY = (HoleR / Dist) * HoleY + (StarY - HoleY)
const ratio = HoleR / holeDist;
const leftDist = left - InitHoleX;
const topDist = top - InitHoleY;
left = InitHoleX + leftDist * ratio;
top = InitHoleY + topDist * ratio;
}
// 利用translate移动,因为transform比单纯的left和right性能更佳
newStar.style.transform = `translate(${
left + (left < InitHoleX ? -1 : 1) * otherXDist
}px, ${top + (top < InitHoleY ? -1 : 1) * otherYDist}px)`;
newStar.style.display = "block";
const starCore = newStar.getElementsByClassName("star")[0];
starCore.style.animation = randomShining;
starArr.push(newStar);
document.body.appendChild(newStar);
}
// 移除星星原型
star.remove();
黑洞的移动
在这里,我们用移动鼠标的方式来控制黑洞,也就是黑洞位置跟随鼠标了,当然如果各位喜欢,也可以换成上下左右,至于坐标的变更也是要稍微改动一下的。
以下是跟随鼠标的代码:
// 移动Hole(*跟随鼠标)
const movingHole = (e) => {
const HoleX = e.x;
const HoleY = e.y;
const hole = document.getElementsByClassName("holeBox")[0];
const HoleDistX = HoleX - HoleR;
const HoleDistY = HoleY - HoleR;
hole.style.transform = `translate(${HoleDistX}px, ${HoleDistY}px)`;
// 弹射星星
reboundStar(HoleX, HoleY);
};
// 开始监听鼠标移动坐标
document.body.addEventListener("mousemove", movingHole);
弹射星星
弹射星星,需要结合黑洞的坐标和半径等距离计算,实际上,和批量生成星星的计算方法大同小异,基本就是额外添加了边界值判断、一些样式变更等,也不难理解。
首先我们先定义星星移动的函数:
// 星星移动函数
const moveStar = (star, MX, MY) => {
let HoleX = MX;
let HoleY = MY;
// 没有传入MX和MY时,直接获取Hole的Dom来获取坐标
if (!MX || !MY) {
const hole = document.getElementsByClassName("holeBox")[0];
const holeTransform = window
.getComputedStyle(hole)
.transform.replace("matrix(", "")
.replace(")", "")
.split(",");
HoleX = parseInt(holeTransform.at(-2).trim());
HoleY = parseInt(holeTransform.at(-1).trim());
}
const randomRun = Math.random() * 30; // 额外的随机移动距离,提高随机性(可有可无
const totalOtherXDist = otherXDist + randomRun;
const totalOtherYDist = otherYDist + randomRun;
// 获取Star的坐标
const starTransform = window
.getComputedStyle(star)
.transform.replace("matrix(", "")
.replace(")", "")
.split(",");
let left = parseInt(starTransform.at(-2).trim());
let top = parseInt(starTransform.at(-1).trim());
// 计算当前星星与Hole的距离:√((HoleX - StarX)^2 + (HoleY - StarY)^2)
const holeDist = Math.abs(
Math.sqrt(Math.pow(HoleX - left, 2) + Math.pow(HoleY - top, 2))
);
// 星星与Hole重叠,则根据将生成的坐标进行计算定位于Hole外
if (holeDist < HoleR) {
// 计算公式:
// NewX = (HoleR / Dist) * HoleX + (StarX - HoleX)
// NewY = (HoleR / Dist) * HoleY + (StarY - HoleY)
const ratio = HoleR / holeDist;
const leftDist = left - HoleX;
const topDist = top - HoleY;
left = HoleX + leftDist * ratio;
top = HoleY + topDist * ratio;
let moveX = left;
let moveY = top;
// 边界判断,超出时,往反方向移动
if (moveX < 0) moveX = moveX + HoleD;
if (moveX > document.body.offsetWidth) moveX = moveX - HoleD;
if (moveY < 0) moveY = HoleY + HoleD;
if (moveY > document.body.offsetHeight) moveY = HoleY - HoleD;
const starCore = star.getElementsByClassName("star")[0];
starCore.style.backgroundColor = "#f00"; // 星星被Hole移动时的变色效果
starCore.style.transform = "scale(1.2)"; // 星星被Hole移动时的放大效果
// 利用translate移动,因为transform比单纯的left和right性能更佳
star.style.transform = `translate(${
moveX + (left < HoleX ? -1 : 1) * totalOtherXDist
}px, ${moveY + (top < HoleY ? -1 : 1) * totalOtherYDist}px)`;
setTimeout(() => {
// 动画结束后,恢复原始颜色和尺寸
starCore.style.backgroundColor = "#fff";
starCore.style.transform = "scale(0.6)";
}, 500);
}
};
需要注意的是,我们对黑洞和星星的坐标获取,并不能用常用的offsetLeft和offsetTop等进行获取,要记得我们用的是transform:translate(X,Y)进行定位的,因此这里比较特殊,用的是window.getComputedStyle函数获取transform的matrix值。
弹射星星的函数代码:
// 弹射星星
const reboundStar = (MX, MY) => {
let HoleX = MX;
let HoleY = MY;
// 没有传入MX和MY时,直接获取Hole的Dom来获取坐标
if (!MX || !MY) {
const hole = document.getElementsByClassName("holeBox")[0];
const holeTransform = window
.getComputedStyle(hole)
.transform.replace("matrix(", "")
.replace(")", "")
.split(",");
HoleX = parseInt(holeTransform.at(-2).trim());
HoleY = parseInt(holeTransform.at(-1).trim());
}
// 对每个星星进行判断并进行移动
starArr.forEach((star) => {
moveStar(star, HoleX, HoleY);
});
};
性能优化
很好,大家能看到这里,相信已经完成了所有代码,接下来再调一下CSS的动画性能。
其实也很简单,归根结底就是两条代码:
// 强制开启硬件渲染
transform-style: preserve-3d;
// 放在有动画的css样式内,输入动画涉及的属性即可,多个属性以','间隔
will-change: xxx;
不过最终结果虽然稍微好了点,但貌似还是有点卡,大概或许是我的技术还不到位,各位大佬要是有时间留言帮忙多多提点建议呗~
总结
最后,不知道大家理解得怎么样了呢?不过只是个小玩意儿,在各位大佬面前班门弄斧实在是献丑了。不过我无论是看文章还是发文章,不求装逼,不求夸奖,都是抱着纯纯的学习心态,有错误大佬们多多包涵!
感谢各位看官赏脸点了进来,好了,下次再见!
(转载请注明来源)