侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

canvas实现烟花的示例代码

技术  /  管理员 发布于 7年前   159

前言:马上过年了,我打算在后台里面偷偷地埋个新春祝福+放烟花的彩蛋。项目是基于react+typescript的,因此最后封装成了一个组件,设置好开启时间就可以显示了。

目录结构

目录结构大致如下

我们将烟花分为两个阶段,一个是未炸开持续上升时期,另一个是炸开后分散的时期。
其中Vector表示一个坐标,Particle表示一个烟花的亮点,Firewor表示烟花未炸开时持续上升的亮点。index.tsx就是组件了,绘制了canvas,并执行了动画。

Vector

这个坐标就很简单,后面涉及到位置的变更都可以使用它的add方法进行偏移操作

export default class Vector {    constructor(public x: number, public y: number) {}    add(vec2: {x: number; y: number}) {        this.x = this.x + vec2.x;        this.y = this.y + vec2.y;    }}

Particle

初始创建的时候给个坐标,后续每次更新的时候控制y坐标下落,gravity变量就是下落的值。timeSpan用于控制烟花展示的时长

import Vector from './Vector';export default class Particle {    pos: Vector = null;    vel: {x: number; y: number} = null;    dead: boolean = false;    start: number = 0;    ctx: CanvasRenderingContext2D = null;    constructor(pos: {x: number; y: number}, vel: {x: number; y: number}, ctx: CanvasRenderingContext2D) {        this.pos = new Vector(pos.x, pos.y);        this.vel = vel;        this.dead = false;        this.start = 0;        this.ctx = ctx;    }    update(time: number, gravity: number) {        let timeSpan = time - this.start;        if (timeSpan > 500) {this.dead = true;        }        if (!this.dead) {this.pos.add(this.vel);this.vel.y = this.vel.y + gravity;        }    }    draw() {        if (!this.dead) {this.drawDot(this.pos.x, this.pos.y, Math.random() > 0.5 ? 1 : 2);        }    }    drawDot(x: number, y: number, size: number) {        this.ctx.beginPath();        this.ctx.arc(x, y, size, 0, Math.PI * 2);        this.ctx.fill();        this.ctx.closePath();    }}

Firework

生成随机的hsl颜色,hsl(' + rndNum(360) + ', 100%, 60%);Firework每次上升的距离是一个递减的过程,我们初始设置一个上升的距离,之后每次绘制的时候,这个距离减gravity,当距离小于零的时候,说明该出现烟花绽放的动画了。

import Vector from './Vector';import Particle from './Particle';let rnd = Math.random;function rndNum(num: number) {    return rnd() * num + 1;}export default class Firework {    pos: Vector = null;    vel: Vector = null;    color: string = null;    size: number = 0;    dead: boolean = false;    start: number = 0;    ctx: CanvasRenderingContext2D = null;    gravity: number = null;    exParticles: Particle[] = [];    exPLen: number = 100;    rootShow: boolean = true;    constructor(x: number, y: number, gravity: number, ctx: CanvasRenderingContext2D) {        this.pos = new Vector(x, y);        this.vel = new Vector(0, -rndNum(10) - 3);        this.color = 'hsl(' + rndNum(360) + ', 100%, 60%)';        this.size = 4;        this.dead = false;        this.start = 0;        this.ctx = ctx;        this.gravity = gravity;    }    update(time: number, gravity: number) {        if (this.dead) {return;        }        this.rootShow = this.vel.y < 0;        if (this.rootShow) {this.pos.add(this.vel);this.vel.y = this.vel.y + gravity;        } else {if (this.exParticles.length === 0) {    for (let i = 0; i < this.exPLen; i++) {        let randomR = rndNum(5);        let randomX = -rndNum(Math.abs(randomR) * 2) + Math.abs(randomR);        let randomY =Math.sqrt(Math.abs(Math.pow(randomR, 2) - Math.pow(randomX, 2))) *(Math.random() > 0.5 ? 1 : -1);        this.exParticles.push(new Particle(this.pos, new Vector(randomX, randomY), this.ctx));        this.exParticles[this.exParticles.length - 1].start = time;    }}let numOfDead = 0;for (let i = 0; i < this.exPLen; i++) {    let p = this.exParticles[i];    p.update(time, this.gravity);    if (p.dead) {        numOfDead++;    }}if (numOfDead === this.exPLen) {    this.dead = true;}        }    }    draw() {        if (this.dead) {return;        }        this.ctx.fillStyle = this.color;        if (this.rootShow) {this.drawDot(this.pos.x, this.pos.y, this.size);        } else {for (let i = 0; i < this.exPLen; i++) {    let p = this.exParticles[i];    p.draw();}        }    }    drawDot(x: number, y: number, size: number) {        this.ctx.beginPath();        this.ctx.arc(x, y, size, 0, Math.PI * 2);        this.ctx.fill();        this.ctx.closePath();    }}

FireworkComponent

组件本身就很简单了,生成和绘制Firework。我们在这里面可以额外加一些文字

import React from 'react';import Firework from './Firework';import {autobind} from 'core-decorators';let rnd = Math.random;function rndNum(num: number) {    return rnd() * num + 1;}interface PropTypes {    onClick?: () => void;}@autobindclass FireworkComponent extends React.Component<PropTypes> {    canvas: HTMLCanvasElement = null;    ctx: CanvasRenderingContext2D = null;    snapTime: number = 0;    fireworks: Firework[] = [];    gravity: number = 0.1;    componentDidMount() {        this.canvas = document.querySelector('#fireworks');        this.canvas.width = window.innerWidth;        this.canvas.height = window.innerHeight;        this.ctx = this.canvas.getContext('2d');        this.init();        this.draw();    }    init() {        let numOfFireworks = 20;        for (let i = 0; i < numOfFireworks; i++) {this.fireworks.push(new Firework(rndNum(this.canvas.width), this.canvas.height, this.gravity, this.ctx));        }    }    update(time: number) {        for (let i = 0, len = this.fireworks.length; i < len; i++) {let p = this.fireworks[i];p.update(time, this.gravity);        }    }    draw(time?: number) {        this.update(time);        this.ctx.fillStyle = 'rgba(0,0,0,0.3)';        this.ctx.fillStyle = 'rgba(0,0,0,0)';        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);        this.ctx.font = 'bold 30px cursive';        this.ctx.fillStyle = '#e91818';        let text = 'XX项目组给您拜个早年!';        let textWidth = this.ctx.measureText(text);        this.ctx.fillText(text, this.canvas.width / 2 - textWidth.width / 2, 200);        text = '在新年来临之际,祝您:';        textWidth = this.ctx.measureText(text);        this.ctx.fillText(text, this.canvas.width / 2 - textWidth.width / 2, 260);        text = '工作顺利,新春快乐!';        this.ctx.font = 'bold 48px STCaiyun';        this.ctx.fillStyle = 'orangered';        textWidth = this.ctx.measureText(text);        this.ctx.fillText(text, this.canvas.width / 2 - textWidth.width / 2, 340);        this.ctx.fillStyle = 'gray';        this.ctx.font = '18px Arial';        text = '点击任意处关闭';        textWidth = this.ctx.measureText(text);        this.ctx.fillText(text, this.canvas.width - 20 - textWidth.width, 60);        this.snapTime = time;        this.ctx.fillStyle = 'blue';        for (let i = 0, len = this.fireworks.length; i < len; i++) {let p = this.fireworks[i];if (p.dead) {    p = this.fireworks[i] = new Firework(        rndNum(this.canvas.width),        this.canvas.height,        this.gravity,        this.ctx    );    p.start = time;}p.draw();        }        window.requestAnimationFrame(this.draw);    }    render() {        return (<canvas    id="fireworks"    onClick={this.props.onClick}    style={{position: 'fixed', zIndex: 99, background: 'rgba(0,0,0, 0.8)'}}    width="400"    height="400"></canvas>        );    }}export default FireworkComponent;

大致效果

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


  • 上一条:
    canvas简单连线动画的实现代码
    下一条:
    使用iframe+postMessage实现页面跨域通信的示例代码
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 2024.07.09日OpenAI将终止对中国等国家和地区API服务(0个评论)
    • 2024/6/9最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(1个评论)
    • 国外服务器实现api.openai.com反代nginx配置(0个评论)
    • 2024/4/28最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(1个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2017-09
    • 2018-01
    • 2018-07
    • 2018-08
    • 2018-09
    • 2018-12
    • 2019-01
    • 2019-02
    • 2019-03
    • 2019-04
    • 2019-05
    • 2019-06
    • 2019-07
    • 2019-08
    • 2019-09
    • 2019-10
    • 2019-11
    • 2019-12
    • 2020-01
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-12
    • 2024-02
    • 2024-04
    • 2024-05
    • 2024-06
    • 2025-02
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客