Taro开发小程序记载-海报生成

文档创立人 创立日期 文档内容 更新时刻
adsionli 2023-06-14 Taro小程序开发相关记载-海报生成 2023-06-14

在公司开发的第一个项目是一个简略的Taro小程序项目,首要运用的则是微信小程序,由所以第一次运用Taro,所以在开发过程中遇到的一些问题和一些开发技巧,记载一下,便利以后进行查看,也与咱们进行分享。

自定义海报

说到自定义海报能够说是许多小程序中都会进行开发的内容,比方需求进行二维码的保存,然后再对二维码进行一点文字的修饰,涉及到这方面的时分咱们就需求运用canvas了。

在实际开发的过程中,遇到了一些很坑的问题,当咱们需求运用离屏canvas来进行制作时,咱们或许就会遇到问题(我自己就遇到了)。

关于安卓端,咱们能够正常的运用OffscreenCanvas来创立离屏canvas,然后制作相关内容,最后在运用Taro.canvasToTempFilePath办法保存到暂时文件下,Taro.canvasToTempFilePath办法会返回文件途径,咱们就能够经过获取到的文件途径来进行下载。

下面是安卓端的一个,咱们有需求也能够直接拿去运用

  1. 需求运用到的办法
/**
 * @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;
};
  1. 生成海报(二维码+标题头)

    /**
     * @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;
    };
    
  2. 详细运用

    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]);
    }
    

上面三块内容就能够组成咱们的海报生成了,这儿面的首要步骤不是很难,包括了几个方面:

  1. 用户授权判定,首要是是否答应保存,这儿做了一点处理,便是能够在用户第一次授权不答应时,进行二次授权调起,这个能够看一下上面的downloadImage这个函数,以及用于判断用户是否授权的checkHasAuthorizedSaveImagePermissions这个函数
  2. 创立OffscreenCanvas并进行制作,这儿其实没有太多的难点,首要便是需求知道,假如咱们运用image的内容的话,或者是一个图片的url时,咱们需求先将其制作到一个canvas上(这儿能够获取imageElement目标,也能够直接运用canvas),这样便利咱们后边进行drawImage时进行运用
  3. 图片保存,这儿也有一个需求留意的点,假如图片(或二维码)是网络图片的话,咱们需求处理以下,先将其转成本地图片,也便是经过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之后,假如直接进行制作的话,这儿存在一个问题,便是这个CanvasElementwidth: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运用过程中的总结,开发完之后进行总结,总是能够让自己回顾在开发过程中遇到的问题的进一步进行思考,这是一个很好的前进过程,加油加油!!!