Taro小程序生成分享海报解决方案

37 阅读3分钟

场景:在Taro开发中,在商品/职位/文章的详情页需要转发生成一个png海报,如果在Web开发中,直接html+css写海报样式,Vue/Teact中通过Props传入详情信息就可以,然后定位到屏幕视口外,通过html2canvas生成和导出海报样式,但是在小程序中,无法使用html2canvas

尝试寻找第三方库

wxml2canvas/taro-wxml2canvas可以吗,我使用过,证明是不可行的,且不说taro导入第三方原生组件十分麻烦,在Taro4中,和Taro3,Taro2基本经历过多次更新,这些库已经很久没有维护,实测不可行。

taro-plugin-canvas和taro3-canvas之类的库长时间不更新无法使用;

taro-html-parser和wxParse库也是长时间不更新无法使用;

mp-html这个还在维护库只支持uniapp和原生小程序;

似乎Taro被市场抛弃了,一个纯前端解决生成分享海报的库都没有吗?

尝试Canvas绘制

其实海报就是一个png图片,canvas一样可以绘制,Taro是支持canvas的,但canvas绘制的海报在多文字处理上非常麻烦,样式调整费劲,其实先写html,让AI转为canvas代码,也是还原度低,对于复杂海报样式很难实现,更难二次调整;

尝试SVG绘制

SVG是在浏览器上可行的,UI设计师出稿一个SVG模板,详情页信息(文字/图片)直接拼接/导出到SVG中,生成海报图片,但是小程序不支持SVG,所以依然不可行;

最终解决方案

使用Taro原生的Snapshot组件,只需要写原生的Taro代码,会被截图为画布,渲染结果导出成图片,需要局部开启Skyline模式,但是总算是实现了浏览器中Html2Canvas的效果,参考文档 docs.taro.zone/docs/apis/s…

参考代码

建议在详情页点击分享->生成分享海报按钮时,跳转该页面,生成完成直接开启分享菜单,结束后跳转回详情页。因为skyline模式的无法使用第三方UI组件,建议单独独立为一个局部页面,影响最小化。

sharePoster.tsx

import { Image, View, Text, Snapshot } from '@tarojs/components';
import Taro, { useLoad } from '@tarojs/taro';
import { useState } from 'react';

export default function SharePosterPage() {
  const { t } = useTranslation();
  const app = Taro.getApp();
  const [positionInfo, setPositionInfo] = useState<PositionItem>(
    {} as PositionItem,
  );
  const [miniAppCode, setMiniAppCode] = useState('');

  useLoad(async () => {
    // 通过路由传参
    const globalPositionInfo = app.globalData.globalPositionInfo;
    setPositionInfo(globalPositionInfo);
    
    //获取小程序码/图片等,微信分享不允许二维码,使用小程序代替。
  });

  // 截图生成分享海报
  const generateSnapshot = async (): Promise<string> => {
    Taro.showLoading({ title: t('share.generating') });
    const query = Taro.createSelectorQuery();
    // 获取截图节点
    //eslint-disable-next-line @typescript-eslint/no-explicit-any
    const snapshot = await new Promise<any>((resolve) => {
      query
        .select('#mySnapshot')
        .fields({ node: true, size: true })
        .exec((res) => {
          if (!res[0]) {
            return;
          }
          resolve(res[0].node);
        });
    });

    // 截图 -> 临时图片路径
    const tempFilePath = await new Promise<string>((resolve, reject) => {
      // 仅支持weapp,需开启Skyline
      snapshot.takeSnapshot({
        format: 'png',
        success: (res) => resolve(res.tempFilePath),
        fail: (err) => {
          Taro.showToast({ title: t('share.generationFailed'), icon: 'none' });
          reject(err);
        },
      });
    });

    Taro.hideLoading();
    return tempFilePath;
  };

  // 生成海报 & 转发
  const generateAndShare = async () => {
    try {
      const generatedImageUrl = await generateSnapshot();
      await Taro.showShareImageMenu({
        path: generatedImageUrl,
        entrancePath: `/pages/position-detail/index?id=${positionInfo.id}`,
      });
      Taro.showToast({ title: t('share.shareSuccess'), icon: 'success' });
    } catch (err) {
      // handleError
    } finally {
      // 分享完成跳转回去
      Taro.navigateBack();
    }
  };

  return (
    // 不能使用nutui组件,Skyline 渲染引擎 对 CSS 的支持有限制
    <View className="snapshot-wrapper">
      <Snapshot id="mySnapshot" mode="view">
       <!-- 这里直接写原生的View代码 -->
      </Snapshot>
    </View>
  );
}

需要在专门的分享页开启skyline

sharePoster.config.ts

export default definePageConfig({
  navigationBarTitleText: '生成分享海报',
  renderer: 'skyline',
  // glass-easel 组件框架,配合使用 Skyline 组件
  componentFramework: 'glass-easel',
  // 自定义导航栏,配合使用 Skyline 组件
  navigationStyle: 'custom',
});