开始
妹子都喜欢流星,如果她说不喜欢,那她一定是一个假妹子。
现在就一起来做一场流星雨,用程序员的野路子浪漫一下。
要画一场流星雨,首先,自然我们要会画一颗流星。
玩过 canvas 的同学,你画圆画方画线条这么 6,如果说叫你画下面这个玩意儿,你会不会觉得你用的是假 canvas?canvas 没有画一个带尾巴玩意儿的 api 啊。
画一颗流星
是的,的却是没这个 api,但是不代表我们画不出来。流星就是一个小石头,然后因为速度过快产生大量的热量带动周围的空气发光发热,所以经飞过的地方看起来就像是流星的尾巴,我们先研究一下流星这个图像,整个流星处于他自己的运动轨迹之中,当前的位置最亮,轮廓最清晰,而之前划过的地方离当前位置轨迹距离越远就越暗淡越模糊。
上面的分析结果很关键, canvas 上是每一帧就重绘一次,每一帧之间的时间间隔很短。流星经过的地方会越来越模糊最后消失不见,那有没有可以让画布画的图像每过一帧就变模糊一点而不是全部清除的办法?如果可以这样,就可以把每一帧用线段画一小段流星的运动轨迹,最后画出流星的效果。
骗纸!你也许会说,这那里像流星了???
别急,让我多画几段给你看看。
什么? 还是不像? 我们把它画小点,这下总该像了把?
上面几幅图我是在 ps 上模拟的,本质上 ps 也是在画布上绘画,我们马上在 canvas 上试试。
那,直接代码实现一下。
// 坐标
class Crood {
constructor(x=0, y=0) {
this.x = x;
this.y = y;
}
setCrood(x, y) {
this.x = x;
this.y = y;
}
copy() {
return new Crood(this.x, this.y);
}
}
// 流星
class ShootingStar {
constructor(init=new Crood, final=new Crood, size=3, speed=200, onDistory=null) {
this.init = init; // 初始位置
this.final = final; // 最终位置
this.size = size; // 大小
this.speed = speed; // 速度:像素/s
// 飞行总时间
this.dur = Math.sqrt(Math.pow(this.final.x-this.init.x, 2) + Math.pow(this.final.y-this.init.y, 2)) *
1000 / this.speed;
this.pass = 0; // 已过去的时间
this.prev = this.init.copy(); // 上一帧位置
this.now = this.init.copy(); // 当前位置
this.onDistory = onDistory;
}
draw(ctx, delta) {
this.pass += delta;
this.pass = Math.min(
this.pass, this.dur);
let percent = this.pass / this.dur;
this.now.setCrood(
this.init.x + (this.final.x - this.init.x) * percent,
this.init.y + (this.final.y - this.init.y) * percent
);
// canvas
ctx.strokeStyle = '#fff';
ctx.lineCap = 'round';
ctx.lineWidth = this.size;
ctx.beginPath();
ctx.moveTo(this.now.x, this.now.y);
ctx.lineTo(
this.prev.x, this.prev.y);
ctx.stroke();
this.prev.setCrood(this.now.x, this.now.y);
if (this.pass === this.dur) {
this.distory();
}
}
distory() {
this.onDistory && this.onDistory();
}
}
// effet
let cvs = document.querySelector('canvas');
let ctx = cvs.getContext('2d');
let T;
let shootingStar = new ShootingStar(
new Crood(100, 100),
new
Crood(400, 400),
3,
200,
()=>{cancelAnimationFrame(T)}
);
let tick = (function() {
let now = (new Date()).getTime();
let last = now;
let delta;
return function() {
delta = now - last;
delta = delta > 500 ? 30 : (delta < 16? 16 : delta);
last = now;
// console.log(delta);
T = requestAnimationFrame(tick);
ctx.save();
ctx.fillStyle = 'rgba(0,0,0,0.2)'; // 每一帧用 “半透明” 的背景色清除画布
ctx.fillRect(0, 0, cvs.width, cvs.height);
ctx.restore();
shootingStar.draw(ctx, delta);
}
})();
tick();
效果:一颗流星
sogoyi 快看,一颗活泼不做作的流星!!! 是不是感觉动起来更加逼真一点?
流星雨
我们再加一个流星雨 MeteorShower 类,生成多一些随机位置的流星,做出流星雨。
// 坐标
class Crood {
constructor(x=
0, y=0) {
this.x = x;
this.y = y;
}
setCrood(x, y) {
this.x = x;
this.y = y;
}
copy() {
return new Crood(this.x, this.y);
}
}
// 流星
class ShootingStar {
constructor(init=
new Crood, final=new Crood, size=3, speed=200, onDistory=null) {
this.init = init; // 初始位置
this.final = final; // 最终位置
this.size = size; // 大小
this.speed = speed; // 速度:像素/s
// 飞行总时间
this.dur = Math.sqrt(Math.pow(this.final.x-this.init.x, 2) + Math.pow(this.final.y-this.init.y, 2)) * 1000 / this.speed;
this.pass = 0; // 已过去的时间
this.prev = this.init.copy(); // 上一帧位置
this.now = this.init.copy(); // 当前位置
this.onDistory = onDistory;
}
draw(ctx, delta) {
this.pass += delta;
this.pass = Math.min(this.pass, this.dur);
let percent =
this.pass / this.dur;
this.now.setCrood(
this.init.x + (this.final.x - this.init.x) * percent,
this.init.y + (this.final.y - this.init.y) * percent
);
// canvas
ctx.strokeStyle =
'#fff';
ctx.lineCap = 'round';
ctx.lineWidth = this.size;
ctx.beginPath();
ctx.moveTo(this.now.x, this.now.y);
ctx.lineTo(
this.prev.x, this.prev.y);
ctx.stroke();
this.prev.setCrood(this.now.x, this.now.y);
if (this.pass === this.dur) {
this
.distory();
}
}
distory() {
this.onDistory && this.onDistory();
}
}
class
MeteorShower {
constructor(cvs, ctx) {
this.cvs = cvs;
this.ctx = ctx;
this.stars = [];
this.T;
this.stop = false;
this.playing = false;
}
createStar() {
let angle = Math.PI / 3;
let distance =
Math.random() * 400;
let init = new Crood(Math.random() * this.cvs.width|0, Math.random() * 100|0);
let final = new
Crood(init.x + distance * Math.cos(angle), init.y + distance * Math.sin(angle));
let size = Math.random() * 2;
let speed = Math.random() * 400 + 100;
let star =
new ShootingStar(
init, final, size, speed,
()=>{
this.remove(star)}
);
return star;
}
remove(star) {
this.stars = this.stars.filter((s)=>{ return s !== star});
}
update(delta) {
if (!this.stop && this