中秋-canvas-唯美星空

2023-11-14

明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。
转朱阁,低绮户,照无眠。不应有恨,何事长向别时圆?人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟。
值此中秋佳节来临之际,只做了一个星空背景,在此做个分享。效果图如下:
PC端效果:
在这里插入图片描述
移动端效果:
在这里插入图片描述
技能点
html 及 canvasApi用来操作canvas进行作画。
es6 中 class 关键字将关键的块进行简单封装提高复用性。

需求分析
最终效果图如上面的图片所示:
前景为山、月亮以及文字,中景为星空,背景为渐变色作为天空。
星星至右向左移动,并且有一闪一闪的效果增加用户体验。

**PS:**这里的文字本该加载个其他字体,或者初次加载时应该是一个字一个字的浮现出来。一个是因为懒,另一个是因为UI水平实在差,觉得这样也挺好看。就懒得弄了,有兴趣可以自行优化。[手动憨笑]
思路分析
需求并不麻烦,接触canvas一段时间就可以完成它。因为是demo性质的作品,所以尽可能的会使用一些花里胡哨的东西,常言道“此时不装,更待何时?”
其实也没有多花里胡哨啦!
首先我在默认的html里面呢,使用module的方式,引入es6规范的包,使用new关键字直接画出整个项目,这里主要是考虑在 webpack 的环境下,可以把项目进行微调,使用webpack进行打包成一个独立的js包进行分享。
然后对于需求中提到的几个核心元素:canvas、moon(月亮)、star(星星)以及整个项目 moon-night 都独立封装成 class ,提高代码的复用性,而且都可以单独提出去到其他地方直接使用,当然这是后话了。
最后对所有的类型class 做了一个简单的约束,每个 class 都有各一个 init 、draw、update 的私有方法,分别对应 初始化、绘画、更新三个状态,或者被我吹做三个生命周期函数,对标 vue 的声明周期函数。 [嚣张]
综上所述是整个项目的思路了,下面是代码分析和实现过程。
实现
0. 项目结构
shell复制代码moon-night
|–index.html
|–index.js
|–canvas.class.js
|–star.class.js
|–moon.class.js

1. index.html
index.html 中很简单,几乎声明都不用做,只是把 canvas 默认的 margin 和 padding 给清掉,不然会有个白边。
可以看到 script 中的 type = “module” ,在这对script标签里面,可以使用 import 导入es6语法导出的包。
html复制代码

温馨夜色

– 上面这个页面,是用 vscode 一键生成的,里面的 viewport 配置和http-equiv有没有对这个项目没有关系。

2. canvas.class.js
canvas.class.js,看到我的命名,应该知道这是个什么样的文件了。
对,就是你想的那样。
声明了一个 class ,名字为Canvas。Canvas这个类可以作为以后所有相关canvas项目的基础类,内部声明一下canvas和canvas.getContext(‘2d’),用以后续真实的绘图类中继承,保证所有的子类都在一个 canvas 上进行操作。
同时,绑定了一个监听函数,用来处理浏览器窗口改变后重新初始化canvas的宽、高。这也是在canvas中经常使用的一个方法。
代码很简单,如下:
js复制代码export default class Canvas {
// 接收 两个 参数,id 和 config
// 目前 config 只接收 一个属性 screen ,用来控制是否跟随窗口变化
constructor(id, config) {

if (id !== 0 && !id) {
  throw new Error('id 不能为空!')
}

this.cv = document.getElementById(id)
this.ctx = this.cv.getContext('2d')

this.config = Object.assign({
  isScreen: true
}, config)

this.initSize()

}

initSize() {
this.cv.style.display = “block”
// 读传入的isScreen的值,根据值判断是否挂载 resize 方法
if (this.config.isScreen) {
this.cv.width = window.innerWidth
this.cv.height = window.innerHeight
window.addEventListener(“resize”, this.resizeCanvas.bind(this), false)
} else {
this.cv.width = this.cv.parentNode.offsetWidth
this.cv.height = this.cv.parentNode.offsetHeight
}

}

resizeCanvas() {
// 重新声明canvas的宽高
this.cv.width = window.innerWidth
this.cv.height = window.innerHeight
// 子类中的 draw 方法
this.draw()
}

}

关键聊一下resizeCanvas中的 this.draw() 方法,这里的this的指向在canvas被继承之后会发生变化,即和子类中的this指向一致。所以,init方法被写为initSize,避免在继承后init被重写。

3. index.js (moonNight.class.js)
index.js 其实也就是 moonNight.class.js。结合上面 canvas 文件,这个文件最后导出一个默认的类:MoonNight。
MoonNight 继承自 Canvas ,所以可以直接使用 cv 和 ctx,而且项目中所有的项目都是在这个类中实现的。
MoonNight 除了一开始说的三个基础方法之外,还声明了两个方法,画文字、画天空、画山。而且方法都在draw中执行。
这里有一个小知识点,就是在canvas中,后面画的东西,在canvas中重叠的时候,后面画的会自动覆盖在前面画的元素中,同理和 css 的 z-index 属性相似。所以要注意在 draw 中,各个方法的执行顺序,应该是结合效果图,从最下面往上画。
js复制代码"use script"

// 引入 各个包
import Canvas from “./canvas.class.js”
import Star from ‘./star.class.js’
import Moon from “./moon.class.js”

// 默认导出 MoonNight 类,并继承自 Canvas
export default class MoonNight extends Canvas {
constructor(id, config) {
// super 继承父类
super(id, config)

// config 为传入的参数,这里还没有什么用,只有传入 canvas 的 isScreen
this.baseConf = Object.assign({ isScreen: true }, config)

// 声明 moon 成员
this.moon = null

// 填充一个随机数组成的 星星 列表
// 星星的数量为 宽度的五分之一
this.starPoints = Array(parseInt(this.cv.width / 5)).fill({}).map(item => {
  return {
    x: parseInt(Math.random() * this.cv.width), // 星星的坐标 x
    y: parseInt(Math.random() * this.cv.height * .6), // 星星的坐标 y
    size: parseInt(Math.random() * 10 + 5), // 星星的大小
    big: Math.random() > .5 ? true : false // 一个Boolean值,表示星星下一次变大还是变小
  }
})

// 初始化 MoonNight,并开始绑定 动画
this.init()
this.t = setInterval(this.update.bind(this), 1000 / 30)

}

// init 方法
init() {
console.log(‘MoonNight::: init!’);
this.draw()
}

// 绘画 方法
draw() {
// 挨个画各个元素
this.drawSky()
// 画星星,将每个坐标转化为 实例化后的 星星
this.stars = this.starPoints.map(item => new Star(this.ctx, item))
this.drawMountain()
// 复制 moon 为 Moon 对象
this.moon = new Moon(this.cv, this.ctx)
this.drawText()
}

// 画 文字
drawText() {
let fontSize = this.cv.width > this.cv.height ? this.cv.width * .02 : this.cv.height * .02
console.log(‘MoonNight::: drawText!’);
this.ctx.beginPath()
this.ctx.font = ${fontSize}px bold 楷体;
this.ctx.fillStyle = “#F0F003”;
this.ctx.textAlign = “center”;
this.ctx.textBaseline = “middle”;
// 绘制文字(参数:要写的字,x坐标,y坐标)
this.ctx.fillText(“但愿人长久,”, this.cv.width * .7, this.cv.height * .16);
this.ctx.fillText(“千里共婵娟。”, this.cv.width * .7, this.cv.height * .16 + fontSize + 10);
this.ctx.closePath()
}

// 画 天空

drawSky() {
console.log(‘MoonNight::: drawSky!’);
let grad = this.ctx.createLinearGradient(0, 0, 0, this.cv.height)
grad.addColorStop(0, “#011447”);
grad.addColorStop(.2, “#011447”);
grad.addColorStop(1, “#958E78”);
this.ctx.fillStyle = grad;
this.ctx.fillRect(0, 0, this.cv.width, this.cv.height);
this.ctx.fill()
}

// 画 山
drawMountain() {
// 山 各个点
let points = [
[0, this.cv.height * (.7)],
[this.cv.width * .06, this.cv.height * .6],
[this.cv.width * .09, this.cv.height * .66],
[this.cv.width * .16, this.cv.height * .5],
[this.cv.width * .2, this.cv.height * .6],
[this.cv.width * .22, this.cv.height * .57],
[this.cv.width * .4, this.cv.height * .8],
[this.cv.width * .5, this.cv.height * .7],
[this.cv.width * .6, this.cv.height * .8],
[this.cv.width * .75, this.cv.height * .75],
[this.cv.width * .9, this.cv.height * .8],
[this.cv.width * .95, this.cv.height * .75],
[this.cv.width, this.cv.height * .8],
[this.cv.width, this.cv.height]
]

// 山 阴影
// 阴影 为山的点进行转换
let yy = points.map(item => {
  return [
    item[0] + 8, item[1] - 1
  ]
})

// 先画 阴影,使用画布默认机制
this.ctx.beginPath()
this.ctx.fillStyle = 'pink'
this.ctx.moveTo(0, this.cv.height)
for (let item of yy) {
  this.ctx.lineTo(item[0], item[1])
}
this.ctx.fill()
this.ctx.closePath()

// 画山
this.ctx.beginPath()
this.ctx.fillStyle = '#1A1401'
this.ctx.moveTo(0, this.cv.height)
for (let item of points) {
  this.ctx.lineTo(item[0], item[1])
}
this.ctx.fill()
this.ctx.closePath()

}

// 更新 方法
update() {
console.log(‘MoonNight::: update!’);
// 清空画布
this.ctx.clearRect(0, 0, this.cv.width, this.cv.height)
this.starPoints = this.starPoints.map(item => {
return {
// 更新 x 位置,如果到最左边则直接变最右边去,如果转换为随机数,后续坐标会像左半边聚集
x: item.x - 1 < 0 ? this.cv.width : item.x - 1,
y: item.y,
// 更新 size,步值为 .5
size: item.big ? item.size + .5 : item.size - .5,
// 如果星星大小超过最大值,则开始变小,否则开始变大
big: item.size > 15 && item.big === true ? false : item.size < 1 && item.big === false ? true : item.big
}
})
// 数据更新后,重新画图
this.draw()
}
}

4. star.class.js
因为五角星坐标计算比较复杂,为了迅速把星星画出来,所以选用的为四角星。
从上面可以看出来,画星星需要三个关键值:x,y,size,还有一个 big 属性作为 update 的依据。
星星的坐标是这样的:
在这里插入图片描述
灵魂画手上线,如上图所示,通过传入的 x,y,size,可以整理出来8个顶点,通过8个顶点连线并填充就能得到一个 布灵布灵 的小星星,同时修改 size 的大小,达到星星闪烁的效果。
代码如下:
js复制代码export default class Star {
constructor(ctx, config) {
this.ctx = ctx
this.config = Object.assign({ x: 100, y: 100, size: 10 }, config)

this.init()

}

init() {
this.draw()
}

draw() {
this.x = this.config.x
this.y = this.config.y

	// 0.6 为星星的宽高比酸楚,计算得出宽高
this.w = this.config.size * .6
this.h = this.config.size

// 0.05 为中心点到最近的四个点的坐标差值百分比
this.i = this.config.size * .05

// 导出 八个点
this.points = [
  [this.x - this.w / 2, this.y],
  [this.x - this.i, this.y - this.i],

  [this.x, this.y - this.h / 2],
  [this.x + this.i, this.y - this.i],

  [this.x + this.w / 2, this.y],
  [this.x + this.i, this.y + this.i],

  [this.x, this.y + this.h / 2],
  [this.x - this.i, this.y + this.i]
]

// 白色的小星星就是这么画出来的
this.ctx.beginPath()
this.ctx.fillStyle = '#fff'
this.ctx.moveTo(this.points[0][0], this.points[0][1])
for (let point of this.points) {
  this.ctx.lineTo(point[0], point[1])
}
this.ctx.fill()
this.ctx.closePath()

}

// 更新方法,其实没用上,下面解释一下
update() {
this.config = {
x: this.config.x–,
y: this.config.y,
size: this.config.size
}
this.draw()
}
}

解释:
在本项目中,星星的更新,其实应该是在 star 的 update中声明,但是这个项目中卸载了 MoonNight 的 update 方法中。
为什么呢?
因为我个人认为在 MoonNight中直接对数组进行操作,可能比 遍历 每个 star 对象要快,纯直觉,并没有做测试。感兴趣的可以操作一下。
哈哈哈~~~

5. moon.class.js
月亮很简单,因为月亮在这个项目中没有做动作,有的话估计也就是光晕会闪一下。
所以这个类中没有做 update方法,只有 draw。
看过上面的代码后这个几乎没有难度:
js复制代码export default class Moon {
constructor(cv, ctx) {
this.cv = cv
this.ctx = ctx

this.init()

}

init() {
this.draw()
}

draw() {
let r = this.cv.width > this.cv.height ? this.cv.width * .05 : this.cv.height * .05
this.ctx.beginPath()
this.ctx.fillStyle = ‘#F0F003’
this.ctx.shadowOffsetX = 0; // 阴影Y轴偏移
this.ctx.shadowOffsetY = 0; // 阴影X轴偏移
this.ctx.shadowBlur = 14; // 模糊尺寸
this.ctx.shadowColor = ‘#F0F003’; // 颜色

this.ctx.arc(this.cv.width * .2, this.cv.height * .2, r, 0, 2 * Math.PI)
this.ctx.fill()
// 把 阴影设为0,避免影响到其他地方
this.ctx.shadowOffsetX = 0; // 阴影Y轴偏移
this.ctx.shadowOffsetY = 0; // 阴影X轴偏移
this.ctx.shadowBlur = 0;
this.ctx.closePath()

this.ctx.beginPath()
this.ctx.strokeStyle = '#F2D90B'
this.ctx.lineWidth = r * .15
this.ctx.lineCap = 'round'

this.ctx.arc(
  this.cv.width * .2,
  this.cv.height * .2,
  r * .8,
  15 / 360 * Math.PI,
  170 / 360 * Math.PI);
this.ctx.stroke()
this.ctx.closePath()

}
}

总结一下
看到这里,是不是觉得很简单?
这个 demo 本来就很简单,主要是想用一下 使用 class 封装在使用 canvas 过程中的一些实例,所以将一个 js 文件 拆分成了4个,并互相引用,很明显,完全也可以把山、文字、天空都提出去,再进行组装。同理,可以封装一下房子、树、云等元素,并在元素上绑定一些交互方法(点击、按键),用来实现一些其他的需求。
canvas 对刚接触的可能会觉得很绕,其实研究一下很有意思的,尤其是对象钻研算法的时候,可以通过很巧妙的方法将算法实现的过程中每一步都完成展现,下面会粘一下那个很棒的设计!叹为观止!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

中秋-canvas-唯美星空 的相关文章

随机推荐

  • Python 爬虫 NO.4 HTTP 响应状态码

    1 HTTP 响应状态码 响应状态码 即 Response Status Code 表示服务器的响应状态 如 200 代表服务器正常响应 404 代表页面未找到 500 代表服务器内部发生错误 在爬虫中 我们可以根据状态码来判断服务器响应状
  • SIGIR'22

    1 背景 近年来 因果推断在推荐 广告 用户增长等领域得到越来越多的关注和应用 如在用户 客户增长领域的消息发送和权益分发方面 为了兼顾用户体验和平台效率 不仅需要预估用户在接受不同权益下的转化概率 还需要预估用户自然情况下未接收干预的转化
  • cocos2d-x屏幕适配原理分析

    转自 https www 2cto com kf 201212 175527 html https blog csdn net u012861978 article details 53233892 分析 designResolutionS
  • QT5.11下载与安装教程

    一 QT软件下载 Qt 5 9 之后的安装包与之前相比 不再区分 VS 版本和 MinGW 版本 而是全都整合到了一个安装包中 因此 与之前的安装包相比 体积也是大了不少 以前是 1G 多 现在是 2G 多 QT5 9 0安装包 QT5 8
  • 电商打折套路解析

    1 分析出 各个品牌都有多少商品参加了双十一活动 这里面有个问题 最后在设置分类是设置错了 应该有4个类 少量少打折 少量大打折 大量小打折 大量大打折 分析思路可以参考下面的思维导图 import numpy as np import p
  • 使用Matplotlib画心形函数图

    函数如下 代码如下 import numpy as np import matplotlib pyplot as plt 导入模块 t np arange 6 6 0 1 x 16 np power np sin t 3 y 13 np c
  • labview与三菱PLC通讯研究

    labview 与三菱 PLC 通讯研究 一 介绍Labview Labvie是实验室虚拟仪器工程工作台 Labview Virtual Instruments Engineering Workbench 的简称 是美国国家仪器公司开发的虚
  • Spring 日志框架

    Spring5 日志使用 Spring jcl 模块 测试一下spring5 的默认日志实现 public class LogDemo public static void main String args Log logger LogFa
  • 第十五届全国大学生信息安全竞赛(ciscn初赛) 部分writeup

    杂项的附件地址 https share weiyun com BZyngGSZ CISCN web Ezpop misc everlasting night ez usb 问卷调查 pwn login nomal crypto 签到电台 基
  • ICDE2020

    Improving Neural Relation Extraction with Implicit Mutual Relations Jun Kuang Yixin Cao Jianbing Zheng Xiangnan He Ming
  • Centos虚拟机使用virt-manager创建虚拟机

    目录 使用virt manager 主机与centos7虚拟机的共享文件夹设置 将共享文件夹中的iso文件复制到上个home iso目录下 使用virt manager创建虚拟机 上篇文章链接 VM下安装Ubuntu CentOS并安装配置
  • C#两种进程暂停和继续的方法 2021-02-16

    C 两种进程暂停和继续的方法 方法一 方法二 方法一 可以使用Thread Suspend和Thread Resume这两个方法 namespace ThreadSuspended public partial class Form1 Fo
  • CLIP跨语言多模态模型 pytorch下安装

    github地址 GitHub openai CLIP Contrastive Language Image Pretraining 创建python环境 conda create n CLIP python 3 8 安装pytorch和t
  • lxml.html方法,用lxml编辑html代码

    假设我有html代码 它被破坏了 它是从另一个程序给出的 我必须找到其中的所有图像 并将src属性更改为一些代码 仅此而已 它应该在决赛中被打破 在 这是我的代码 coding utf 8 from lxml import etree fr
  • Android 查看是否已经安装某应用(获取某应用的包名)

    借助的工具 adb以及真手机 adb下载 https dl google com android repository platform tools r33 0 1 windows zip 下载解压 解压到ADB文件 复制该路径 配置环境变
  • 数据库SQL运用(1)

    数据库关键字 Top Like In Between As Join InnerJoin LeftJoin RightJoin FullJoin Union Top Top 子句用来截取要返回的数目 如果查询出的数据有成千上百条 那么Top
  • 机器学习——回归问题中的曲线过拟合问题

    问 使用简单的函数解决回归问题时更容易过拟合 答 使用简单的函数解决回归问题可能更容易欠拟合 而不是过拟合 这是因为简单的函数通常具有较低的灵活性 不能很好地拟合复杂的数据模式 因此 如果使用简单的函数来解决复杂的回归问题 则可能会发生欠拟
  • 计算机二级python基础题刷题笔记(三)

    hello 看到三的小伙伴们你们已经超过30 的对手啦 接下来也要加油呀 代码没有最好 只有更好 如果你有更好的想法答案欢迎在评论区里发表呀 1 将程序里定义好的std列表里的姓名和成绩与已经定义好的模板拼成一段话 显示在屏幕里 std 张
  • 使用QrCode解析二维码

    使用QrCode识别二维码 1 引入QrCode import QrCode from qrcode decoder 2 HTML 使用el upload上传图片
  • 中秋-canvas-唯美星空

    明月几时有 把酒问青天 不知天上宫阙 今夕是何年 我欲乘风归去 又恐琼楼玉宇 高处不胜寒 起舞弄清影 何似在人间 转朱阁 低绮户 照无眠 不应有恨 何事长向别时圆 人有悲欢离合 月有阴晴圆缺 此事古难全 但愿人长久 千里共婵娟 值此中秋佳节