canvas动画科技园_Canvas 写的酷炫动画代码分析

霓虹灯线形成的自发六边形,随机性生成火花

在看这篇文章时,里面有个动画的示例(如上图),然后感觉有点很酷炫,就打算了解一下怎么写的。( 先上代码示例链接)

前景提要

需要先确保你还记得三角函数的知识。

对 Canvas 的 API 有点了解,且稍微了解其中的 globalCompositeOperation API (不了解的话,可以看下这个文章,基本可以有点感觉)。

代码分析

尽我所能,我尽量在代码里关键地方都增加了注释。(可能有些描述表达不够好,请见谅~)

var w = (c.width = window.innerWidth),

h = (c.height = window.innerHeight),

ctx = c.getContext("2d"),

//一些配置项

opts = {

len: 20, //线长

count: 50, //线总数

baseTime: 10, //线停留基础时间

addedTime: 10, //线额外停留时间

dieChance: 0.05, //线重置的概率

spawnChance: 1, //线生成的概率

sparkChance: 0.1, //火花生成的概率

sparkDist: 10, //火花距离线的距离

sparkSize: 2, //火花大小

color: "hsl(hue,100%,light%)", //hsl() 函数使用色相、饱和度、亮度来定义颜色。

baseLight: 50, //基础的颜色亮度

addedLight: 10, // [50-10,50+10]

shadowToTimePropMult: 6, //阴影的模糊级别

baseLightInputMultiplier: 0.01, //基础亮度

addedLightInputMultiplier: 0.02, //额外亮度

cx: w / 2,

cy: h / 2,

repaintAlpha: 0.04,

hueChange: 0.1,

},

tick = 0, //控制颜色色相

lines = [],

dieX = w / 2 / opts.len,

dieY = h / 2 / opts.len,

baseRad = (Math.PI * 2) / 6;

ctx.fillStyle = "black";

ctx.fillRect(0, 0, w, h);

function loop() {

//浏览器下次重绘前调用该方法

window.requestAnimationFrame(loop);

//循环过程中更改生成的霓虹灯颜色色相

++tick;

/* 目标图像 = 已经放置在画布上的绘图。

源图像 = 打算放置到画布上的绘图。 */

ctx.globalCompositeOperation = "source-over"; //目标图像上显示源图像

ctx.shadowBlur = 0;

ctx.fillStyle = "rgba(0,0,0,alp)".replace("alp", opts.repaintAlpha);

ctx.fillRect(0, 0, w, h);

ctx.globalCompositeOperation = "lighter"; //显示源图像 + 目标图像(重叠图形的颜色是通过颜色值相加来确定)

//保持生成的霓虹灯线共有 count 个

if (lines.length < opts.count && Math.random() < opts.spawnChance)

lines.push(new Line());

lines.map(function (line) {

line.step();

});

}

function Line() {

//生成霓虹灯线时进行初始化

this.reset();

}

//初始化,重置

Line.prototype.reset = function () {

this.x = 0;

this.y = 0;

this.addedX = 0;

this.addedY = 0;

this.rad = 0;

//亮度

this.lightInputMultiplier =

opts.baseLightInputMultiplier +

opts.addedLightInputMultiplier * Math.random();

this.color = opts.color.replace("hue", tick * opts.hueChange);

this.cumulativeTime = 0; //累计的时间

this.beginPhase();

};

//霓虹灯线每一步的开始前规划阶段

Line.prototype.beginPhase = function () {

this.x += this.addedX;

this.y += this.addedY;

this.time = 0;

//霓虹灯线每一步的停留时间

this.targetTime = (opts.baseTime + opts.addedTime * Math.random()) | 0;

//随机六边形路线方向

this.rad += baseRad * (Math.random() < 0.5 ? 1 : -1);

this.addedX = Math.cos(this.rad);

this.addedY = Math.sin(this.rad);

//霓虹灯线消失重置的条件

if (

Math.random() < opts.dieChance ||

this.x > dieX ||

this.x < -dieX ||

this.y > dieY ||

this.y < -dieY

)

this.reset();

};

//行走一步

Line.prototype.step = function () {

++this.time;

++this.cumulativeTime;

//超过行走时间,规划下一步

if (this.time >= this.targetTime) this.beginPhase();

var prop = this.time / this.targetTime,

wave = Math.sin((prop * Math.PI) / 2), //sin90°=1

x = this.addedX * wave, //cos(R)=b/c

y = this.addedY * wave; //sin(R)=a/c

ctx.shadowBlur = prop * opts.shadowToTimePropMult; //阴影的模糊级别

//模糊和填充的颜色

ctx.fillStyle = ctx.shadowColor = this.color.replace(

"light",

opts.baseLight +

opts.addedLight *

Math.sin(this.cumulativeTime * this.lightInputMultiplier)

);

//绘制霓虹灯线

ctx.fillRect(

opts.cx + (this.x + x) * opts.len,

opts.cy + (this.y + y) * opts.len,

2,

2

);

//随机生成火花

if (Math.random() < opts.sparkChance)

ctx.fillRect(

opts.cx +

(this.x + x) * opts.len +

Math.random() * opts.sparkDist * (Math.random() < 0.5 ? 1 : -1) -

opts.sparkSize / 2,

opts.cy +

(this.y + y) * opts.len +

Math.random() * opts.sparkDist * (Math.random() < 0.5 ? 1 : -1) -

opts.sparkSize / 2,

opts.sparkSize,

opts.sparkSize

);

};

loop();

//监听浏览器窗口调整,重置

window.addEventListener("resize", function () {

w = c.width = window.innerWidth;

h = c.height = window.innerHeight;

ctx.fillStyle = "black";

ctx.fillRect(0, 0, w, h);

opts.cx = w / 2;

opts.cy = h / 2;

dieX = w / 2 / opts.len;

dieY = h / 2 / opts.len;

});

简单来描述下,上面的主要代码:

每一次浏览器重绘前都调用 loop() 函数。

在 loop() 函数里,保持共有 count 个实例化的 Line 。

在实例化时,调用 reset() 函数进行一些属性的初始化。

在初始化完成后,调用 beginPhase() 函数进行下一步绘制的路线规划。(其中霓虹灯线触发重置条件时,调用 reset() 函数,进行属性的数值化)

回到 loop() 函数,遍历每一个示例 Line ,调用 step() 函数,进行绘制霓虹灯线和线周围的火花。(其中超过每一步规定的停留时间后,调用 beginPhase() 函数,规划下一步。)

问题

这个动画,让我一开始感觉到厉害的地方是,霓虹灯线行走的尾部,有个渐渐的变暗淡的过程。所以,让人感觉这个动画,就很酷炫。

而这个是怎么做的呢?我上面描述刻意没有讲到。可以看下代码,思考下,思路感觉挺微妙的。(我是重新看了下代码才明白的)

答案

关键点就在于 loop() 函数里的这两行代码。

ctx.fillStyle = "rgba(0,0,0,alp)".replace("alp", opts.repaintAlpha);

ctx.fillRect(0, 0, w, h);

通过每一次的层层叠加上有一定透明度的黑色,从而达到了后面尾巴逐渐消灭的效果。(如果你开始一眼就发现了,打扰了,献丑了)

最后

酷炫的 Canvas 从来没有写过,也没接触过。这次试着分析这个酷炫动画代码,算是对如何用 Canvas 画动画有了点感觉了吧。

另外,虽然看懂了代码,但似乎不是知道 Canvas 怎么画动画就能写出这个效果的,总感觉里面似乎蕴涵了一些数学功底~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值