在 Canvas 开发中,经常会提到粒子系统,使用粒子系统可以模拟出火、雾、云、雪等抽象视觉效果。它是HTML5新增的为页面添加多样化效果的绝佳实践。

正巧最近做的一个天气类微信小程序紧接尾声,寻思着首页展示温度等信息的地方似乎少了点什么。到Android自带的天气预报上看了下,恍然大悟。于是结合前一段时间钻研的canvas为其添加了雨雪粒子效果。


小程序中的“绘图”API

小程序的绘图 API 虽然也叫canvas,但跟 HTML5 的 Canvas 本质上有很大区别的,其原因是小程序的绘图(Canvas)是客户端实现的 Native UI 组件,而不是普通的 H5 组件,所以在使用上跟普通的 H5 组件用法略有不同。

IOS和Android实现方式还不一样,据说它是基于 Ejecta 实现的。

其主要体现在以下方式上:

  1. 上下文获取方式不同

小程序绘图 API 的 canvasContext 获取方式是通过 <canvas>canvas-id 来获取的,即:

<canvas canvas-id="test"></canvas>
//获取canvas
let ctx = wx.createCanvasContext('test')

这里有一点:它并不同于“获取元素”!

  1. API 写法不同

曾经的小程序的绘图 API 在用法上区别于绝大部分的 HTML5 Canvas 属性写法,它有自己的小程序写法,例如:

const ctx = wx.createCanvasContext('myCanvas')
ctx.setFillStyle('red')
ctx.fillRect(10, 10, 150, 75)
ctx.draw()

不过值得一提的是,在 1.9.0 基础库以上,类似 fillStyle、lineWidth 这类的,可以直接跟 H5 的写法一样,不需要使用 setXxxx 的方式了。

  1. 想要显示绘制效果,需要 ctx.draw() 使用

在小程序的绘图使用中,对 context 进行绘制之后,并不会立即绘制到画布上,而是通过执行 ctx.draw() 的方式,将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中。但 ctx.draw() 方法比较消耗性能,因此不建议在一个绘制周期内多次调用!


粒子系统的设计

在这个小程序中,笔者使用粒子系统做了雨雪效果,采用es6-Class写法:粒子系统由基类和子类组成。Particle 是基类,定义了子类统一的方法,如 run()stop()clear() 等。基类负责整个粒子系统动画周期和流程的维护,子类负责具体实现的粒子效果,比如下雨下雪的效果是子类实现的,而下雨下雪的开关和公共处理流程是基类控制的。

基类由如下几个方法组成:

  • _init():实例化时第一执行的方法;空,由子类具体实现
  • _draw():每个动效周期内画图用的方法;空,由子类具体实现
  • run:设置定时器,定时执行 _draw(),实现动画周期
  • stop:停止动画
  • clear:停止动画,并且清空画板

其大致流程如下:

  • 在构造器内调用 _init,随机生成单个粒子,放进数组对象;
  • 在执行实例 run 的时候,设置定时器,定时器回调调用 _draw 绘制粒子,设置单个粒子下一步的属性;
  • 而 _init 和 _draw 是子类具体根据效果实现的

根据如上解释,整个流程就很明了了:

//同级的effect.js文件
// 两个状态
const STATUS_STOP = 'stop'
const STATUS_RUNNING = 'running'
//“基”类-这里就直接当“下雨”类了
class Particle {constructor(ctx, width, height, opts) {this._timer = nullthis._options = opts || {}// canvas 上下文this.ctx = ctxthis.status = STATUS_STOPthis.w = widththis.h = heightthis._init()}_init() {}_draw() {}_update(){}run() {if (this.status !== STATUS_RUNNING) {// 更改状态this.status = STATUS_RUNNING// 绘制循环this._timer = setInterval(() => {this._draw()}, 30)}return this}stop() {// 清理定时器,状态修改this.status = STATUS_STOPclearInterval(this._timer)return this}clear(){this.stop()this.ctx.clearRect(0, 0, this.w, this.h)this.ctx.draw()return this}
}
exports.Particle=Particle

根据上面的提示,在 _init() 中,根据需要生成的粒子个数 amount 循环随机生成每个粒子,放入 this.particles 数组:

// _init
let h = this.h
let w = this.w
// 数量,根据不同雨大小,数量可调
let amount = this._options.amount || 100
// 速度参数,调节下落速度
let speedFactor = this._options.speedFactor || 0.03
let speed = speedFactor * h
let ps = (this.particles = [])
for (let i = 0; i < amount; i++) {let p = {x: Math.random() * w,y: Math.random() * h,l: 2 * Math.random(),xs: -1,ys: 10 * Math.random() + speed,color: 'rgba(0, 0, 0, 0.15)'}ps.push(p)
}

其中:

  • x、y 代表单个粒子的位置,即雨滴开始绘图的位置
  • xs、ys 分别代表 x、y 方向上的加速度,即雨滴的下落速度和角度
  • l 代表雨滴的长度

_draw() 的方法,是先将画布清空,然后遍历 this.particles 数组取出单个雨滴并进行绘制,最后调用一个单独实现的 _update() 重新计算单个雨滴的位置:

// _draw
let ps = this.particles
let ctx = this.ctx
// 清空画布
ctx.clearRect(0, 0, this.w, this.h)
// 遍历绘制雨滴
for (let i = 0; i < ps.length; i++) {let s = ps[i]ctx.beginPath()ctx.moveTo(s.x, s.y)// 画线绘制雨点效果ctx.lineTo(s.x + s.l * s.xs, s.y + s.l * s.ys)ctx.setStrokeStyle(s.color)ctx.stroke()
}
ctx.draw()
return this._update()

_update() 中,我们要做的就是判断“下一时刻每一个雨点的位置”以及“是否超出了“画布”的范围”:

// _update
let {w, h} = this // 获取画布大小
for (let ps = this.particles, i = 0; i < ps.length; i++) {// 开始下一个周期的位置计算let s = ps[i]s.x += s.xss.y += s.ys// 超出范围,重新回收,重复利用if (s.x > w || s.y > h) {s.x = Math.random() * ws.y = -10}
}

我们大致一看会发现,除了调用的名称有的不一样之外,似乎和原生js中的canvas API1没什么区别。

上面是控制“下雨”的,其实下雪的例子类和下雨的唯一区别就是“粒子的形状”了:

class Snow extends Particle {_init() {let {w, h} = thislet colors = this._options._colors || ['#ccc', '#eee', '#fff', '#ddd']// 雪的大小用数量来计算let amount = this._options.amount || 100let speedFactor = this._options.speedFactor || 0.03// 速度let speed = speedFactor * h * 0.15let radius = this._options.radius || 2let ps = (this.particles = [])for (let i = 0; i < amount; i++) {let x = Math.random() * wlet y = Math.random() * hps.push({x,y,// 原始 x 坐标,后面计算随机雪摆动是以此为基础ox: x,// 向下运动动能变量ys: Math.random() + speed,// 雪的半径大小r: Math.floor(Math.random() * (radius + 0.5) + 0.5),// 颜色随机取color: colors[Math.floor(Math.random() * colors.length)],rs: Math.random() * 80})}}_draw() {let ps = this.particleslet ctx = this.ctxctx.clearRect(0, 0, this.w, this.h)for (let i = 0; i < ps.length; i++) {let {x, y, r, color} = ps[i]ctx.beginPath()// 绘制下雪的效果ctx.arc(x, y, r, 0, Math.PI * 2, false)ctx.setFillStyle(color)ctx.fill()ctx.closePath()}ctx.draw()this._update()}_update() {let {w, h} = thislet v = this._options.speedFactor / 10for (let ps = this.particles, i = 0; i < ps.length; i++) {let p = ps[i]let {ox, ys} = pp.rs += v// 这里使用了 cos,做成随机左右摆动的效果p.x = ox + Math.cos(p.rs) * w / 2p.y += ys// console.log(ys)// 重复利用if (p.x > w || p.y > h) {p.x = Math.random() * wp.y = -10}}}
}

使用粒子系统

首先,在 WXML 代码中,给实时天气模块增加 id 为 effect 的 Canvas 组件:

<canvas canvas-id="effect" id="effect"></canvas>

而后引入上面的js文件:

import Particle from './effect'
let Rain=Particle.Particle

重点: 在微信小程序内,绘图 API(Canvas)内的长宽单位为 px,而我们页面布局用的是 rpx,虽然我们在 CSS 内已经使用 rpx 设置了 Canvas 的大小,但是由于内部单位的缘故,在实例化 Rain/Snow 粒子系统的时候,传入的 width 和 height 参数应该是实际的 px 大小。

rpx 转 px 是根据不同的设备屏幕尺寸转换的。虽然切图可以按照 1rpx=2px 这样标准的 iPhone 6 视觉稿做页面,而且微信似乎帮我们做了兼容处理,但是涉及实际 px 计算时,仍不能简单采用 1rpx=2px 的方式来解决,需要我们按照实际的 rpx 对应 px 的比例进行转换。如何获取 rpx 和 px 的实际比例呢?我们知道微信小程序中默认规定了屏幕宽度为 750rpx,根据这个设计,我们可以通过 wx.getSystemInfo 获取到的信息,找到手机屏幕的宽度大小 windowWidth 即可算出对应的比例,代码如下:

// 在 onload 内
wx.getSystemInfo({success: (res) => {let width = res.windowWidththis.setData({width,scale: width / 375})}
})

这样,上面的 width就是屏幕的实际 px 宽度,而每个元素的实际 px 高度则由 元素 rpx 高度 / 2 * scale 得到。

最后,我们在页面代码中,实际使用时的代码是下面这样的:

const ctx = wx.createCanvasContext('effect')
let {width, scale} = this.data
// 768 为 CSS 中设置的 rpx 值
let height = 768 / 2 * scale
let rain = new Rain(ctx, width, height, {amount: 100,speedFactor: 0.03
})
// 跑起来
rain.run()

在切换城市或者检测到没有雨雪天气时调用clear去除效果:

rain.clear()

  1. 感兴趣的朋友可以参见这篇文章:HTML5 canvas基础与「生成名片」应用程序 ↩︎

微信小程序 | canvas为你的天气预报添加雨雪效果相关推荐

  1. canvas为你的天气预报添加雨雪效果 | 微信小程序

    关注 前端瓶子君,回复"交流" 加入我们一起学习,天天进步 来源:行舟客 https://yunxiaomeng.blog.csdn.net/article/details/110 ...

  2. 微信小程序上拉触底案例添加loading效果

    在上拉触底的时候如何添加小程序的loading效果呢??就是左边现实的效果.有一个转动的圈圈 首先我们打开.js文件 在原有的请求颜色的基础上添加wx.showLoading getColors(){ ...

  3. 【微信小程序canvas】实现小程序手写板用户签名(附代码)

    [微信小程序canvas]实现小程序手写板用户签名(附代码) 工作中公司业务需要的微信小程序用户签字功能 先看效果图: wxml <view class="wrapper"& ...

  4. 微信小程序-canvas绘制文字实现自动换行

    微信小程序-canvas绘制文字实现自动换行 在使用微信小程序canvas绘制文字时,时常会遇到这样的问题:因为canvasContext.fillText参数为 我们只能设置文本的最大宽度,这就产生 ...

  5. 微信小程序 canvas type = 2d 绘制海报心得(包括怎么绘制图片和圆角图片和圆角矩形等)

    微信小程序 canvas type=2d 使用心得 为了方便这里我封装成了一个component 然后说说怎么使用最新的方法(使用方法类似于html中的canvas可以进行参考)获取--canvas ...

  6. 微信小程序canvas画布新接口type为2D时drawImage方法的使用以及注意事项

    微信小程序canvas画布自2.9.0 起支持一套新 Canvas 2D 接口(需指定 type 属性),但文档不全,从原canvas的api转变到新的api时遇到不少问题,现将新版与旧版的画布载入图 ...

  7. 微信小程序详解 php,微信小程序canvas基础详解

    canvas 元素用于在网页上绘制图形.HTML5 的 canvas 元素使用 JavaScript 在网页上绘制2D图像.本文主要和大家分享微信小程序canvas基础详解,希望能帮助到大家. 一.了 ...

  8. 微信小程序canvas简单使用

    微信小程序canvas简单使用 index.wxml文件 index.js文件 效果图 index.wxml文件 <canvas type="2d" id="can ...

  9. 微信小程序canvas实现签名功能

    微信小程序canvas实现签名功能 在微信小程序项目中,开发模块涉及到手写签名功能,微信小程序canvas闪亮登场 文章目录 微信小程序canvas实现签名功能 前言 一.微信小程序canvas实现签 ...

最新文章

  1. php 任意文件上传,任意文件上传漏洞
  2. 飞蚊症手术失败与Photostress Recovery
  3. free命令里的buffers/cache
  4. Android 系统性能优化(79)---提升Android应用的启动速度与设计
  5. 货车交强险在网上能买吗?
  6. CentOS 7 编译安装 PHP 7
  7. const 与 readonle 的异同
  8. Oracle迁移PostgreSQL经验总结
  9. 如何提升数据分析的效率
  10. 随笔小杂记(三)——将遥感大图随机分割成小图作为训练集
  11. ad16自动布线设置规则_未来的PCB协同设计制造过程离不开自动化工具
  12. 很喜欢博客园这个平台
  13. My Fifty-Seventh Page 递增子序列 - By Nicolas
  14. Factor Graphs and GTSAM
  15. 黄东旭当选 CCF 数据库专业委员会、开源发展委员会、大数据专家委员会执行委员
  16. Chapter3.2 实现多个PLAYS
  17. Rockchip Linux PCIe 开发指南
  18. 【公众号】公众号网页跳转关注微信公众号
  19. 第十二届蓝桥杯嵌入式——赛后总结
  20. oracle 基本语法大全

热门文章

  1. 实现OCR语言识别Demo(一)- BottomSheet实现
  2. 在Gtalk中和你的msn,yahoo,icq/aim,qq好友聊天
  3. 你目前在用的 RSS 服务还满意吗,赶紧进来手把手教你自建一个私有的 RSS 服务器!...
  4. 无人机自由飞行测试台 FFT GYRO 2000
  5. Cocos2dx之Scene和Scene Graph
  6. 关于多个ul标签并列时下对齐的问题
  7. R语言程序包下载地址:CRAN Packages
  8. 电源模块设计过程(降压、正压转负压)-MC34063
  9. 泛泰A880 Recovery
  10. 思考互联网发展三阶段