Taro开发小程序记载-海报生成
文档创立人 | 创立日期 | 文档内容 | 更新时刻 |
---|---|---|---|
adsionli | 2023-06-14 | Taro小程序开发相关记载-海报生成 | 2023-06-14 |
在公司开发的第一个项目是一个简略的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运用过程中的总结,开发完之后进行总结,总是能够让自己回顾在开发过程中遇到的问题的进一步进行思考,这是一个很好的前进过程,加油加油!!!