Mini programa WeChat | lienzo agrega efectos de lluvia y nieve a su pronóstico del tiempo

En el desarrollo de Canvas, a menudo se menciona el sistema de partículas, que se puede utilizar para simular efectos visuales abstractos como fuego, niebla, nubes y nieve. Es una buena práctica agregar efectos diversificados a la página recién agregada por HTML5.

Dio la casualidad de que un applet de WeChat meteorológico que hice recientemente llegó a su fin, pensando en lo que parece faltar en la página de inicio donde se muestra información como la temperatura. Eché un vistazo al pronóstico del tiempo que viene con Android y de repente me di cuenta. Entonces, combinado con el lienzo que estudié hace un tiempo, le agregué partículas de lluvia y nieve.


API "Dibujo" en Mini Programa

Aunque la API de dibujo del applet también se llama lienzo, es esencialmente diferente del Canvas de HTML5. La razón es que el dibujo del applet (Canvas) es un componente de IU nativo implementado por el cliente, no un componente H5 normal. por lo que el uso es ligeramente diferente al de los componentes H5 ordinarios.

Las implementaciones de IOS y Android no son iguales, se dice que se basa en Ejecta .

Se refleja principalmente de las siguientes formas:

  1. Diferentes métodos de adquisición de contexto

El dibujo de la API del subprograma canvasContext se adquiere mediante <canvas>un id de lienzo para obtener, a saber:

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

Aquí hay un punto: ¡no es lo mismo que "obtener elementos"!

  1. La escritura de API es diferente

La API de dibujo del antiguo applet es diferente de la mayoría de la escritura de atributos de HTML5 Canvas en uso. Tiene su propia escritura de applet, por ejemplo:

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

Sin embargo, vale la pena mencionar que por encima de la biblioteca básica 1.9.0, cosas como fillStyle y lineWidth se pueden escribir directamente de la misma manera que H5, y no es necesario usar setXxxx.

  1. El dibujo quiere mostrar resultados, necesita ctx.draw()usar

Pequeño programa de dibujo, luego dibuja en el contexto, y no se dibuja inmediatamente en el lienzo, sino realizando ctx.draw()la forma, en el contexto de dibujo descrito anteriormente (ruta, deformación, patrón) dibujado en el lienzo. ¡Pero el ctx.draw()método es relativamente un rendimiento de consumo y no se recomiendan múltiples llamadas en un ciclo de extracción!


Diseño de sistema de partículas

En este pequeño programa, el autor usa el sistema de partículas para hacer efectos de lluvia y nieve, usando la notación de clase es6: el sistema de partículas está compuesto por clases base y subclases. De partículas es la clase base, define las subclases de manera uniforme, tales como run(), stop(), clear()y similares. La clase base es responsable del mantenimiento de todo el ciclo y flujo de animación del sistema de partículas, y la subclase es responsable de los efectos de partículas específicos implementados. Por ejemplo, el efecto de lluvia y nieve lo implementa la subclase, mientras que el cambio y el público El flujo de procesamiento de lluvia y nieve está controlado por la clase base.

La clase base consta de los siguientes métodos:

  • _init (): el método que se ejecutará primero en la instanciación; vacío, que se implementa mediante subclases
  • _draw (): el método utilizado para dibujar imágenes en cada ciclo de animación; vacío, que es implementado por subclases
  • ejecutar: configure el temporizador, ejecute _draw () regularmente para realizar el ciclo de animación
  • detener: detener la animación
  • claro: detiene la animación y limpia el tablero de dibujo

El proceso general es el siguiente:

  • Llame a _init en el constructor para generar aleatoriamente una sola partícula y colocarla en un objeto de matriz;
  • Cuando se ejecuta la ejecución de la instancia, se establece el temporizador y la devolución de llamada del temporizador llama a _draw para dibujar las partículas y establecer las propiedades de las partículas individuales en el siguiente paso;
  • Y _init y _draw se implementan por subclases de acuerdo con el efecto

Según la explicación anterior, todo el proceso es muy claro:

//同级的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

De acuerdo con el mensaje anterior, en _init()el, de acuerdo con el número de partículas que se generarán, se generó aleatoriamente cada cantidad de circulación de partículas, en this.particlesuna matriz:

// _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)
}

entre ellos:

  • xey representan la posición de una sola partícula, es decir, la posición donde la gota de lluvia comienza a dibujarse
  • xs e ys representan la aceleración en las direcciones xey, es decir, la velocidad de caída y el ángulo de las gotas de lluvia
  • l representa la longitud de la gota de lluvia

Y _draw()el método, el lienzo se vacía primero y luego atraviesa this.particlesla matriz y elimina el dibujo de gotas de lluvia individuales, una llamada separada para lograr la _update()posición final de las gotas de lluvia individuales recalculadas:

// _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()

Y lo _update()que tenemos que hacer es determinar "la próxima vez que una gota de lluvia en cada posición" y "exceda el" "rango" Canvas:

// _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
  }
}

lona de lluvia

De un vistazo, encontraremos que, excepto por los diferentes nombres de las llamadas, no parece ser diferente de la API de lienzo 1 en js nativo .

Lo anterior es para controlar la "lluvia". De hecho, la única diferencia entre la clase de ejemplo nevando y la lluvia es la "forma de las partículas":

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
      }
    }
  }
}

Utilizar sistema de partículas

Primero, en el código WXML, agregue un componente Canvas con una identificación de efecto al módulo meteorológico en tiempo real:

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

Luego introduzca el archivo js anterior:

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

Importante: En el subprograma WeChat, la unidad de longitud y ancho en la API de dibujo (Canvas) es px, y nuestro diseño de página usa rpx. Aunque hemos usado rpx para establecer el tamaño del Canvas en CSS, se debe a las unidades internas. Al crear una instancia del sistema de partículas Lluvia / Nieve, los parámetros de ancho y alto pasados ​​deben ser el tamaño real en px.

rpx a px se convierte de acuerdo con los diferentes tamaños de pantalla del dispositivo. Aunque la imagen cortada se puede hacer de acuerdo con el borrador visual estándar del iPhone 6, como 1rpx = 2px, y WeChat parece ayudarnos a hacer el procesamiento compatible, pero cuando se trata del cálculo de px real, todavía no podemos simplemente usar 1rpx = 2px para resolverlo Necesitamos seguir El rpx real se convierte a la proporción de px. ¿Cómo obtener la proporción real de rpx y px? Sabemos que el pequeño programa de micro-canales especifica el ancho de pantalla predeterminado 750rpx, de acuerdo con este diseño, podemos wx.getSystemInfoacceder a la información, encontrar el tamaño de la windowWidthrelación de ancho de pantalla del teléfono se puede calcular el código correspondiente es el siguiente:

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

Por lo tanto, el ancho anterior es el ancho real de la pantalla px, px altura real de cada elemento 元素 rpx 高度 / 2 * scaleobtenido.

Finalmente, en el código de la página, el código en uso real es el siguiente:

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()

Llame para eliminar el efecto al cambiar de ciudad o al detectar que no llueve ni nieva:

rain.clear()

  1. Los amigos interesados ​​pueden consultar este artículo: Conceptos básicos del lienzo HTML5 y la aplicación "generación de tarjetas de visita" ↩︎

Supongo que te gusta

Origin blog.csdn.net/qq_43624878/article/details/110059776
Recomendado
Clasificación