用canvas绘制流星夜空

·  阅读 3981
用canvas绘制流星夜空

流星是一种唯美的天文现象,我一度想用所学知识将它绘制,最近阅读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();
        }
复制代码

结语

陪你去看流星雨落在这地球上
让你的泪落在我肩膀
要你相信我的爱只肯为你勇敢……

文章随着《流星雨》的歌声,也走向了尾声。人生如流星划过,转瞬即逝,然而,流星易逝,真情永恒……

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改