流星是一種唯美的天文現象,我一度想用所學知識將它繪製,最近閱讀MDN上的canvas教程得到啟發,用一個canvas的長尾效果繪製流星……
什麼是長尾效果?
我們知道,canvas動畫實現依賴於畫布的重繪,通過不停的清空畫布,繪製畫布就能實現基本的動畫效果。一般使用clearRect方法清除指定矩形區域,來實現重繪。長尾效果是使用透明的填充色代替clearRect方法來實現的。
使用clearRect
吐槽:由於錄屏軟體fps跟不上canvas所以這個gif圖有點卡頓
使用fillRect
1.透明度為1
可以看出透明度為1時,效果與清除效果一致,我們可以理解為在畫布上又蓋上了一畫布 ,遮住了之前所畫的內容所以和清除效果是一樣的。2.透明度為0 透明度為0的效果,則等價於沒有清除畫布的效果,此時運動的球體像一隻尾巴不斷變長的蛇,可以以此猜想,將透明度設為0~1之間,就能調整尾巴的長度,達到一個帶尾巴的運動模糊效果。
3.長尾效果 因為不斷蓋上透明的畫布,所以從繪製點到出發點就產生了一條顏色不斷變淺的路徑,給人視覺效果就是一個動態模糊。
流星
可以將流星解構為一個圓形和長尾效果:
肉眼效果:
實際效果:封裝頁面形狀
使用物件導向程式設計完成canvas繪製
月亮類
class Moon {
constructor(x, y, ctx, r = 25) {
this.x = x;
this.y = y;
this.ctx = ctx;
this.r = r;
}
draw() {
this.ctx.fillStyle = 'rgba(255,255,255,0.6)';
this.ctx.shadowBlur = this.r + 5; //光暈半徑
this.ctx.shadowColor = "#fff"; // 光暈顏色
this.ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
this.ctx.fill();
}
}
複製程式碼
因為月亮是靜止在頁面上的,所以只有一個draw方法,月亮光暈的實現是把陰影和填充設為同一顏色,然後讓陰影透明度大於填充透明度,就形成一個外發光的效果。
星星類
class Star extends Moon {
constructor(x, y, ctx, r) {
super(x, y, ctx, r);
}
draw() {
this.ctx.fillStyle = 'rgba(255,255,255,0.8)';
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
this.ctx.closePath();
this.ctx.fill();
}
move() {
this.x += 0.08;
if (this.x > meteorCanvas.width) {
this.x = 0;
}
this.draw();
}
}
複製程式碼
星星與月亮的唯一區別是可以移動,所以用星星類去繼承月亮類,實現物件導向的繼承與多型。
用星星緩慢的向右移動可以模擬地球自轉帶來的效果。
流星類
class Meteor extends Star {
constructor(x, y, ctx, r,angle) {
super(x, y, ctx, r);
this.angle = angle;
}
draw() {
this.ctx.fillStyle = '#ffffff';
this.ctx.rotate(this.angle);
this.ctx.translate(100, -meteorCanvas.height / 1.5);
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
this.ctx.closePath();
this.ctx.fill();
this.ctx.translate(-100, meteorCanvas.height / 1.5);
this.ctx.rotate(-this.angle);
}
move() {
this.x += 4;
this.y += 1;
if (this.x > meteorCanvas.width) {
this.x = Math.random() * 5
this.y = -2 * meteorCanvas.height + Math.random() * meteorCanvas.height * 3;
}
this.draw();
}
}
複製程式碼
同理用流星類去繼承星星類。
注意的是,流星類與星星類運動的不同之處是流星劃過天空有一個夾角,所以在繪製時將畫布旋轉了角度之後,需要回歸原位。
為了讓流星出現的位置不會太密集,我將流星在y軸出現的位置設定在-2倍畫布高度到1倍畫布高度之間,並在draw方法中將畫布往上挪了畫布高度的2/3(同理要將畫布歸位)。
繪製canvas
流星需要使用長尾效果渲染,星星需要clearRect重繪,月亮就只需要繪製一次。為了三種形狀互不干擾,我分別使用了不同畫布去渲染它們。
優點:互不干擾,繪製邏輯清晰,優化渲染。
原始碼
const meteorCanvas = document.getElementById('meteor');
const starCanvas = document.getElementById('star');
const moonCanvas = document.getElementById('moon');
const meteors = [], stars = [];
meteorCanvas.width = document.body.clientWidth;
meteorCanvas.height = document.body.clientHeight;
starCanvas.width = document.body.clientWidth;
starCanvas.height = document.body.clientHeight / 3;
moonCanvas.width = document.body.clientWidth;
moonCanvas.height = document.body.clientHeight / 3;
const meteorCtx = meteorCanvas.getContext('2d');
const starCtx = starCanvas.getContext('2d');
const moonCtx = moonCanvas.getContext('2d');
init();
animate();
function init() {
for (var i = 0; i < 4; i++) {
meteors[i] = new Meteor(Math.random() * meteorCanvas.width,
-2 * meteorCanvas.height + Math.random() * meteorCanvas.height * 3,
meteorCtx, Math.floor(Math.random() * 2) + 1.5, Math.PI / 7);
meteors[i].draw();
}
for (var i = 0; i < 60; i++) {
stars[i] = new Star(Math.random() * starCanvas.width, Math.random() * starCanvas.height,
starCtx, Math.random());
stars[i].draw();
}
moon = new Moon(moonCanvas.width - 50, 50, moonCtx)
moon.draw();
}
function animate() {
starCtx.clearRect(0, 0, starCanvas.width, starCanvas.height);
meteorCtx.fillStyle = `rgba(0, 0, 0, 0.1)`;
meteorCtx.fillRect(0, 0, meteorCanvas.width, meteorCanvas.height);
for (let meteor of meteors)
meteor.move();
for (let star of stars)
star.move();
requestAnimationFrame(animate);
}
function recover() {
for (let meteor of meteors)
meteor = null;
for (let star of stars)
star = null;
moon = null;
}
window.onresize = function () {
meteorCanvas.width = document.body.clientWidth;
meteorCanvas.height = document.body.clientHeight;
starCanvas.width = document.body.clientWidth;
starCanvas.height = document.body.clientHeight / 3;
moonCanvas.width = document.body.clientWidth;
moonCanvas.height = document.body.clientHeight / 3;
recover();
init();
}
複製程式碼
結語
陪你去看流星雨落在這地球上
讓你的淚落在我肩膀
要你相信我的愛只肯為你勇敢……
文章隨著《流星雨》的歌聲,也走向了尾聲。人生如流星劃過,轉瞬即逝,然而,流星易逝,真情永恆……