小程序使用lottie-miniprogram和canvas把rpx转px从而实现适配多端的动画
- 使用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
- canvas仅支持px,所以使用小程序物理像素进行转成px,
getContext方法返回一个canvas和contextpromise对象(这里的命名不太准确),getDraw主要用来画图的跟使用lottie-miniprogram动画没有影响,需要canvas画动画的可以使用
尺寸单位
rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
| 设备 | rpx换算px (屏幕宽度/750) | px换算rpx (750/屏幕宽度) |
|---|---|---|
| iPhone5 | 1rpx = 0.42px | 1px = 2.34rpx |
| iPhone6 | 1rpx = 0.5px | 1px = 2rpx |
| iPhone6 Plus | 1rpx = 0.552px | 1px = 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
};
-
结合使用 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
})
})
-
使用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)
如果有问题请,请私信我哦!!! 🙃🙃🙃