Canvasの開発では、パーティクルシステムがよく言及されます。パーティクルシステムは、火、霧、雲、雪などの抽象的な視覚効果をシミュレートするために使用できます。HTML5によって新しく追加されたページに多様な効果を追加するための優れた方法です。
たまたま、気温などの情報が表示されているホームページに何が欠けているのかを考えて、最近作ったWeChatアプレットの天気が終わりました。Androidに付属の天気予報を見て、いきなり気づきました。さっき勉強した帆布と合わせて、雨や雪の粒子を追加しました。
ミニプログラムの「描画」API
アプレットの描画APIはcanvasとも呼ばれますが、HTML5のCanvasとは本質的に異なります。その理由は、アプレット(Canvas)の描画は、通常のH5コンポーネントではなく、クライアントによって実装されるネイティブUIコンポーネントであるためです。そのため、通常のH5コンポーネントとは使用方法が少し異なります。
IOSとAndroidの実装は同じではなく、Ejectaに基づいていると言われています。
これは主に次のように反映されます。
- さまざまなコンテキスト取得方法
canvasContextアプレットAPIの描画は、取得<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を使用する必要はありません。
- 図面は結果を表示したい、あなたは
ctx.draw()
使用する必要があります
小さな描画プログラムは、コンテキストに描画します。すぐにキャンバスに描画されるのではなくctx.draw()
、前述の描画コンテキスト(パス、変形、パターン)でキャンバスに描画されます。ただし、このctx.draw()
方法は比較的消費パフォーマンスが高く、描画サイクルで複数回呼び出すことはお勧めしません。
パーティクルシステムの設計
この小さなプログラムでは、作成者はes6-Class表記を使用して、パーティクルシステムを使用して雨や雪の効果を作成します。パーティクルシステムは、基本クラスとサブクラスで構成されます。粒子は、基本クラスであり、サブクラスのような、統一的な方法を定義しrun()
、stop()
、clear()
などが挙げられます。基本クラスはパーティクルシステムのアニメーションサイクルとフロー全体の維持を担当し、サブクラスは実装された特定のパーティクルエフェクトを担当します。たとえば、雨と雪のエフェクトはサブクラスによって実装され、スイッチとパブリックはサブクラスによって実装されます。雨と雪の処理フローは、の基本クラスによって制御されます。
基本クラスは、次のメソッドで構成されています。
- _init():インスタンス化時に最初に実行されるメソッド。サブクラスによって実装される空。
- _draw():各アニメーションサイクルで絵を描くために使用されるメソッド;空、サブクラスによって実装されます
- 実行:タイマーを設定し、_draw()を定期的に実行して、アニメーションサイクルを実現します
- 停止:アニメーションを停止します
- クリア:アニメーションを停止し、製図板をクリアします
一般的なプロセスは次のとおりです。
- コンストラクターで_initを呼び出して、単一のパーティクルをランダムに生成し、それを配列オブジェクトに配置します。
- インスタンスの実行が実行されると、タイマーが設定され、タイマーコールバックが_drawを呼び出してパーティクルを描画し、次のステップで個々のパーティクルのプロパティを設定します。
- そして、_initと_drawは効果に応じてサブクラスによって実装されます
上記の説明によると、プロセス全体は非常に明確です。
//同级的effect.js文件
// 两个状态
const STATUS_STOP = 'stop'
const STATUS_RUNNING = 'running'
//“基”类-这里就直接当“下雨”类了
class Particle {
constructor(ctx, width, height, opts) {
this._timer = null
this._options = opts || {
}
// canvas 上下文
this.ctx = ctx
this.status = STATUS_STOP
this.w = width
this.h = height
this._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_STOP
clearInterval(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()
に従って、で、生成される粒子の数に応じて、ランダムに生成された各粒子循環量を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.xs
s.y += s.ys
// 超出范围,重新回收,重复利用
if (s.x > w || s.y > h) {
s.x = Math.random() * w
s.y = -10
}
}
一見、我々はそれを見つけるだろう、通話の別の名前を除いて、しないようだと何ら変わりキャンバスAPIで1ネイティブJS 。
上記は「雨」を制御するためのものです。実際、雪が降る例のクラスと雨の唯一の違いは「粒子の形状」です。
class Snow extends Particle {
_init() {
let {
w, h} = this
let colors = this._options._colors || ['#ccc', '#eee', '#fff', '#ddd']
// 雪的大小用数量来计算
let amount = this._options.amount || 100
let speedFactor = this._options.speedFactor || 0.03
// 速度
let speed = speedFactor * h * 0.15
let radius = this._options.radius || 2
let ps = (this.particles = [])
for (let i = 0; i < amount; i++) {
let x = Math.random() * w
let y = Math.random() * h
ps.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.particles
let ctx = this.ctx
ctx.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} = this
let v = this._options.speedFactor / 10
for (let ps = this.particles, i = 0; i < ps.length; i++) {
let p = ps[i]
let {
ox, ys} = p
p.rs += v
// 这里使用了 cos,做成随机左右摆动的效果
p.x = ox + Math.cos(p.rs) * w / 2
p.y += ys
// console.log(ys)
// 重复利用
if (p.x > w || p.y > h) {
p.x = Math.random() * w
p.y = -10
}
}
}
}
パーティクルシステムを使用する
まず、WXMLコードで、効果のIDを持つCanvasコンポーネントをリアルタイム天気モジュールに追加します。
<canvas canvas-id="effect" id="effect"></canvas>
次に、上記のjsファイルを紹介します。
import Particle from './effect'
let Rain=Particle.Particle
重要: WeChatアプレットでは、描画API(Canvas)の長さと幅の単位はpxであり、ページレイアウトはrpxを使用します。CSSでCanvasのサイズを設定するためにrpxを使用しましたが、これは内部単位によるものです。 Rain / Snowパーティクルシステムをインスタンス化する場合、渡される幅と高さのパラメータは実際のピクセルサイズである必要があります。
rpxからpxは、さまざまなデバイスの画面サイズに応じて変換されます。カット画像は1rpx = 2pxなどの標準のiPhone6ビジュアルドラフトに従って作成でき、WeChatは互換性のある処理を行うのに役立つようですが、実際のpx計算に関しては、単純に使用することはできません。それを解決するには1rpx = 2pxに従う必要があります。実際のrpxはpxの比率に変換されます。rpxとpxの実際の比率を取得するにはどうすればよいですか?小さなマイクロチャネルプログラムがデフォルトの画面幅750rpxを指定していることがわかっています。この設計によればwx.getSystemInfo
、情報を取得し、電話の画面幅のwindowWidth
比率のサイズを計算できます。対応するコードは次のとおりです。
// 在 onload 内
wx.getSystemInfo({
success: (res) => {
let width = res.windowWidth
this.setData({
width,
scale: width / 375
})
}
})
したがって、上記の幅は、画面の実際の幅px、元素 rpx 高度 / 2 * scale
得られた各要素のpx実際の高さです。
最後に、ページコードでは、実際に使用されているコードは次のとおりです。
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()
興味のある友達はこの記事を参照できます:HTML5キャンバスの基本と「名刺生成」アプリケーション ↩︎