流星是一種惟美的天文現象,我一度想用所學知識將它繪製,最近閱讀MDN上的canvas教程獲得啓發,用一個canvas的長尾效果繪製流星……編程
咱們知道,canvas動畫實現依賴於畫布的重繪,經過不停的清空畫布,繪製畫布就能實現基本的動畫效果。通常使用clearRect方法清除指定矩形區域,來實現重繪。長尾效果是使用透明的填充色代替clearRect方法來實現的。canvas
吐槽:因爲錄屏軟件fps跟不上canvas因此這個gif圖有點卡頓bash
1.透明度爲1dom
能夠看出透明度爲1時,效果與清除效果一致,咱們能夠理解爲在畫布上又蓋上了一畫布 ,遮住了以前所畫的內容因此和清除效果是同樣的。能夠將流星解構爲一個圓形和長尾效果:優化
肉眼效果: 動畫
實際效果:使用面向對象編程完成canvas繪製
月亮類ui
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方法,月亮光暈的實現是把陰影和填充設爲同一顏色,而後讓陰影透明度大於填充透明度,就造成一個外發光的效果。
星星類this
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();
}
}
複製代碼
星星與月亮的惟一區別是能夠移動,因此用星星類去繼承月亮類,實現面向對象的繼承與多態。
用星星緩慢的向右移動能夠模擬地球自轉帶來的效果。
流星類spa
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(同理要將畫布歸位)。3d
流星須要使用長尾效果渲染,星星須要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();
}
複製代碼
陪你去看流星雨落在這地球上
讓你的淚落在我肩膀
要你相信個人愛只肯爲你勇敢……
文章隨着《流星雨》的歌聲,也走向了尾聲。人生如流星劃過,轉瞬即逝,然而,流星易逝,真情永恆……