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

今天是中秋节,于是突发奇想,欸不如用canvas来画一画月亮吧。

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

Demo

在这里我用了ES6语法,星星,月亮和流星都单独写成了一个module。
所以js一共分成这四个文件:

files
源码

为了方便,用了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的占用,还好,优化的还比较到位,我那渣族手机都能跑的很流畅...
今天是中秋节,可惜我这下雨了...没月亮可看...
不过我有了这个月亮。
“但愿人长久,千里共婵娟”,千里之外的朋友,看到同一轮“明月”,也是缘分吧~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容

  • 一.Canvas的基本介绍 1.什么是Canvas 定义:是HTML5提供的一种新标签, ie9才开始支持的,Ca...
    LiLi原上草阅读 698评论 0 3
  • 一:canvas简介 1.1什么是canvas? ①:canvas是HTML5提供的一种新标签 ②:HTML5 ...
    GreenHand1阅读 4,679评论 2 32
  • 一、canvas简介 1.1 什么是canvas?(了解) 是HTML5提供的一种新标签 Canvas是一个矩形区...
    Looog阅读 3,941评论 3 40
  • 广义的Web全栈,一般包含后端开发、前端开发、界面设计、产品设计、数据库、各种移动客户端开发等等,狭义的We...
    阿狸不歌阅读 729评论 5 4
  • 2016年8月的某一天,我在朋友圈看到一篇文章,具体是什么已经记不清了,但是从此我知道了一个APP-简书,首先...
    清晓远阅读 342评论 0 4