用canvas画一轮明月,夜空与流星

今天是中秋节,因而突发奇想,欸不如用canvas来画一画玉轮吧。

因而一副用canvas画出的星空就这样诞生了。

Demo

在这里我用了ES6语法,星星,玉轮和流星都零丁写成了一个module。

因而我把js一共分红这四个文件:main.js, Moon.js, Stars.js和Meteor.js,背面三个各自export出一个类。

源码

为了轻易,用了gulp做自动化的东西。

main.js

import Stars    from './Stars'
import Moon     from './Moon'
import Meteor   from './Meteor'

let canvas = document.getElementById('canvas'),
    ctx = canvas.getContext('2d'),
    width = window.innerWidth,
    height = window.innerHeight,
    //实例化玉轮和星星。流星是随机时候天生,所以只初始化数组
    moon = new Moon(ctx, width, height),
    stars = new Stars(ctx, width, height, 200),
    meteors = [],
    count = 0

canvas.width = width
canvas.height = height

//流星天生函数
const meteorGenerator = ()=> {
    //x位置偏移,以避免经由玉轮
    let x = Math.random() * width + 800
    meteors.push(new Meteor(ctx, x, height))

    //每隔随机时候,天生新流星
    setTimeout(()=> {
        meteorGenerator()
    }, Math.random() * 2000)
}

//每一帧动画天生函数
const frame = ()=> {
    //每隔10帧星星闪灼一次,节约盘算资本
    count++
    count % 10 == 0 && stars.blink()

    moon.draw()
    stars.draw()

    meteors.forEach((meteor, index, arr)=> {
        //假如流星脱离视野以内,烧毁流星实例,接纳内存
        if (meteor.flow()) {
            meteor.draw()
        } else {
            arr.splice(index, 1)
        }
    })
    requestAnimationFrame(frame)
}

meteorGenerator()
frame()

开首离别引入了别的三个module,离别是星星,玉轮和流星。

接着初始化了玉轮和星星,但由于流星是不定时随机天生的,所以初始化一个数组用来保留接下来天生的流星。

在每一帧中,离别挪用moon,star和meteor的draw函数,用来画出每一帧,迥殊的,由于星星须要闪灼,流星须要挪动,所以在draw之前对半径和坐标举行处置惩罚。假如流星跑出了canvas外,就从数组中消灭响应的流星,从而消除援用和接纳内存。

Moon.js

export default class Moon {
    constructor(ctx, width, height) {
        this.ctx = ctx
        this.width = width
        this.height = height
    }

    draw() {
        let ctx = this.ctx,
            gradient = ctx.createRadialGradient(
            200, 200, 80, 200, 200, 800)
        //径向渐变
        gradient.addColorStop(0, 'rgb(255,255,255)')
        gradient.addColorStop(0.01, 'rgb(70,70,80)')
        gradient.addColorStop(0.2, 'rgb(40,40,50)')
        gradient.addColorStop(0.4, 'rgb(20,20,30)')
        gradient.addColorStop(1, 'rgb(0,0,10)')
        ctx.save()
        ctx.fillStyle = gradient
        ctx.fillRect(0, 0, this.width, this.height)
        ctx.restore()
    }
}

这是玉轮的类,重要用到了canvas里的径向渐变结果。为了到达调和的水平,我试了良久T_T…

Stars.js

export default class Stars {
    constructor(ctx, width, height, amount) {
        this.ctx = ctx
        this.width = width
        this.height = height
        this.stars = this.getStars(amount)
    }

    getStars(amount) {
        let stars = []
        while (amount--) {
            stars.push({
                x: Math.random() * this.width,
                y: Math.random() * this.height,
                r: Math.random() + 0.2
            })
        }
        return stars
    }

    draw() {
        let ctx = this.ctx
        ctx.save()
        ctx.fillStyle = 'white'
        this.stars.forEach(star=> {
            ctx.beginPath()
            ctx.arc(star.x, star.y, star.r, 0, 2 * Math.PI)
            ctx.fill()
        })
        ctx.restore()
    }

    //闪灼,星星半径每隔10帧随机变大或变小
    blink() {
        this.stars = this.stars.map(star=> {
            let sign = Math.random() > 0.5 ? 1 : -1
            star.r += sign * 0.2
            if (star.r < 0) {
                star.r = -star.r
            } else if (star.r > 1) {
                star.r -= 0.2
            }
            return star
        })

    }
}

星星的鸠合。由于不至于给每个星星都写成零丁的对象,因而就写了一个星星的鸠合类,一切的星星都保留在实例的stars中。个中的blink函数用来随机转变每个星星的半径大小,从而发生闪灼的结果。

Meteor.js

export default class Meteor {
    constructor(ctx, x, h) {
        this.ctx = ctx
        this.x = x
        this.y = 0
        this.h = h
        this.vx = -(4 + Math.random() * 4)
        this.vy = -this.vx
        this.len = Math.random() * 300 + 500
    }

    flow() {
        //剖断流星出界
        if (this.x < -this.len || this.y > this.h + this.len) {
            return false
        }
        this.x += this.vx
        this.y += this.vy
        return true
    }

    draw() {
        let ctx = this.ctx,
            //径向渐变,从流星头尾圆心,半径越大,透明度越高
            gra = ctx.createRadialGradient(
                this.x, this.y, 0, this.x, this.y, this.len)

        const PI = Math.PI
        gra.addColorStop(0, 'rgba(255,255,255,1)')
        gra.addColorStop(1, 'rgba(0,0,0,0)')
        ctx.save()
        ctx.fillStyle = gra
        ctx.beginPath()
        //流星头,二分之一圆
        ctx.arc(this.x, this.y, 1, PI / 4, 5 * PI / 4)
        //绘制流星尾,三角形
        ctx.lineTo(this.x + this.len, this.y - this.len)
        ctx.closePath()
        ctx.fill()
        ctx.restore()
    }
}

流星就比较有意思啦。猜猜每个流星是怎样画的?

实际上每个流星的表面由一个半圆和一个三角形构成,类似于一个不倒翁。然后团体倾角45度,而且添补时用上一个径向渐变,就能够相称圆满的到达盛行尾巴那样渐行渐远渐隐约的模样。

对,就是这么干净利落~

末了看了一下CPU和GPU的占用,还好,优化的还比较到位,我那渣族手机都能跑的很流通…

今天是中秋节,惋惜我这下雨了…没玉轮可看…

不过我有了这个玉轮。

“但愿人长久,千里共婵娟”,千里以外的朋侪,看到统一轮“明月”,也是缘分吧~

    原文作者:ciqulover
    原文地址: https://segmentfault.com/a/1190000006913317
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞