大家好!我是Fly哥,最近做了很多粒子动画, 对canvas 实现粒子动画有了一点小感悟,前几天北方都下雪了, 身在魔都的我们, 一点点雪的影子都没有。而下雪动画作为粒子动画中我觉得算是比较简单的好理解, 先把这篇文章讲完,后面再去给大家讲 酷炫的 canvas 文字烟花动画。 本篇文章大概阅读花费7分钟, 你可以学到如何去实现一个粒子动画,我觉得把思路学会了, 后面产品假设提任何需求你都是可以cover 住的。 废话不多说, 直接先看先看效果:

snow

看着是不是像辣么回事,哈哈哈哈, fly哥写的基础版,圣诞节快到了,你可以再我的基础上做下面👇🏻几个优化

  1. 使用雪花贴图
  2. 为每一个粒子增加重力效果
  3. 性能优化的角度考虑下,考虑使用离屏canvas(数量比较多的情况下,可以去使用)

# 粒子动画

可能有的人到现在还不是很清楚啥是粒子动画:

# 粒子

粒子是什么?粒子是一种微小的物体,比如像我们周边环境中的雪花,火星等物体。因此在游戏中一般都用粒子特效来模拟咱们现实生活中的许多自然现象。

# 粒子系统

粒子系统是众多粒子的集合。一般具有具有粒子的更新,显示,销毁及其创建粒子等性质。不同的粒子系统具有不同的粒子行为,所以所具有的性质会略有区别。

# 综合

粒子动画其实很容易理解了,就是很多个粒子按照某个特定的运动轨迹组合起来的动画,所以做粒子类属性 会带有一些物理属性, 比如重力,风的阻力, 加速度。。。 这些东西,其实主要为了更加贴近自然,更加真实。物理比较差的同学记得把物理知识补一下。

菜狗

# 雪粒子

简单介绍上面的概念后,大家可以结合上面的动画简单分析一下,雪粒子这个类应该有哪些属性呢???

首先我雪是用ctx.arc 去画的,所以呢?? 肯定有 x 和 y 轴 坐标 ,还有 radius 半径

没错

这时候有同学开始抢答了?说fly哥,你上面提到了粒子的速度, 雪花肯定每一个下的速度不一样, 肯定有一个 vx, vy

不错哦

哎呦不错哦, 现在已经5个属性了, 还有没有属性呢, 这时候有个细心的妹子站出来了, 我看到了透明度的变化,确实 还是妹子细心哇!!

小姐姐

大家在思考下, 还有没有什么属性呢? 看着同学们雅雀无声, 我给一个提醒, 如何保持雪一直下,难道我要不断添加粒子嘛, 有没有边界啥的??

thinking

有同学就说:不亏是fly哥,思考问题就是全面哇,哈哈哈哈其实这里涉及到 就一个属性 边界检测呗, 不过没辣么夸张, 就是是一个最大距离 maxDistance, 如果超过了 最大距离 我们就让它从一开始落呗, 不就实现了永动机的效果!

# 编码实现

理论说的都差不多了,我这里写的都是伪代码,本着 授人以鱼不如授人以渔 的目的, 你如果真的想学, 跟着我的思路做一篇肯定很没问题!

# 创建canvas

创建canvas 拿到canvas 的上下文 这个应该不用多讲了

  const canvas = document.getElementById( 'canvas' );
  const ctx = canvas.getContext( '2d' );

# 创建snow 类

主要是每一个小的雪花粒子:

class Snow {
  dia: number
  fill: string
  vy: number
  vx: number
  z: number
  y: number
  x: number
  maxDistance: number
  width: number
  height: number

  constructor(width: number, height: number, maxDistance: number) {
    this.x = Math.random() * (width + maxDistance) - maxDistance / 2
    this.y = Math.random() * (height + maxDistance) - maxDistance / 2
    this.maxDistance = maxDistance
    this.width = width
    this.height = height
    this.z = Math.random() * 0.5 + 0.5
    this.vx = (Math.random() * 2 - 0.5) * this.z
    this.vy = (Math.random() * 1.5 + 1.5) * this.z
    this.fill = 'rgba(255,255,255,' + (0.5 * Math.random() + 0.5) + ')'
    this.dia = (Math.random() * 2.5 + 1.5) * this.z
  }

  draw(ctx: CanvasRenderingContext2D) {
    ctx.beginPath()
    ctx.strokeStyle = 'transparent'
    ctx.fillStyle = this.fill
    ctx.arc(this.x, this.y, this.dia, 0, 2 * Math.PI)
    ctx.closePath()
    ctx.stroke()
    ctx.fill()
    return this
  }

  update() {
    this.x += this.vx
    this.y += this.vy
    if (this.x > this.width + this.maxDistance / 2) {
      this.x = -(this.maxDistance / 2)
    } else if (this.x < -(this.maxDistance / 2)) {
      this.x = this.width + this.maxDistance / 2
    }
    if (this.y > this.height + this.maxDistance / 2) {
      this.y = -(this.maxDistance / 2)
    } else if (this.y < -(this.maxDistance / 2)) {
      this.y = this.height + this.maxDistance / 2
    }
  }
}

这里面 把上面的所说的属性 都讲到了, 其实无论你做任何粒子, 他都有一个创建 和 更新 ,因为你是做动画,

比如第一帧画面 是没有雪花粒子的, 所以第一帧 就是创建粒子,然后后面每一帧其实就是改变粒子的位置 就好了,不断重复这样的过程 配合 requestanimation 去实现。 后面每一帧 就是更新 update 函数,

我这里还是解释下: 粒子不断加 一个固定的速度 vx vy ,然后做了边界判断 , 你可以自己去修改的。 由于 有不同的粒子, 粒子的大小 透明度 、速度 都是不同的, 所以 使用了random

# 动画实现

动画实现 很简单 就两步骤

  1. 创建粒子
  2. 更新粒子
// 第一帧 创建1000 个
for (let i = 0; i < 1000; i++) {
  points.push(new Snow(100, 100, 100))
}
// 后面都是更新
 ctx.clearRect(0, 0, view.width, view.height)
 ctx.fillStyle = 'rgba(0,128,255,1)'
 ctx.fillRect(0, 0, view.width, view.height)
 // 调用每个粒子的更新 函数
 points.forEach((point) => {
    point.draw(ctx).update()
 })

# 源码获得

如果还是有同学还是要看源码的, 直接关注公众号,私信我就好了!!

# 总结

很感谢你能看到这里,如果觉得写的不错, 帮我点个赞和在看,让更多同学看到, 如果有任何问题欢迎直接私信我。多交流,多学习。欢迎**「长按图片 加 Fly 为好友」**,我会精选优质数据可视化、游戏、图形相关好文、还有各种大厂面试文章等等,立志做一个有温度的公众号。

图片

围观fly哥朋友圈👬🏻

2021陪你一起度过!

回复思维导图 免费获取20G 前端数据可视化学习资料!

回复 加群 拉你进可视化交流技术群,一起吹水,长期学习交流2021陪你一起度过!

回复面试 获得一线大厂相关面经文章

上一次更新时间: 2021/12/26 下午6:37:38