当青训营遇上码上掘金
主题 2:遇见
据说世界上两个陌生人相遇且相识的概率是 0.0000005,大家来到我们青训营能够遇见许多志同道合的伙伴一同学习、快乐成长,这是件多么美妙的事情。请大家围绕“遇见”这个主题进行代码创作。
💡效果如下图所示:
设计思路
两个人在茫茫人海中相遇和概率据说只有0.0000005。我看到这个题目第一时间就想到了无序额度粒子系统。完全无序的粒子在画布上无序运动,不正像是茫茫人海中的红尘众生吗?再点上两个红绿色的粒子代表陌生的两个人,在这茫茫的人海中随波逐流,相遇的几率确实渺茫至极。
正巧前两天跟着某博主利用canvas绘制了无序粒子,正好发扬拿来主义,稍加修改,便成了这个作品~
代码逻辑
利用canvas画布功能,绘制粒子和统计数据。
- 首先写入
canvas标签
<canvas id="canvas"></canvas>
-
数据初始化部分:初始化粒子数量,粒子大小,粒子颜色等信息
- 设置背景人物:本案例将背景人数设置为100个,可以模拟出较好的人海效果并且节省性能。
//定义背景人海粒子 const particleNum = 100; const colorRGB = "254, 250, 224"; let particles = []; let interacionParticle = null;- 设置陌生两个人:因为要单独设置样式,计算相遇和位置等,因此单独提出来作为变量保存,便于后期读取
//定义陌生的两人 let person = []; const personNumber = 2; const personDistance = 20; const personColor = ["red", "green"]; const personSize = 5; let flag = true; //节流后返回的函数~ let printProbability; //相遇可能性 const date = +new Date(); let collision = 1; let possible = 1;- 定义粒子类:包括位置(
x, y)和正交分速度(velocityX, velocityY),粒子大小和颜色。
//定义粒子类 class Particle { constructor(x, y, velocityX, velocityY, size, color) { this.x = x; this.y = y; this.velocityX = velocityX; this.velocityY = velocityY; this.size = size; this.color = color; } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fillStyle = this.color; ctx.fill(); } update() { if (this.x < 0 || this.x > canvas.width) { this.velocityX *= -1; } if (this.y < 0 || this.y > canvas.height) { this.velocityY *= -1; } this.x += this.velocityX; this.y += this.velocityY; this.draw(); } }- 创建粒子:用
createParticles和createPerson两个函数分别创造背景粒子和陌生人粒子。
//背景粒子 function createParticles() { for (let i = 0; i < particleNum; i++) { let size = getRandomArbitrary(2, 5); let x = Math.random() * canvas.width; let y = Math.random() * canvas.height; let velocityX = getRandomArbitrary(-2, 2); let velocityY = getRandomArbitrary(-2, 2); let color = `rgba(${colorRGB},${1 - size / 5})`; particles.push( new Particle(x, y, velocityX, velocityY, size, color) ); } } //创造相遇的陌生人粒子 function createPerson() { for (let i = 0; i < personNumber; i++) { let x = Math.random() * canvas.width; let y = Math.random() * canvas.height; let velocityX = getRandomArbitrary(-2, 2); let velocityY = getRandomArbitrary(-2, 2); person.push( new Particle( x, y, velocityX, velocityY, personSize, personColor[i] ) ); } }- 计算相遇可能性:利用初始化中的时间戳和每次更新动画时间戳对比,计算经过时间(在本案例中设置1秒为1天);然后通过相遇次数和时间相除得到相遇概率。
PS:为了计算方便,将初始的相遇次数置为1,便于看到概率随时间下降。
function probability() { let distance = Math.sqrt( Math.pow(person[0].x - person[1].x, 2) + Math.pow(person[0].y - person[1].y, 2) ); possible = collision / ((+new Date() - date) / 1000); console.log(possible); }- 定义动画:利用
requestAnimationFrameAPI,申请动画帧。
//定义动画 function animate() { requestAnimationFrame(animate); ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach((item) => { item.update(); }); person.forEach((item) => { item.update(); }); printProbability(); connect(); ctx.beginPath(); ctx.fillStyle = `rgba(${colorRGB},0.5)`; ctx.fillRect(0, 0, 300, 180); ctx.beginPath(); ctx.font = "26px sanserif"; if (possible < 0.0000005) { ctx.fillStyle = "red"; } else { ctx.fillStyle = "#fff"; } ctx.fillText(`经过时间:${((+new Date() - date) / 1000).toFixed(0)} 天`, 30, 50); ctx.fillText(`相遇次数:${collision - 1} 次`, 30, 100); ctx.fillText(`相遇概率:${possible.toFixed(7)}`, 30, 150); }
效果展示
可能由于页面大小原因显示不完整,具体可以查看这个链接遇见