Taro开发小程序记录-海报生成
Taro开发小程序记录-海报生成
在公司开发的第一个项目是一个简单的Taro小程序项目,主要应用的则是微信小程序,由于是第一次使用Taro,所以在开发过程中遇到的一些问题和一些开发技巧,记录一下,方便以后进行查看,也与大家进行分享。
自定义海报
说到自定义海报可以说是很多小程序中都会进行开发的内容,比如需要进行二维码的保存,然后再对二维码进行一点文字的修饰,涉及到这方面的时候我们就需要使用canvas了。
在实际开发的过程中,遇到了一些很坑的问题,当我们需要使用离屏canvas来进行绘制时,我们可能就会遇到问题(我自己就遇到了)。
对于安卓端,我们可以正常的使用OffscreenCanvas
来创建离屏canvas,然后绘制相关内容,最后在使用Taro.canvasToTempFilePath
方法保存到临时文件下,Taro.canvasToTempFilePath
方法会返回文件路径,我们就可以通过获取到的文件路径来进行下载。
下面是安卓端的一个🌰,大家有需要也可以直接拿去使用
- 需要使用到的方法
/**
* @description 获取二维码图像
*/
export const qrCodeImage = async (qrCodeValue: string, size: number = 128) => {
/* NOTE: 通过创建离屏canvas承载code */
const context = createOffscreenCanvas('2d', size, size);
QRCode.toCanvas(context, qrCodeValue, { width: size, height: size, margin: 1 });
return (context as unknown as HTMLCanvasElement).toDataURL();
};
/**
* @description 创建离屏canvas对象,width与height单位为px
*/
export const createOffscreenCanvas = (type: '2d' | 'webgl', width: number = 100, height: number = 100) => {
return Taro.createOffscreenCanvas({ type, width, height });
};
/**
* @description 将传入的图片url转换成一个ImageElement对象
*/
export const loadImageByUrlToCanvasImageData = async (url: string, width: number = 100, height: number = 100) => {
const context = createOffscreenCanvas('2d', width, height);
const imageElement = context.createImage();
await new Promise(resolve => {
imageElement.onload = resolve;
imageElement.src = url;
});
return imageElement;
};
/**
* @description 将canvas转成图片文件并保存在临时路径下
*/
export const changeCanvasToImageFileAndSaveToTempFilePath = async (options: Taro.canvasToTempFilePath.Option) => {
const successCallback = await Taro.canvasToTempFilePath(options);
return successCallback.tempFilePath;
};
interface SettingOptions {
title: string;
titleInfo: {
dx: number;
dy: number;
color?: string;
font?: string;
};
imageUrl: string;
imagePos: {
dx: number;
dy: number;
};
width: number;
height: number;
}
/**
* @description 获取二维码图像并设置标题
*/
export const generateQrCodeWithTitle = async (option: SettingOptions): Promise<string> => {
const {
title,
titleInfo,
imageUrl,
imagePos,
width,
height,
} = option;
const context = await createOffscreenCanvas('2d', width, height);
const ctx = (context.getContext('2d') as CanvasRenderingContext2D);
const imgElement: any = await loadImageByUrlToCanvasImageData(imageUrl, width, height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = titleInfo.color || 'black';
ctx.font = titleInfo.font || '';
ctx.textAlign = 'center';
ctx.fillText(title, titleInfo.dx || 0, titleInfo.dy || 0);
ctx.drawImage(imgElement, imagePos.dx, imagePos.dy);
const filePath = await changeCanvasToImageFileAndSaveToTempFilePath({
canvas: (context as Canvas),
width,
height,
fileType: 'png',
destWidth: width,
destHeight: height,
});
return filePath;
};
/**
* @description 保存图片
*/
export const saveImage = async (urls: string[], isLocal: boolean = true) => {
let filePath = urls;
if (!isLocal) {
filePath = await netImageToLocal(urls);
}
await Promise.all(filePath.map(path => {
return Taro.saveImageToPhotosAlbum({ filePath: path });
}));
return true;
};
/**
* @description 加载在线图片,并返回临时图片文件地址
*/
export const netImageToLocal = async (urls: string[]) => {
const res = await Promise.all(urls.map((url:string) => {
return Taro.downloadFile({ url });
}));
const result = res.map(data => {
if (data.statusCode === 200) {
return data.tempFilePath;
}
throw new Error(data.errMsg);
});
return result;
};
/**
* @description 判断用户是否授权保存图片
*/
export const checkHasAuthorizedSaveImagePermissions = async () => {
const setting = await Taro.getSetting();
const { authSetting } = setting;
return authSetting['scope.writePhotosAlbum'];
};
/**
* @description 下载图片,需要区分是本地图片还是在线图片
*/
export const downloadImage = async (urls: string[], isLocal: boolean = true) => {
const hasSaveImagePermissions = await checkHasAuthorizedSaveImagePermissions();
if (hasSaveImagePermissions === undefined) {
// NOTE: 用户未授权情况下,进行用户授权,允许保存图片
await Taro.authorize({ scope: 'scope.writePhotosAlbum' });
return await saveImage(urls, isLocal);
} else if (typeof hasSaveImagePermissions === 'boolean' && !hasSaveImagePermissions) {
return new Promise((resolve, reject) => {
Taro.showModal({
title: '是否授权保存到相册',
content: '需要获取您的保存图片权限,请确认授权,否则图片将无法保存到相册',
success: (result) => {
if (result.confirm) {
Taro.openSetting({
success: async (data) => {
if (data.authSetting['scope.writePhotosAlbum']) {
showLoadingModal('正在保存...');
resolve(await saveImage(urls, isLocal));
}
},
});
} else {
reject(new Error('未授予保存权限'));
}
},
});
});
}
await saveImage(urls, isLocal);
return true;
};
生成海报(二维码+标题头)
/**
* @description 获取二维码图像并设置标题
*/
export const generateQrCodeWithTitle = async (option: SettingOptions): Promise<string> => {
const {
title,
titleInfo,
imageUrl,
imagePos,
width,
height,
} = option;
const context = await createOffscreenCanvas('2d', width, height);
const ctx = (context.getContext('2d') as CanvasRenderingContext2D);
const imgElement: any = await loadImageByUrlToCanvasImageData(imageUrl, width, height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = titleInfo.color || 'black';
ctx.font = titleInfo.font || '';
ctx.textAlign = 'center';
ctx.fillText(title, titleInfo.dx || 0, titleInfo.dy || 0);
ctx.drawImage(imgElement, imagePos.dx, imagePos.dy);
const filePath = await changeCanvasToImageFileAndSaveToTempFilePath({
canvas: (context as Canvas),
width,
height,
fileType: 'png',
destWidth: width,
destHeight: height,
});
return filePath;
};
具体使用
export const saveQrCodeImageWithTitle = async () => {
const url = await qrCodeImage(enterAiyongShopUrl(), 160);
const imgUrl: string = await generateQrCodeWithTitle({
title: 'adsionli菜鸡前端',
titleInfo: {
dx: 95,
dy: 20,
font: '600 14px PingFang SC',
color: 'black',
},
imageUrl: url,
imagePos: {
dx: 15,
dy: 34,
},
width: 190,
height: 204,
});
await downloadImage([imgUrl]);
}
上面三块内容就可以组成我们的海报生成了,这里面的主要步骤不是很难,包括了几个方面:
- 用户授权鉴定,主要是是否允许保存,这里做了一点处理,就是可以在用户第一次授权不允许时,进行二次授权调起,这个可以看一下上面的
downloadImage
这个函数,以及用于判断用户是否授权的checkHasAuthorizedSaveImagePermissions
这个函数 - 创建
OffscreenCanvas
并进行绘制,这里其实没有太多的难点,主要就是需要知道,如果我们使用image的内容的话,或者是一个图片的url
时,我们需要先将其绘制到一个canvas
上(这里可以获取imageElement
对象,也可以直接使用canvas
),这样方便我们后面进行drawImage
时进行使用 - 图片保存,这里也有一个需要注意的点,如果图片(或二维码)是网络图片的话,我们需要处理以下,先将其转成本地图片,也就是通过
netImageToLocal
这个方法,然后再还给对应的将图片画在canvas
上的方法。最后的保存很简单,我们可以直接使用Taro.canvasToTempFilePath
这个方法转到临时地址,再通过downloadImage
就可以搞定了。
感觉好像很麻烦,其实就四步:图片加载转化—>canvas
绘制—>用户鉴权—>图片保存。
安卓端实现起来还是很简单的,但是这些方法对于ios端就出现了问题,如果按照上面的路线进行海报绘制保存的话,在ios端就会报一个错误(在本地开发的时候并不会抛出): canvasToTempFilePath:fail invalid viewId
这一步错误就是发生在Taro.canvasToTempFilePath
这里,保存到临时文件时会触发,然后这一切的原因就是使用了OffscreenCanvas
离屏canvas造成的。
所以为了能够兼容ios端的这个问题,有了以下的修改:
首先需要在我们要下载海报的pages中,添加一个Canvas
,帮助我们可以获取CanvasElement
<Canvas
type='2d'
id='qrCodeOut'
className='aiyong-shop__qrCode'
/>
这里需要注意一下,我们需要添加一个type='2d'
的属性,这是为了能够使用官方提供的获取Canvas2dContext
的属性,这样就可以不使用createCanvasContext
这个方法来获取了(毕竟已经被官方停止维护了)。
然后我们就可以获取一下CanvasElement
对象了
/**
* @description 获取canvas标签对象
*/
export const getCanvasElement = (canvasId: string): Promise<Taro.NodesRef> => {
return new Promise(resolve => {
const canvasSelect: Taro.NodesRef = selectQuery().select(`#${canvasId}`);
canvasSelect.node().exec((res: Taro.NodesRef) => {
resolve(res);
});
});
};
注:这里又有一个小坑,我们在获取CanvasElement
之后,如果直接进行绘制的话,这里存在一个问题,就是这个CanvasElement
的width:300、height:150
被限制死了,所以我们需要自己在拿到CanvasElement
之后,在设置一下width、height
。
const canvasNodeRef = await getCanvasElement(canvas);
let context;
if (canvasNodeRef && canvasNodeRef[0].node !== null) {
context = canvasNodeRef[0].node;
(context as Taro.Canvas).width = width;
(context as Taro.Canvas).height = height;
}
好了,改造完成,这样就可以兼容ios端的内容了,实际我们只需要修改generateQrCodeWithTitle
这个方法和page新增Canvas
用于获取CanvasElement
就可以了,其他可以不要动。修改后的generateQrCodeWithTitle
方法如下:
/**
* @description 获取二维码图像并设置标题
*/
export const generateQrCodeWithTitle = async (option: SettingOptions): Promise<string> => {
const {
title,
titleInfo,
imageUrl,
imagePos,
width,
height,
qrCodeSize,
canvas,
} = option;
const canvasNodeRef = await getCanvasElement(canvas);
let context;
if (canvasNodeRef && canvasNodeRef[0].node !== null) {
context = canvasNodeRef[0].node;
(context as Taro.Canvas).width = width;
(context as Taro.Canvas).height = height;
}
const ctx = (context.getContext('2d') as CanvasRenderingContext2D);
const imgElement: Taro.Image = await loadImageByUrlToCanvasImageData(imageUrl, qrCodeSize.width, qrCodeSize.height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = titleInfo.color || 'black';
ctx.font = titleInfo.font || '';
ctx.textAlign = 'center';
ctx.fillText(title, titleInfo.dx || 0, titleInfo.dy || 0);
ctx.drawImage((imgElement as HTMLImageElement), imagePos.dx, imagePos.dy, imgElement.width, qrCodeSize.height);
const filePath = await changeCanvasToImageFileAndSaveToTempFilePath({
canvas: (context as Canvas),
width,
height,
fileType: 'png',
destWidth: width,
destHeight: height,
});
return filePath;
};
如果大家不想让海报被人看到,那可以设置一下css
.qrCode {
position: fixed;
left: 100%;
}这样就可以啦
突然发现内容可能有点多了,所以打算分成两篇进行Taro使用过程中的总结,开发完之后进行总结,总是可以让自己回顾在开发过程中遇到的问题的进一步进行思考,这是一个很好的进步过程,加油加油!!!
链接:https://juejin.cn/post/7244452419458498616
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。