一个小程序生成海报的问题
问题背景:
最近还是在做基于ts taro的小程序开发,有个前端生成海报的需求。本来想着这个需求很简单,因为之前写过这个功能,基本的逻辑就是产品图片地址和小程序码图片地址下载到本地,然后通过createCanvasContext()
这个方法拿到canvas
的上下文,进行绘制即可。
而且代码也是现成的,想着直接移植一下不就行了吗,但是整整花了一天,也没整明白。
原先的生成海报代码
原先的项目是用taro redux结合taro-ui进行开发,没有复杂的类型和封装。
且整体版本较高。
"@tarojs/taro": "3.2.1",
"@tarojs/components": "3.2.1",
"taro-ui": "^3.0.0-alpha.4"
海报组件的代码如下:
代码语言:javascript复制// 生成海报
import React, { useState, useEffect } from 'react';
import Taro from '@tarojs/taro';
import { View, Image, Canvas } from '@tarojs/components'
import './index.scss'
export default ({ ...props }) => {
const { posterUrl, qrCodeUrl, title = '分享海报', desc = '作者张超', remark = '', className } = props
const [combinedImg, setCombinedImg] = useState('');
const [localImgs, setLocalImgs] = useState({})
// 绘制之前下载道本地
const transToLocal = async (url) => {
return Taro.downloadFile({
url: url, //图片url
success: function (res) {
console.log(res);
},
error: (err) => {
console.log(err)
}
})
}
const saveToLocal = async (imgUrl, type) => {
let { tempFilePath } = await transToLocal(imgUrl)
// let obj = {}
localImgs[type] = tempFilePath
setLocalImgs(localImgs)
console.log('localImgs---', localImgs)
if (Object.keys(localImgs).length > 1) {
genCompositeImg(localImgs)
}
}
const genCompositeImg = (obj) => {
console.log('obj---', obj)
const ctx = Taro.createCanvasContext('cvs');
// 背景白色
ctx.setStrokeStyle('#fff');
ctx.setLineWidth(10);
// 750 1336
ctx.rect(0, 0, 750, 1336);
ctx.setFillStyle('#fff');
ctx.fill();
ctx.stroke();
// 参考写 标题设置
ctx.restore()
ctx.setTextBaseline('top');
ctx.setFillStyle('rgba(51,51,51,1)');
ctx.setFontSize(32);
ctx.fillText(title, 32, 1169);
// 参考写 推广语
ctx.restore()
ctx.setTextBaseline('top');
ctx.setFillStyle('rgba(153,153,153,1)');
ctx.setFontSize(24);
if (desc.length <= 20) {
ctx.fillText(desc, 32, 1218);
} else if (desc.length <= 40) {
let context1 = desc.slice(0, 20);
let context2 = desc.slice(20, desc.length);
ctx.fillText(context1, 32, 1218);
ctx.fillText(context2, 32, 1251);
} else {
let context1 = desc.slice(0, 20);
let context2 = desc.slice(20, 39) '...';
ctx.fillText(context1, 32, 1218);
ctx.fillText(context2, 32, 1251);
}
// 背景图
ctx.drawImage(obj.poster, 0, 0, 750, 1120);
// 二维码
ctx.drawImage(obj.qrcode, 570, 1152, 148, 148);
// 绘制
ctx.save();
ctx.draw(true, function (e) {
Taro.canvasToTempFilePath({
x: 0,
y: 0,
width: 750,
height: 1336,
destWidth: 750,
destHeight: 1336,
canvasId: 'cvs',
success(res) {
setCombinedImg(res.tempFilePath)
props.genCompositeImgSucc(res.tempFilePath);
// Taro.hideLoading();
}
})
});
}
useEffect(() => {
if (posterUrl && qrCodeUrl) {
saveToLocal(posterUrl, 'poster')
saveToLocal(qrCodeUrl, 'qrcode')
}
}, [posterUrl, qrCodeUrl])
return (
<View className={className ? className ' ' 'canvas-wrap' : 'canvas-wrap'}>
<View className='canvas-wrap-cvsbox'>
<Canvas style='width: 750px; height: 1336px;' canvasId='cvs' />
</View>
<View className='cvsDoneWrap'>
<Image className='cvsDoneWrap-img' mode='widthFix' src={combinedImg} />
</View>
</View>
);
}
这个组件在原有的项目中正常运行。
现在的项目
现在的项目整体是基于taro2.x结合ts的架构。
"@tarojs/cli": "2.2.1",
"@tarojs/components": "2.2.1",
"@tarojs/mobx": "2.2.1",
页面的大体逻辑是实现了 taro/mobx
中的 ITaroComponent
的类型。
然后自己实现了一个viewModal
层,用来做数据的处理和传递。
/**
* 基础model返回值
*/
export default interface CreateOptions<P, S> {
useCallBackState: (defaultState: S) => [S, (state: Partial<S>, callback?: (state: S) => void) => void],
useInitialize: (hook: () => void) => void,
props?: P
}
按照正常的逻辑,原先生成海报能够正常执行,移植过来问题也应该不大。
但是最初遇到的问题是 ctx.draw()
回调不执行。
去掉 ctx.draw()
,直接导出canvas图片地址:
Taro.canvasToTempFilePath({
x: 0,
y: 0,
width: 750,
height: 1336,
destWidth: 750,
destHeight: 1336,
canvasId: 'myCanvas',
success(res) {
setCombinedImg(res.tempFilePath)
props.genCompositeImgSucc(res.tempFilePath);
// Taro.hideLoading();
}
})
报错:
代码语言:javascript复制{errMsg: "canvasToTempFilePath: fail canvas is empty"}
canvas根本没有绘制相关的内容。
问题出在哪呢?
代码中获取上下文本,使用的是:
代码语言:javascript复制const ctx = Taro.createCanvasContext('cvs');
看了文档,wx.createCanvasContext(string canvasId, Object this)
从基础库 2.9.0 开始,改接口已经停止维护,文档让用 Canvas 进行代替。
好吧,换成Canvas,用Canvas.getContext(string contextType)
获取 Canvas 的绘图上下文。
需要用到查找dom节点的API。
代码语言:javascript复制Taro.createSelectorQuery().in(useScope())
.select('#myCanvas').fields({
node: true,
size: true,
}).exec(async (res) => {
console.log('res====>', res)
let canInstance = res[0].node
let canvasCtx = res[0].node.getContext('2d')
})
然后重新进行绘制。
发现相关的API差异比较大。
比如drawImage(imageResource)
,原先第一个参数,文档上说:
CanvasContext.drawImage(string imageResource, number sx, number sy, number sWidth, number sHeight, number dx, number dy, number dWidth, number dHeight)。 string imageResource 所要绘制的图片资源(网络图片要通过 getImageInfo / downloadFile 先下载)
此时的imageResource
需要是个image对象。
const img = canvas.createImage()
img.scr = "url"
img.onload = () {
canvasCtx.drawImage(img,0,0)
}
这样才能勉强将图片绘制到画布上。
然而,事情并没有结束。
用canvas.createImage()
创建的图片对象,img的onload方法会不停的重复执行,似乎进入了一个死循环。
最终还是暂时放弃前端生成海报,暂时由服务端去合成。
很奇怪的问题,使用wx.createCanvasContext()
,绘制方法不起作用。
使用Canvas 2D
,drawImage()又陷入死循环。
但是以前的项目又一切正常,这给我整的有点不会了。
不甘心的我又翻了翻github上相关的issue。有一个最接近的问题是:
CanvasRenderingContext2D 对象通过canvas.getContext("2d")创建出来,无法使用drawImage方法绘制图片 #5881
但是目前没有解决方法。
感觉问题应该还是出在系统或者版本上,API更新了,文档没更新。或者canvas相关的API没有完全和HTML Canvas 2D Context
同步。