利用canvas生成海报

准备

准备两张图片连接,最好是自己开发账号验证的https图片链接。

实现思路

其实就是canvas实现方式,首先要就是定义一个canvas容器,把容器放在中间,图片也要动态计算大小居中,显示下面的文字和二维码也是要根据容器动态去改变。

实现代码

  • 下载头像,将图片缓存到本地
async onLoad() {
  let url1 = 'https://static-client-image.jrucker.cn/image/1547303314501.jpeg';
  let url2 = 'https://static-client-image.jrucker.cn/image/1550757251583.jpeg';

  this.getAsyncPic(url1, url2);
},
async getAsyncPic(url1, url2) {
  let res = await this.getUrlPromise(url1, url2);

  if (res.length && res.length === 2) {
    let [bannerObj, wxcodePicObj] = res;

    this.setData({
      'canvas.banner': bannerObj.banner,
      'canvas.wxcodePic': wxcodePicObj.wxcodePic
    });
  }
},
async getUrlPromise(url1, url2) {
  // 生成海报的封面图缓存到本地
  const promise1 = new Promise((resolve, reject) => {
    wx.getImageInfo({   //  小程序获取图片信息API
      src: url1,
      success: function (res) {
        resolve({
          banner: res.path
        })
      },
      fail(err) {
        reject(err)
      }
    })
  });

  // 生成海报的小程序图缓存到本地
  const promise2 = new Promise((resolve, reject) => {
    wx.getImageInfo({   //  小程序获取图片信息API
      src: url2,
      success: function (res) {
        resolve({
          wxcodePic: res.path
        })
      },
      fail(err) {
        reject(err)
      }
    })
  });

  let res = await Promise.all([promise1, promise2]);

  const promise = new Promise((resolve) => {
    resolve(res ? res : [])
  });

  return promise;
},
复制代码
  • 绘制海报
async setPoster() {
  if (this.data.posterImage) {
    return this.setData({
      visible: true,
      posterImage: this.data.posterImage
    })
  }

  wx.showLoading({title: '海报生成中...'});

  if (!this.data.canvas.banner || !this.data.canvas.wxcodePic) {
    let url1 = 'https://static-client-image.jrucker.cn/image/1547303314501.jpeg';
    let url2 = 'https://static-client-image.jrucker.cn/image/1550757251583.jpeg';

    await this.getAsyncPic(url1, url2);
  }

  const ctx = wx.createCanvasContext('canvas-box');
  ctx.setFillStyle('#ffffff');
  ctx.fillRect(0, 0, 270, 330);

  // 绘制顶部banner
  ctx.drawImage(this.data.canvas.banner, 0, 0, 270, 170);

  this.dealWords({
    ctx: ctx, // 画布上下文
    fontSize: 16, // 字体大小
    word: this.data.canvas.title, // 需要处理的文字
    maxWidth: 255, // 一行文字最大宽度
    fillStyle: '#333333',
    x: 8, // 文字在x轴要显示的位置
    y: 177, // 文字在y轴要显示的位置
    maxLine: 1 // 文字最多显示的行数
  });

  this.dealWords({
    ctx: ctx,
    fontSize: 14,
    word: this.data.canvas.desc,
    maxWidth: 260,
    fillStyle: '#999999',
    x: 8,
    y: 201,
    maxLine: 2
  });

  ctx.moveTo(0, 252);
  ctx.lineTo(270, 252);
  ctx.setLineWidth(0.5);
  ctx.setStrokeStyle('#f2f2f2');
  ctx.stroke();

  // 绘制小程序码
  ctx.drawImage(this.data.canvas.wxcodePic, 9, 261, 60, 60);
  ctx.setFontSize(13);
  ctx.setFillStyle('#999999');
  ctx.fillText('长按扫码查看详情', 80, 296);
  ctx.draw();

  setTimeout(() => {
    wx.canvasToTempFilePath({
      quality: 1,
      canvasId: 'canvas-box',
      success: (res) => {
        let tempFilePath = res.tempFilePath;
        wx.hideLoading();
        this.setData({
          visible: true,
          posterImage: tempFilePath
        })
      },
      fail: (res) => {
        console.log(res);
      }
    });
  }, 1000)
}
复制代码
  • 绘制多行文本
dealWords(options) {
  options.ctx.setFontSize(options.fontSize);//设置字体大小
  let allRow = Math.ceil(options.ctx.measureText(options.word).width / options.maxWidth);//实际总共能分多少行
  let count = allRow >= options.maxLine ? options.maxLine : allRow;//实际能分多少行与设置的最大显示行数比,谁小就用谁做循环次数
  let endPos = 0;//当前字符串的截断点
  for (let j = 0; j < count; j++) {
    let nowStr = options.word.slice(endPos);//当前剩余的字符串
    let rowWid = 0;//每一行当前宽度
    options.ctx.setFillStyle(options.fillStyle);

    if (options.ctx.measureText(nowStr).width > options.maxWidth) {//如果当前的字符串宽度大于最大宽度,然后开始截取
      for (let m = 0; m < nowStr.length; m++) {
          rowWid += options.ctx.measureText(nowStr[m]).width;//当前字符串总宽度
        if (rowWid > options.maxWidth) {
          if (j === options.maxLine - 1) { //如果是最后一行
            options.ctx.fillText(nowStr.slice(0, m - 1) + '...', options.x, options.y + (j + 1) * 18);  //(j+1)*18这是每一行的高度
          } else {
            options.ctx.fillText(nowStr.slice(0, m), options.x, options.y + (j + 1) * 18);
          }
          endPos += m;//下次截断点
          break;
        }
      }
    } else {//如果当前的字符串宽度小于最大宽度就直接输出
      options.ctx.fillText(nowStr.slice(0), options.x, options.y + (j + 1) * 18);
    }
  }
}
复制代码
  • 保存绘制图片至相册,需提前判断是否已授权,这一步就是最后把canvas生成图片了,大功告成。
savePoster() {
  const canvasToTempFilePath = () => {
    wx.showLoading({title: '加载中'});

    wx.canvasToTempFilePath({
      quality: 1,
      canvasId: 'canvas-box',
      fileType: 'png',
      success: (res) => {
        wx.saveImageToPhotosAlbum({
          filePath: res.tempFilePath,
          success: (res) => {
            wx.hideLoading();
            wx.showToast({
              title: '保存成功',
            });
            this.setData({
              visible: false
            })
          },
          fail() {
            wx.hideLoading()
          }
        })
      }
    })
  };
  /*查看是否授权*/
  wx.getSetting({
    success: (res) => {
      if (!res.authSetting['scope.writePhotosAlbum']) {
        wx.authorize({
          scope: 'scope.writePhotosAlbum',
          success() { //这里是用户同意授权后的回调
            canvasToTempFilePath();
          },
          fail() { //这里是用户拒绝授权后的回调
            wx.showModal({
              title: '提示',
              content: '若不打开授权,则无法将图片保存在相册中!',
              showCancel: true,
              cancelText: '暂不授权',
              cancelColor: '#000000',
              confirmText: '去授权',
              confirmColor: '#3CC51F',
              success: function (res) {
                if (res.confirm) {
                  wx.openSetting({
                    //调起客户端小程序设置界面,返回用户设置的操作结果。
                  })
                } else {
                  console.log('用户点击取消')
                }
              }
            })
          }
        })
      } else {
        canvasToTempFilePath()
      }
    }
  });
}
复制代码

注意

1、图片要提前下载

这里面还有一个问题就是,图片要提前下载完之后再绘图,不然图片显示不出来,可以把下载图片的方法单独拎出来,然后下载图片后回调绘图方法。

2、ctx.draw()

这个方法是在绘制完成之后在调用,不然容易其它被覆盖。

效果图

<think>好的,我现在需要帮助用户解决在Uni-app中使用html2canvas生成海报的问题。首先,我得回忆一下用户提到的几个引用内容,看看有没有相关的信息可以利用。引用1和2提到了使用html2canvas库来生成海报,但用户可能遇到了一些问题,比如如何正确引入和使用。引用3和4则涉及到Uni-app中Canvas组件的使用以及自定义海报组件的实现步骤,特别是动态生成元素和图片合成的方法。 首先,用户想要的是一个示例教程,所以需要提供一个清晰的步骤指南。根据引用4,生成海报的步骤包括动态生成元素、图片合成、自定义样式和长按保存。结合引用1中的引入html2canvas的方法,我应该先指导用户安装和引入这个库。但需要注意的是,Uni-app的环境可能和普通网页不同,可能需要处理跨平台兼容性问题,比如H5和小程序的区别。 接下来,用户可能需要一个基础示例的结构。引用3提到了一些常见的错误,比如没有正确传入组件实例或生命周期的问题,所以在示例代码中需要确保在onReady或onMounted生命周期中初始化Canvas。此外,生成海报后保存图片的方法也需要参考引用4中的长按保存功能,使用uni.saveImageToPhotosAlbum。 然后,要考虑用户可能遇到的兼容性问题。引用2指出,现有的插件可能不符合需求,所以需要强调动态生成元素的方法,让用户可以根据自己的设计调整结构和样式。同时,引用3中的注意事项,比如canvas-id的设置和2D上下文的处理,应该在步骤中提醒用户避免常见错误。 最后,生成相关问题时要围绕用户的需求扩展,比如性能优化、动态内容处理、跨平台兼容性等,这些都是用户在实际开发中可能遇到的后续问题。需要确保这些相关问题能帮助用户进一步深入学习和解决问题。</think>以下是在Uni-app中使用html2canvas生成海报的完整实现步骤和示例: ### 一、基础实现步骤 1. **安装依赖** ```bash npm install html2canvas --save ``` 2. **创建海报容器** ```vue <template> <view> <view id="posterBox" class="poster-container"> <!-- 自定义海报内容 --> <image src="/static/logo.png" class="header-image"/> <text class="title">{{ title }}</text> </view> <canvas canvas-id="myCanvas" class="canvas-box"></canvas> </view> </template> ``` 3. **核心生成函数** ```javascript import html2canvas from 'html2canvas'; export default { data() { return { title: "夏日促销海报" } }, methods: { async generatePoster() { // 获取DOM节点 const element = document.getElementById('posterBox'); // 生成Canvas const canvas = await html2canvas(element, { useCORS: true, // 允许跨域图片 scale: 2, // 提高分辨率 backgroundColor: null }); // 转换为临时路径 const tempFilePath = canvas.toDataURL(); // 保存到本地 uni.saveImageToPhotosAlbum({ filePath: tempFilePath, success: () => uni.showToast({ title: '保存成功' }), fail: (err) => console.error(err) }); } } } ``` ### 二、关键注意事项 1. **跨平台兼容性**: - H5端可直接使用DOM操作 - 小程序端需通过`uni.createSelectorQuery()`获取节点信息[^3] - 需要处理不同端的图片域名白名单 2. **样式处理技巧**: ```css .poster-container { width: 750rpx; height: 1334rpx; position: absolute; left: -9999px; /* 隐藏原始容器 */ } .canvas-box { width: 100%; height: 100vh; } ``` 3. **性能优化方案**: - 预加载所有图片资源 - 使用`will-change: transform`提升渲染性能 - 对静态内容进行缓存处理[^4] ### 三、完整示例代码 ```vue <script> export default { onReady() { this.generatePoster(); }, methods: { async generatePoster() { try { const res = await html2canvas(document.querySelector('#posterBox'), { allowTaint: true, logging: true }); uni.previewImage({ urls: [res.toDataURL()], current: 0 }); } catch (e) { console.error('生成失败:', e); } } } } </script> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值