canvasで明月と夜空と流星を描きます.

5276 ワード

今日は中秋節ですから、奇想天外です.えっと、canvasで月を描きましょう.
canvasで描いた星空がこうして生まれた.
モモ
ここで私はES 6文法を使いました.星と月と流星は一人でmoduleと書きました.
私はjsを全部でこの四つのファイルに分けます.main.js、Moon.js、スター.jsとMeteor.js、後の三つはそれぞれexportから一つの種類を出します.
ソース
便利さのために、gulpを使って自動化の道具を作りました.
mail.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…
スターズ.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の占有を見ましたが、幸いです.最適化されたのはまだ適切です.私のクズ族の携帯は全部流暢に走ることができます.
今日は中秋節です.残念ですが、雨が降っています.月がないので…
でも、この月がありました.
「長い間、美しい人になりたい」と、千里以外の友達が同じ「明月」を見たのも、縁でしょう.