前言
最近看了《万神殿 第一季 Pantheon Season》,开篇画面的一个效果让我产生创作灵感👇
GIF
图中人物碎片化🧩,一点点脱落,似乎有引力将其撕扯开裂
于是,我们实现了下面的效果,并将其命名为归为尘埃
- 寓意人(或者说万物)在宇宙中的渺小存在。
我们主要使用了 canvas
绘图,下面来一步步讲解:
基本骨架
HTML
里面就两行代码:
<canvas id="canvas"></canvas>
<button id="start">Click to Start</button>
嗯~也许你会有疑问:图片跑哪里去了呢?
图片放在了 javascript
文件中处理,我们将图片转化为 base64
字符串了(前端优化的一种方式)。见项目源码
归为尘埃。
色调指定
在特效中,我们使用的图片是路飞,如下:
该图片来源网络,侵删
根据图片的色调,我们指定了 primaryColor
,secondColor
和 accentColor
如下,并对开始按钮样式做了些协调调整:
@primaryColor: #F00; // 主色调
@secondColor: #FDB117; // 次色调
@accentColor: #FDAC77; // 强调色
#start {
cursor: pointer;
position: absolute;
text-align: center;
top: 60px;
left: 50%;
padding: 3px 12px;
transform: translateX(-50%);
font-size: 20px;
font-family: serif;
border: 1px solid transparent;
letter-spacing: 2px;
background: linear-gradient(to bottom right, @primaryColor,@secondColor, @accentColor);
-webkit-background-clip: text;
color: transparent;
border-image: linear-gradient(to top left, @primaryColor, @secondColor, @accentColor) 1;
&:hover {
background: linear-gradient(to top left, @primaryColor, @secondColor, @accentColor);
border-image: linear-gradient(to top left, @accentColor, @secondColor, @primaryColor) 1;
-webkit-background-clip: text;
color: transparent;
}
text-shadow: 1px 1px 2px #000;
}
上面的样式,我们对 id
为 start
的 button
进行设定,将其置顶居中展示。我们怎么选择该 button
的主色调、次色调和强调色呢?这结合了我们找的图片包含的(或者说相近的)色调。关于网站中色调的使用,可以参考我们之前的文章 -- 网站设计:十条需避免的常见错误。
绘制粒子图像
我们通过 javascript
加载 onload
图片之后,再进行处理:
const imgDom = new Image();
// PS:实际代码中,我将图片转换成 base64,当然,你也可以直接使用超链接
imgDom.src = '';
// 监听图片加载完后处理
imgDom.onload = function() {
// 图片加载后,相关操作,比如创建 canvas dom
}
首先,我们收集图像数据,存放在指定的变量中,如 particleArray
中:
function collectData() {
// 这里要重新赋值(清空),避免浏览器窗口更改等造成的粒子记入
particleArray = [];
context.drawImage(imgDom, startX, startY); // 绘制图片
let i = 0;
for(let y = 0; y < canvasDom.height; y += 1) {
for(let x = 0; x < canvasDom.width; x += 1) {
// rgba [i, i + 1, i + 2, i + 3]
i = (y * canvasDom.width + x) * 4;
let r = imgData[i];
let g = imgData[i + 1];
let b = imgData[i + 2];
let color = `rgb(${r}, ${g}, ${b})`;
let a = imgData[i + 3];
if(a > 0) {
particleArray.push({
x,
y,
color,
vx: 0,
vy: 0,
size: size * Math.random()
})
}
}
}
}
通过上面的函数,我们将图片转换成数组数据,每个粒子数据项包含下面的字段:
{
x: x, // 坐标点 x
y: y, // 坐标点 y
color: color, // 粒子颜色
vx: 0, // x 轴的加速度
vy: 0, // y 轴的加速度
size: size * Math.random(), // 粒子的大小
}
需要留意的是 color
这个字段,生成的粒子数组中,从索引 0
开始,每四个数据为一组,每组里面从前往后依次代表该点的红色 Red
、绿色 Green
、蓝色 Blue
和透明度 Alpha
。
然后,我们将粒子数据绘制出来:
function drawPaticleImage() {
for(let i = 0; i < particleArray.length; i += 1) {
let paticle = particleArray[i];
context.fillStyle = paticle.color;
// 绘制粒子
context.fillRect(paticle.x, paticle.y, paticle.size, paticle.size);
}
}
drawPaticleImage
方法根据上面图像的粒子数据,绘制成图像,效果如下:
很不错 👍,值得一赞。那么,接下来,我们可以对图像中的单个粒子进行操作了!
添加引力效果
下面是整个项目中的重点,即添加引力效果。首先,我们得设置触发引力点:
let gravitySource = {
x: canvasDom.width / 2, // 引力坐标 x
y: canvasDom.height / 2, // 引力坐标 y
strength: 100, // 引力强度
}
接着设置引力函数,对粒子产生相应的影响:
function gravityAttractor(x, y, strength) {
return function(paticle) {
let dx = x - paticle.x;
let dy = y - paticle.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let angle = Math.atan2(dy, dx);
// 直线加速度
let acceleration = strength / (distance * distance);
// x 轴加速度
paticle.vx += acceleration * Math.cos(angle);
// y 轴加速度
paticle.vy += acceleration * Math.sin(angle);
}
}
上面代码中,我们获取引力点对粒子的距离 distance
,引力点对粒子的角度 angle
,当前粒子的加速度 acceleration
,当前粒子在 x
轴方向的加速度和当前粒子在 y
轴方向上的加速度。触发 Click to Start
按钮,我们感受下效果:
当然,我们也可以将引力点设置在不同的位置,比如根据鼠标的移动而改变,那将会很酷;或者设置多个引力源,那又会是怎么样子的呢?感兴趣的读者可以尝试实现一下。
源码
我们在码上掘金上完成本效果。项目源码见: 归为尘埃