Canvas实现礼花飘落效果

需求

  • 实现礼花飘落效果

  • 定义几种礼花类型(圆形、方形、矩形、三角形),随机出现

  • 降落速度、礼花大小、数量等可以通过参数传入

实现

  • 新建util.js文件,定义一些常用方法
export const random = () => Math.random() // 不包含max export const randomMax = max => Math.floor(Math.random() * max) // 包含max export const randomMaxCeil = max => Math.ceil(Math.random() * max) if (!window.requestAnimationFrame) { window.requestAnimationFrame = ( window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || function (callback) { return setTimeout(callback, Math.floor(1000 / 60)) } ) } if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = ( window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.msCancelAnimationFrame || window.oCancelAnimationFrame || clearTimeout ) }
  • 新建flake.js实现礼花碎屑基础类Flake
import { random, randomMax, randomMaxCeil } from './util' class Flake { constructor(canvas, size, fallSpeed) { this.canvas = canvas this.maxSize = size this.fallSpeed = fallSpeed // Y方向速度 this.speed = 1 * random() + fallSpeed // 用来计算X方向偏移 this.stepSize = random() / 30 this.step = 0 // 背景色 const r = ~~(256 * random()) const g = ~~(256 * random()) const b = ~~(256 * random()) this.rgba = `rgba(${r}, ${g}, ${b}, 1)` // 旋转角度 this.rot = 0 this.reset() // 初始化时候,让碎屑随机分布在屏幕上 this.y = randomMax(canvas.height) } // 更新礼花碎屑位置 update() { this.velX *= 0.98 this.velY <= this.speed && (this.velY = this.speed) this.velX += Math.cos(this.step += 0.05) * this.stepSize this.y += this.velY this.x += this.velX if ( this.x >= this.canvas.width || this.x <= 0 || this.y >= this.canvas.height || this.y <= 0 ) { this.reset() } } // 礼花碎屑重置到最上面,大小和X方向位置重新随机 reset() { const { canvas, maxSize, speed } = this this.x = randomMax(canvas.width) this.y = 0 this.size = random() * maxSize + 2 this.velY = speed this.velX = 0 } }
  • 修改flake.js文件,定义几种礼花类型,并添加各自的绘制方法render()
// 圆形 export class CircularFlake extends Flake { constructor(canvas, size, fallSpeed) { super(canvas, size, fallSpeed) this.radius = randomMaxCeil(size / 2) } render(ctx) { const { x, y, rgba, radius } = this ctx.save() ctx.beginPath() ctx.fillStyle = rgba ctx.arc(x, y, radius, 0, 2 * Math.PI, true) ctx.fill() ctx.restore() } } // 方形 export class SquareFlake extends Flake { constructor(canvas, size, fallSpeed) { super(canvas, size, fallSpeed) this.width = this.height = randomMaxCeil(size) } render(ctx) { const { x, y, rgba, rot, width, height } = this ctx.save() ctx.beginPath() ctx.fillStyle = rgba ctx.translate(x, y) ctx.rotate(rot * Math.PI / 180) ctx.translate(-x, -y) ctx.fillRect(x - width / 2, y - height / 2, width, height) ctx.restore() } } // 矩形 export class RectangleFlake extends Flake { constructor(canvas, size, fallSpeed) { super(canvas, size, fallSpeed) this.width = randomMaxCeil(size) this.height = randomMaxCeil(size) } render(ctx) { const { x, y, rgba, rot, width, height } = this ctx.save() ctx.beginPath() ctx.fillStyle = rgba ctx.translate(x, y) ctx.rotate(rot * Math.PI / 180) ctx.translate(-x, -y) ctx.fillRect(x - width / 2, y - height / 2, width, height) ctx.restore() } } // 三角形 export class TriangleFlake extends Flake { constructor(canvas, size, fallSpeed) { super(canvas, size, fallSpeed) this.width = this.height = randomMaxCeil(size) } render(ctx) { const { x, y, rgba, rot, width, height } = this ctx.save() ctx.beginPath() ctx.fillStyle = rgba ctx.translate(x, y) ctx.rotate(rot * Math.PI / 180) ctx.translate(-x, -y) ctx.moveTo(x - width / 2, y + 0.333 * height) ctx.lineTo(x + width / 2, y + 0.333 * height) ctx.lineTo(x, y - 0.666 * height) ctx.closePath() ctx.fill() ctx.restore() } }
  • 新建index.js文件,添加初始化canvas、生成碎片和绘制方法
import { randomMax } from './util' import { CircularFlake, SquareFlake, RectangleFlake, TriangleFlake } from './flake' export default class CanvasFlake { constructor(opts) { // const { // maxFlake = 200, // flakeSize = 10, // fallSpeed = 2, // parentNode = document.body // } = opts // Object.assign(this, { maxFlake, flakeSize, fallSpeed, parentNode }) opt = opts || {} this.maxFlake = opt.maxFlake || 200 this.flakeSize = opt.flakeSize || 10 this.fallSpeed = opt.fallSpeed || 2 this.parentNode = opt.parentNode || document.body // status: 0 初始状态 1 开始状态 2 停止状态 3 暂停状态 4 重新开始 this.status = 0 this.canvas = null this.ctx = null this.flakes = [] } // 在指定的DOM中插入canvas,并设置相关属性 initCanvas() { const canvas = document.createElement('canvas') canvas.id = 'Paperfall' canvas.width = this.parentNode.clientWidth canvas.height = this.parentNode.clientHeight canvas.setAttribute('style', 'position:absolute;top:0;left:0;z-index:0;pointer-events:none;') this.parentNode.appendChild(canvas) this.canvas = canvas this.ctx = canvas.getContext('2d') window.onresize = function() { canvas.width = window.innerWidth canvas.height = window.innerHeight } } // 生成maxFlake个随机类型的碎片 getFlakes() { const flakeTypes = [ CircularFlake, SquareFlake, RectangleFlake, TriangleFlake ] for (let i = 0; i < this.maxFlake; i++) { this.flakes.push(new flakeTypes[randomMax(flakeTypes.length)](this.canvas, this.flakeSize, this.fallSpeed)) } } // canvas中渲染所有碎片 render() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) this.flakes.forEach(item => { item.update() item.render(this.ctx) }) this.loop = window.requestAnimationFrame(() => { // 用CanvasFlake去执行,否则找不到ctx、flakes等 this.render.apply(this) }) } }
  • 修改index.js,添加方法控制canvas的绘制、停止、暂停和重新开始
import { randomMax } from './util' import { CircularFlake, SquareFlake, RectangleFlake, TriangleFlake } from './flake' export default class CanvasFlake { // ... start() { if (this.status === 1 || this.status === 4) return this.status = 1 this.initCanvas() this.getFlakes() this.render() } stop() { if (this.status === 2 || this.status === 0 || !this.canvas) return this.pause() this.status = 2 this.parentNode.removeChild(this.canvas) this.canvas = null } pause() { if (this.status === 3) return this.status = 3 window.cancelAnimationFrame(this.loop) } resume() { if (this.status === 3 && this.canvas) { this.status = 4 this.loop = window.requestAnimationFrame(() => { this.render.apply(this) }) } } }
  • 在页面中使用
<!-- 有一个DOM节点,放canvas --> <div id="canvasWrapper" style="width: 100vw; height: 100vh"></div>
import CanvasFlake from '@/utils/flake' export default { mounted() { new CanvasFlake({ parentNode: document.getElementById('canvasWrapper'), maxFlake: 80 }).start() } }

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: