用canvas繪製流星夜空

流星是一種惟美的天文現象,我一度想用所學知識將它繪製,最近閱讀MDN上的canvas教程獲得啓發,用一個canvas的長尾效果繪製流星……編程

什麼是長尾效果?

咱們知道,canvas動畫實現依賴於畫布的重繪,經過不停的清空畫布,繪製畫布就能實現基本的動畫效果。通常使用clearRect方法清除指定矩形區域,來實現重繪。長尾效果是使用透明的填充色代替clearRect方法來實現的。canvas

使用clearRect

吐槽:因爲錄屏軟件fps跟不上canvas因此這個gif圖有點卡頓bash

使用fillRect

1.透明度爲1dom

能夠看出透明度爲1時,效果與清除效果一致,咱們能夠理解爲在畫布上又蓋上了一畫布 ,遮住了以前所畫的內容因此和清除效果是同樣的。
2.透明度爲0
透明度爲0的效果,則等價於沒有清除畫布的效果,此時運動的球體像一隻尾巴不斷變長的蛇,能夠以此猜測,將透明度設爲0~1之間,就能調整尾巴的長度,達到一個帶尾巴的運動模糊效果。
3.長尾效果
由於不斷蓋上透明的畫布,因此從繪製點到出發點就產生了一條顏色不斷變淺的路徑,給人視覺效果就是一個動態模糊。

流星

能夠將流星解構爲一個圓形和長尾效果:優化

肉眼效果: 動畫

實際效果:

封裝頁面形狀

使用面向對象編程完成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

繪製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();
        }
複製代碼

結語

陪你去看流星雨落在這地球上
讓你的淚落在我肩膀
要你相信個人愛只肯爲你勇敢……

文章隨着《流星雨》的歌聲,也走向了尾聲。人生如流星劃過,轉瞬即逝,然而,流星易逝,真情永恆……

相關文章
相關標籤/搜索