UniApp 中使用 Canvas 生成分享海报的完整实现

在移动端应用中,分享海报是一种非常常见的营销和传播手段。用户可以将带有个人邀请码、商品信息或活动内容的图片保存到相册,再分享到微信、朋友圈等社交平台。在 UniApp 中,由于需要兼容 App、H5 以及各家小程序,使用 Canvas 绘制海报是最稳妥的方案。本文将完整讲解在 UniApp 中如何利用 Canvas 生成分享海报,包括布局思路、代码实现以及常见问题的解决方案。

一、为什么选择 Canvas 绘制海报

在 UniApp 中生成海报通常有两种思路:

  1. 服务端生成:由后端使用 Node.js、Java 等生成图片,前端只负责展示和保存。优点是兼容性好,缺点是依赖网络、响应慢、服务器压力大。
  2. 前端 Canvas 生成:利用 UniApp 提供的 uni.createCanvasContext API 在本地绘制。优点是速度快、不依赖网络、可离线使用,缺点是需要处理各端的兼容性问题。

对于大多数场景,前端 Canvas 方案已经足够,并且用户体验更好。下面我们以一个典型的“商品分享海报”为例进行实现。

二、整体实现思路

生成海报的流程可以概括为以下几步:

  1. 在页面中放置一个 <canvas> 组件,设置好宽高(注意使用 rpxpx 的转换)。
  2. 使用 uni.createCanvasContext 获取绘图上下文。
  3. 依次绘制背景、图片、文字、二维码等内容。
  4. 调用 ctx.draw() 完成绘制。
  5. 使用 uni.canvasToTempFilePath 将 Canvas 导出为临时图片路径。
  6. 通过 uni.saveImageToPhotosAlbum 保存到相册,或通过 uni.previewImage 预览。

三、页面结构与基础样式

首先在页面中定义 Canvas 组件。需要注意的是,Canvas 的宽高建议使用 px 单位,并通过 uni.getSystemInfoSync() 获取屏幕宽度进行适配。

<template>
  <view class="poster-page">
    <canvas
      canvas-id="posterCanvas"
      :style="{ width: canvasW + 'px', height: canvasH + 'px' }"
      class="poster-canvas"
    ></canvas>
    <button @click="generatePoster">生成海报</button>
    <image v-if="posterUrl" :src="posterUrl" mode="widthFix" />
  </view>
</template>
export default {
  data() {
    return {
      canvasW: 300,
      canvasH: 500,
      posterUrl: ''
    };
  },
  mounted() {
    const systemInfo = uni.getSystemInfoSync();
    // 以 750rpx 设计稿为基准,换算成 px
    this.canvasW = systemInfo.windowWidth * 0.8;
    this.canvasH = this.canvasW * 1.6;
  }
};

四、绘制海报的核心代码

下面是一个完整的绘制方法,包含了背景、头像、文字、商品图和二维码。

methods: {
  async generatePoster() {
    const ctx = uni.createCanvasContext('posterCanvas', this);
    const W = this.canvasW;
    const H = this.canvasH;

    // 1. 绘制背景
    ctx.setFillStyle('#ffffff');
    ctx.fillRect(0, 0, W, H);

    // 2. 绘制顶部背景色块
    ctx.setFillStyle('#ff6b00');
    ctx.fillRect(0, 0, W, H * 0.35);

    // 3. 绘制头像(圆形)
    const avatarSize = W * 0.2;
    const avatarX = W * 0.5 - avatarSize / 2;
    const avatarY = H * 0.08;
    ctx.save();
    ctx.beginPath();
    ctx.arc(W * 0.5, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
    ctx.closePath();
    ctx.clip();
    // 注意:图片需要先下载到本地
    const avatarPath = await this.downloadImage('https://example.com/avatar.png');
    ctx.drawImage(avatarPath, avatarX, avatarY, avatarSize, avatarSize);
    ctx.restore();

    // 4. 绘制昵称
    ctx.setFillStyle('#ffffff');
    ctx.setFontSize(16);
    ctx.setTextAlign('center');
    ctx.fillText('用户昵称', W * 0.5, avatarY + avatarSize + 24);

    // 5. 绘制商品图片
    const goodsW = W * 0.7;
    const goodsH = goodsW;
    const goodsX = (W - goodsW) / 2;
    const goodsY = H * 0.4;
    const goodsPath = await this.downloadImage('https://example.com/goods.png');
    ctx.drawImage(goodsPath, goodsX, goodsY, goodsW, goodsH);

    // 6. 绘制商品标题
    ctx.setFillStyle('#333333');
    ctx.setFontSize(14);
    ctx.setTextAlign('left');
    ctx.fillText('商品名称示例', goodsX, goodsY + goodsH + 30);

    // 7. 绘制价格
    ctx.setFillStyle('#ff6b00');
    ctx.setFontSize(18);
    ctx.fillText('¥ 99.00', goodsX, goodsY + goodsH + 60);

    // 8. 绘制二维码
    const qrSize = W * 0.25;
    const qrX = W - qrSize - 20;
    const qrY = H - qrSize - 20;
    const qrPath = await this.downloadImage('https://example.com/qrcode.png');
    ctx.drawImage(qrPath, qrX, qrY, qrSize, qrSize);

    // 9. 绘制提示文字
    ctx.setFillStyle('#999999');
    ctx.setFontSize(12);
    ctx.fillText('长按识别二维码', qrX - 80, qrY + qrSize / 2);

    // 10. 完成绘制
    ctx.draw(false, () => {
      setTimeout(() => {
        this.canvasToImage();
      }, 300);
    });
  }
}

五、图片下载与跨域处理

Canvas 绘制网络图片时,必须先将图片下载到本地,否则在小程序和 App 端会绘制失败。这里封装一个 downloadImage 方法:

downloadImage(url) {
  return new Promise((resolve, reject) => {
    uni.downloadFile({
      url,
      success: (res) => {
        if (res.statusCode === 200) {
          resolve(res.tempFilePath);
        } else {
          reject(new Error('下载失败'));
        }
      },
      fail: reject
    });
  });
}

在 H5 端,如果图片存在跨域问题,需要服务端配置 Access-Control-Allow-Origin,或者将图片转为 base64 后再绘制。

六、导出图片并保存到相册

绘制完成后,调用 uni.canvasToTempFilePath 导出图片:

canvasToImage() {
  uni.canvasToTempFilePath({
    canvasId: 'posterCanvas',
    success: (res) => {
      this.posterUrl = res.tempFilePath;
      this.saveToAlbum(res.tempFilePath);
    },
    fail: (err) => {
      console.error('导出失败', err);
    }
  }, this);
},

saveToAlbum(filePath) {
  uni.saveImageToPhotosAlbum({
    filePath,
    success: () => {
      uni.showToast({ title: '已保存到相册', icon: 'success' });
    },
    fail: (err) => {
      if (err.errMsg.includes('auth deny')) {
        uni.showModal({
          title: '提示',
          content: '需要您授权保存图片到相册',
          success: (res) => {
            if (res.confirm) {
              uni.openSetting();
            }
          }
        });
      }
    }
  });
}

七、常见问题与优化建议

  1. Canvas 层级问题:在小程序中,Canvas 是原生组件,层级最高,会遮挡其他元素。可以通过 cover-view 或绘制完成后隐藏 Canvas 来解决。
  2. 绘制时机:必须等图片全部下载完成后再调用 ctx.draw(),否则会出现图片空白。
  3. 圆角图片:Canvas 本身不支持圆角,需要结合 arcclip 实现。
  4. 多行文字fillText 不支持自动换行,需要手动计算文字宽度并逐行绘制。
  5. 性能优化:海报尺寸不宜过大,建议宽度不超过屏幕宽度的 1.5 倍,避免导出慢或内存溢出。
  6. 兼容性:App 端建议使用 uni.createCanvasContext 的旧版接口,或升级到 type="2d" 的新版 Canvas,后者性能更好但需要额外适配。

八、总结

通过 UniApp 的 Canvas API,我们可以灵活地生成各种样式的分享海报。核心步骤包括:搭建 Canvas 容器、下载网络图片、按顺序绘制元素、导出临时文件并保存到相册。虽然过程中需要处理图片下载、跨域、层级等细节,但整体方案成熟稳定,适用于电商、活动推广、邀请注册等多种业务场景。掌握这套流程后,你可以根据设计稿自由扩展海报内容,打造更具传播力的分享体验。

未经允许不得转载:任鹏个人博客 » UniApp 中使用 Canvas 生成分享海报的完整实现

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏