0
点赞
收藏
分享

微信扫一扫

第九十六期:一个小程序生成海报的问题

这里记录工作中遇到的技术点,以及自己对生活的一些思考,周三或周五发布。

封面图

第九十六期:一个小程序生成海报的问题_taro

一个小程序生成海报的问题

问题背景:

最近还是在做基于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"​

海报组件的代码如下:

// 生成海报
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();
}
})

报错:

{errMsg: "canvasToTempFilePath: fail canvas is empty"}

canvas根本没有绘制相关的内容。

问题出在哪呢?

代码中获取上下文本,使用的是:

const ctx = Taro.createCanvasContext('cvs');

看了文档,​​wx.createCanvasContext(string canvasId, Object this)​​从基础库 2.9.0 开始,改接口已经停止维护,文档让用 Canvas 进行代替。

好吧,换成Canvas,用​​Canvas.getContext(string contextType)​​获取 Canvas 的绘图上下文。

需要用到查找dom节点的API。

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 ​​同步。

最后

  • 公众号《JavaScript高级程序设计》
  • 公众号内回复”vue-router“ 或 ”router“即可收到 VueRouter源码分析的文档。
  • 回复”vuex“ 或 ”Vuex“即可收到 Vuex 源码分析的文档。

全文完,如果喜欢。

请点赞吧,最好也加个"关注",或者分享到朋友圈。

举报

相关推荐

0 条评论