小程序使用lottie-miniprogram和canvas把rpx转px从而实现适配多端的动画

1,466 阅读3分钟
小程序使用lottie-miniprogram和canvas把rpx转px从而实现适配多端的动画
  1. 使用lottie-miniprogram封装一个简单的类库
import lottie from 'lottie-miniprogram'
class Lottie{
  canvas = null;
  context = null;
  //渲染出来的是什么格式
  renderer = 'svg'; 
  //是否循环播放(选填)
  loop = true;
  //是否自动播放(选填)
  autoplay = true;
  //lottie json包的网络链接,可以防止小程序的体积过大,要注意请求域名要添加到小程序的合法域名中
  animationData= null;

  /**
   * @param {*} canvas 画布
   * @param {*} context  获取的canvas对象 canvas.getContext('2d');
   * @param {*} options 其他
   */
  constructor(canvas, context, options={}){
    this.canvas = canvas;
    this.context = context;
    this.options = Object.assign({
      renderer: this.renderer, 
      loop: this.loop,
      autoplay: this.autoplay,
      animationData:  this.animationData,
      rendererSettings:{
        context: this.context
      }       
    },options);
    this.handleAnimation();
  }

  /**
   * 执行动画
   */
  handleAnimation(){
    lottie.setup(this.canvas);//要执行动画,必须调用setup,传入canvas对象
    lottie.loadAnimation(this.options)
  }

  /**
   * 优化  
   * 如果参数这一次和上一次没有发现变化 则不需要重新渲染动画
   */
  static getInstance(canvas, context, options={}) {
    if(this.tmpOptions != JSON.stringify(options)){
      this.instance = new Lottie(canvas, context, options);
      console.log('重新渲染动画');
    }
    this.tmpOptions = JSON.stringify(options);
    return this.instance;
  }

}

function wxLottie(canvas, context, options={}){
 const o =  Lottie.getInstance(canvas, context, options)
  return o
}

export default wxLottie
  1. canvas仅支持px,所以使用小程序物理像素进行转成px,getContext方法返回一个 canvascontext promise对象(这里的命名不太准确),getDraw主要用来画图的跟使用lottie-miniprogram 动画没有影响,需要canvas画动画的可以使用
尺寸单位

rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

设备rpx换算px (屏幕宽度/750)px换算rpx (750/屏幕宽度)
iPhone51rpx = 0.42px1px = 2.34rpx
iPhone61rpx = 0.5px1px = 2rpx
iPhone6 Plus1rpx = 0.552px1px = 1.81rpx
class WxCanvas {
  info = null;
  dom = null;
  instance = null;
  options = null;
  canvas = null;
  context = null;
  rpxWidth = 750; //你的设计图相对应的rpx      //重要
  /**
   * 初始化参数
   * @param { String } dom
   * @param { Object| '' } instance page组件传 '' ,components 组件传this //重要
   */
  constructor(dom, instance, options = {}) {
    this.dom = dom;
    this.instance = instance;
    this.options = options;
    this.rpxWidth = options.rpxWidth ? options.rpxWidth : this.rpxWidth;
  }

  /**
   * 开始绘画
   */
  async draw() {
    const { canvas, context } = await obj.getNewContext();
    // 清除画布内容
    context.clearRect(0, 0, canvas.width, canvas.height);
    // 填充背景色
    context.fillStyle = this.options.backgroundColor;
    context.fillRect(0, 0, canvas.width, canvas.height);
    // 按配置画canvas
    await this._render(this.options.paints);
    // canvas转base64
    const url = await this.canvas.toDataURL(
      this.options.dataUrl.type,
      this.options.dataUrl.quality
    );
    return {
      url: url
    };
  }

  /**
   * 获取新画布信息
   * @param {*} dom dom节点
   * @param {*} instance  组件实例化
   */
  getNewContext() {
    let QuerySelector = wx.createSelectorQuery();
    this.instance && (QuerySelector = QuerySelector.in(this.instance));
    return new Promise((resolve) => {
      QuerySelector.select(this.dom)
        .fields({
          node: true,
          size: true,
        })
        .exec(this._getCanvas.bind(this, resolve));
    });
  }

  /**
   * 把小程序中的rpx转成真正的px
   * @param { Array } res  QuerySelector 的回调参数
   */
  _getCanvas(resolve, res) {    
    const width = res[0].width;
    const height = res[0].height;
    this.canvas = res[0].node;
    const systemInfo = wx.getSystemInfoSync();
    const dpr = systemInfo.pixelRatio;
    //设计稿是375相对应的750rpmx (1px = 2rpx)
    const ratio = this.rpxWidth / systemInfo.windowWidth;
    this.canvas.width = width * dpr;
    this.canvas.height = height * dpr;
    this.context = this.canvas.getContext('2d');
    this.context.scale(dpr / ratio, dpr / ratio);
    this.info = {
      canvas: this.canvas,
      context: this.context,
    };
    resolve(this.info);
  }

  /**
   * 作画
   * @param options 作画配置
   * @returns {Promise<void>}
   */
  async _render(options) {
    const way = {
      image: '_drawImage',
      text: '_drawText',
    };

    let i = 0;
    const j = options.length;

    while (i < j) {
      await this[way[options[i].type]](options[i]);
      i++;
    }
  }

  /**
   * 画圆角矩形
   * @param x 坐标x
   * @param y 坐标y
   * @param width 宽度
   * @param height 高度
   * @param radius 圆角度数
   */
  _drawRoundedRect(x, y, width, height, radius) {
    const context = this.context;

    context.moveTo(x, y + radius);
    context.beginPath();
    context.arc(x + radius, y + radius, radius, Math.PI, 1.5 * Math.PI);
    context.arc(x + width - radius, y + radius, radius, 1.5 * Math.PI, 2 * Math.PI);
    context.arc(x + width - radius, y + height - radius, radius, 0, 0.5 * Math.PI);
    context.arc(x + radius, y + height - radius, radius, 0.5 * Math.PI, Math.PI);
    context.closePath();
  }

  /**
   * 画换行文字
   * @param x 坐标x
   * @param y 坐标y
   * @param text 文本
   * @param length 一行的长度
   * @param size 字体大小
   */
  _drawWrapText(x, y, text, length, size) {
    const context = this.context;

    for (
      let i = 0, j = length;
      j < text.length + length;
      i += length, j += length
    ) {
      const section = text.slice(i, j);
      context.fillText(section, x, y + size);
      y += size;
    }
  }

  /**
   * canvas画图片(正常,圆形,圆角图片)
   * @param src 图片src
   * @param x 坐标x
   * @param y 坐标y
   * @param width 宽度
   * @param height 高度
   * @param round 是否圆形
   * @param radius 圆角弧度
   * @returns {Promise<unknown>}
   */
  _drawImage({ src, x, y, width, height, round, radius }) {
    const context = this.context;
    const canvas = this.canvas;

    return new Promise((resolve, reject) => {
      context.save();
      if (round) {
        context.beginPath();
        context.arc(
          width / 2 + x,
          height / 2 + y,
          width / 2,
          0,
          Math.PI * 2,
          false
        );
        context.clip();
      }
      if (radius) {
        context.beginPath();
        this._drawRoundedRect(x, y, width, height, radius);
        context.clip();
      }

      if (src.includes('http') && !src.includes('https')) {
        src = src.replace('http', 'https');
      }

      if (src.includes('https')) {
        wx.getImageInfo({
          src: src, //服务器返回的图片地址
          success: function (res) {
            //res.path是网络图片的本地地址
            let path = res.path;

            let img = canvas.createImage();
            img.src = path;
            img.onload = () => {
              context.drawImage(img, x, y, width, height);
              context.restore();
              resolve();
            };
          },
          fail: function (res) {
            //失败回调
          },
        });
        return;
      }

      let img = canvas.createImage();
      img.src = src;
      img.onload = () => {
        context.drawImage(img, x, y, width, height);
        context.restore();
        resolve();
      };
    });
  }

  /**
   * canvas画文字(正常,换行文字)
   * @param text 文本
   * @param x 坐标x
   * @param y 坐标y
   * @param color 文本颜色
   * @param size 字体大小
   * @param opacity 字体透明度
   * @param wrap 一行文字长度
   */
  _drawText({ text, x, y, color, size, opacity, wrap }) {
    const context = this.context;

    context.save();
    context.fillStyle = color;
    context.font = size + 'px sans-serif';
    opacity && (context.globalAlpha = opacity);
    wrap
      ? this._drawWrapText(x, y, text, wrap, size)
      : context.fillText(text, x, y);
    context.restore();
  }
}

/**
 * 返回一个promise 对象  {  canvas, context };
 * @param {*} dom dom对象
 * @param {*} instance  组建的实例化
 */
function getContext(dom, instance) {
  const obj = new WxCanvas(dom, instance);
  return new Promise((resolve) => {
    obj.getNewContext().then((res) => {
      resolve(res);
    });
  });
}

/**
 * 获取绘画结果
 */
function getDraw(dom, instance, options={}){
  const obj = new WxCanvas(dom, instance,options);
  return obj.draw()
}
export default getContext
export  {
  getContext,
  getDraw
};

  1. 结合使用 json动画

import wxCanvas from '../../../../utils/wx-canvas'
import wxLottie from '../../../../utils/wx-lottie'
import animationData from '你的json动画' // 记得转成js export出来

wx.nextTick(async () => { //这个不重要
    const { canvas, context} = await  wxCanvas("#canvas",this);
    wxLottie(canvas, context, {
        animationData: animationData
    })
 })
  1. 使用draw画canvas动画(补充)

import { getDraw } from "../../utils/wx-canvas"

const options = {
      backgroundColor: "#ffffff",
      paints: [
        {
          type: "image",
          src: userInfo.headimgurl,
          x: 42,
          y: 28,
          width: 64,
          height: 64,
          round: true,
        },
        {
          type: "text",
          text: "向你推荐",
          x: 124,
          y: 84,
          color: "#ffffff",
          size: 20,
          opacity: 0.8,
        },
        {
          type: "text",
          text: videoInfo.name,
          x: 110,
          y: 696,
          color: "#666666",
          size: 26,
        },
      ],
      dataUrl: {
        type: "image/jpeg",
        quality: 1.0,
      },
    };

 await getDraw("#poster", "", options)


如果有问题请,请私信我哦!!! 🙃🙃🙃