微信小程序海报生成封装--记录一下

注意:因为非2D的Canvas,微信不在维护wx.createCanvasContext这个API,所有苹果端裁剪会有问题,有时间改造一版2D的吧

从别人 那看过来的,原项目是一个uniapp,因为需要在小程序里直接用,为了需求,所以编译后修改了部分内容。

目前还有点缺陷就是海报不能放大,可能会不清晰

MDCanvas.js代码

class MDCanvas {
  /**
   * 构造函数
   * @param {Object} ctx canvas的实例
   * @param {String} canvasId  canvas组件的canvasId
   * @param {Array} canvasData 要画canvas的数组
   */
  constructor(ctx, canvasId, canvasData) {
    this._canvasData = canvasData; // 要画的canvas数组
    this._ctx = ctx; // canvas 实例
    this._canvasId = canvasId; // canvasId
    this._pmImgTask = []; // promise下载图片任务
  }

  /**
   * 画canvas
   */
  drawCanvas() {
    wx.showToast({
      title: '加载素材..',
      icon: 'loading',
      mask: true,
      duration: 10 * 1000
    });
    const ctx = this._ctx;
    const canvasId = this._canvasId;
    this.asyncImage();
    return new Promise((resolve, reject) => {
      Promise.all(this._pmImgTask).then(() => {
        this._canvasData.forEach(item => {
          switch (item.type) {
            case "bg": //背景
              const radius = item.radius ? item.radius : 0;
              this.roundRect(ctx, 0, 0, upx2px(item.width), upx2px(item.height), radius);
              break;
            case 'lg': // 线性渐变
            {
              const lrd = ctx.createLinearGradient(upx2px(item.x0), upx2px(item.y0),
                upx2px(item.x1), upx2px(item.y1));
              item.colors.forEach(x => {
                lrd.addColorStop(x.scope, x.value);
              });
              ctx.setFillStyle(lrd);
              ctx.fillRect(upx2px(item.x), upx2px(item.y), upx2px(item.width),
                upx2px(item.height));
            }
            break;
          case 'img': // 图片
            ctx.save();
            ctx.beginPath();
            if (item.border === 'circle') { // 圆角
              const radius = item.width <= item.height ? item.width : item.height;
              if (item.width >= item.height) {
                item.x = item.x - (item.width - item.height) / 2;
              } else {
                item.y = item.y - (item.height - item.width) / 2;
              }
              ctx.arc(upx2px(item.x + item.width / 2), upx2px(item.y + item.height / 2),
                upx2px(radius / 2), 0, 2 * Math.PI);
              ctx.setFillStyle('#fff');
              ctx.fill();
              ctx.clip();
            }
            ctx.drawImage(
              item.content,
              upx2px(item.x),
              upx2px(item.y),
              upx2px(item.width),
              upx2px(item.height)
            );
            ctx.restore();
            break;
          case 'text': // 文字
          {
           
            ctx.setFillStyle(item.color);
            if (item.textAlign) ctx.setTextAlign(item.textAlign);
            if (item.baseLine) ctx.setTextBaseline(item.baseLine);
            const bold = item.bold ? item.bold : 'normal'; // 加粗
            const family = item.family ? item.family : 'Microsoft YaHei'; // 字体
            ctx.font = `${bold} ${Math.floor(upx2px(item.fontSize))}px ${family}`;
            const arr = this.newLine(item.content, upx2px(item.width), item.row); // 换行
            const lineHeight = item.lineHeight ? item.lineHeight : Number(item.fontSize || 30) * 1.2; // 字体大小默认30
            var fontSize = Number(item.fontSize || 30);

          

            arr.forEach((itemText, key) => {
              var x = upx2px(item.x);
              if(item.textAlign){
                var len1 = this.getZhongWenLen(itemText);
                var len2 = itemText.length - len1;
                x = upx2px((item.width - len1 * fontSize - len2 * (fontSize/2)) / 2);
              }
              ctx.fillText(itemText, x, upx2px(item.y + lineHeight * key));
            });
          }
          break;
          case 'shape': // 形状
            ctx.save();
            ctx.beginPath();
            if (item.linearGradient) { // 渐变
              const grad = ctx.createLinearGradient(item.linearGradient.x1, item.linearGradient.y1,
                item.linearGradient
                .x2, item.linearGradient.y2); // 创建一个渐变色线性对象
              grad.addColorStop(0, item.linearGradient.color1); // 定义渐变色颜色
              grad.addColorStop(1, item.linearGradient.color2);
              ctx.setFillStyle(grad);
            } else {
              ctx.setFillStyle(item.background);
            }
            switch (item.shape) {
              case 'circle': // 圆圈
                ctx.arc(upx2px(item.x), upx2px(item.y), upx2px(item.radius), 0, 2 * Math.PI);
                ctx.fill();
                break;
              case 'rect': // 四变形
                ctx.fillRect(upx2px(item.x), upx2px(item.y), upx2px(item.width), upx2px(
                  item.height));
                break;
              case 'round': // 带圆角的形状
              {
                const radius = item.radius ? upx2px(item.radius) : 4;
                ctx.arc(upx2px(item.x) + radius, upx2px(item.y) + radius, radius, Math.PI, (
                  Math.PI *
                  3) / 2);
                ctx.lineTo(upx2px(item.width) - radius + upx2px(item.x), upx2px(item.y));
                ctx.arc(upx2px(item.width) - radius + upx2px(item.x), radius + upx2px(
                    item.y),
                  radius, (Math.PI * 3) / 2, Math.PI * 2);
                ctx.lineTo(upx2px(item.width) + upx2px(item.x), upx2px(item.height) + uni
                  .upx2px(
                    item.y) - radius);
                ctx.arc(
                  upx2px(item.width) - radius + upx2px(item.x),
                  upx2px(item.height) - radius + upx2px(item.y),
                  radius,
                  0,
                  (Math.PI * 1) / 2
                );
                ctx.lineTo(radius + upx2px(item.x), upx2px(item.height) + upx2px(item.y));
                ctx.arc(radius + upx2px(item.x), upx2px(item.height) - radius + upx2px(
                    item.y),
                  radius, (Math.PI * 1) / 2, Math.PI);
                ctx.closePath();
                ctx.fill();
                ctx.strokeStyle = item.background;
                ctx.stroke();
              }
              break;
            }
            ctx.restore();
            break;
          }

        });
        ctx.save();
        wx.showToast({
          title: '正在生成..',
          icon: 'loading',
          mask: true,
          duration: 2 * 1000
        });
        ctx.draw(false);
        setTimeout(() => {
          try {
            wx.canvasToTempFilePath({
              canvasId: canvasId,
              success: res => {
                resolve(res.tempFilePath);
              },
              fail: err => {
                reject(err);
              },
              complete() {

                wx.hideToast();
              }
            });
          } catch (error) {
            console.log(error)
          }
        }, 300);
      });
    });
  }

  /**
   * 计算换行
   * @param {String} str 文字
   * @param {Number} width 宽度
   * @param {Number} row 行数
   */
  newLine(str, width, row) {
    const arr = [];
    let str1 = '';
    let newArr = [];
    const ctx = this._ctx;
    if (width) {
      for (let i = 0; i < str.length; i++) {
        if (i === str.length - 1) {
          const str2 = str1 + str[i];
          if (this.measureText(ctx.state.fontSize, str2)) {
            arr.push(str2);
          } else {
            arr.push(str1);
            str1 = str[i];
            arr.push(str1);
          }
        } else {
          const str2 = str1 + str[i];
          if (this.measureText(ctx.state.fontSize, str2) > width) {
            arr.push(str1);
            str1 = str[i];
          } else {
            str1 = str2;
          }
        }
      }
    } else {
      arr.push(str);
    }
    newArr = row ? arr.slice(0, row) : arr;
    if (row && arr.length > row) {
      const len = newArr[row - 1].length;
      const lastStr = newArr[row - 1][len - 1];
      const pattern = new RegExp('[\u4E00-\u9FA5]+');
      newArr[row - 1] = pattern.test(lastStr) ?
        newArr[row - 1].substring(0, newArr[row - 1].length - 1) + '...' :
        newArr[row - 1].substring(0, newArr[row - 1].length - 2) + '...';
    }
    return newArr;
  }
  /**
   * 计算文字宽度
   * @param {Number} fontSize
   * @param {String} str
   */
  measureText(fontSize, str) {
    if (!str) return 0;
    if (typeof str !== 'string') {
      str += '';
    }
    return (str.replace(/[^\x00-\xff]/g, 'ab').length / 2) * fontSize;
  }


  /**
   * 处理图片
   */
  asyncImage() {
    this._canvasData.forEach(x => {
      if (x.type === 'img') {
        const p = this.downLoadFile(x.content, x.useGet).then(res => {
          x.content = res;
        });
        this._pmImgTask.push(p);
      }
    });
  }

  /**
   * 下载文件
   * @param {String} url 下载文件地址
   * @param {Boolean} useGet 是否需要从服务器下载(小程序未配置下载域名、跨域时需要服务器支持)
   */
  downLoadFile(url, useGet) {

    if (url.indexOf('base64') > -1) { // base64文件直接返回
      return new Promise(resolve => {
        resolve(url);
      });
    }

    //url += `?r=${+new Date()}`; // 加上随机数防止微信缓存导致canvas画图时出错
    if (url.indexOf("?") > -1) {
      if (url.endsWith("&")) {
        url += `r=${+new Date()}`; // 加上随机数防止微信缓存导致canvas画图时出错
      } else {
        url += `&r=${+new Date()}`; // 加上随机数防止微信缓存导致canvas画图时出错
      }
    } else {
      url += `?r=${+new Date()}`; // 加上随机数防止微信缓存导致canvas画图时出错
    }

    return new Promise((resolve, reject) => {
      wx.downloadFile({
        url,
        success: res => {
          resolve(res.tempFilePath);
        },
        fail: err => {
          console.error('下载文件出错', err, url);
          reject(err);
          wx.showModal({
            title: '提示',
            showCancel: false,
            content: err.errMsg
          });
        }
      });
    });
  }
  /**
   * 切割圆角
   * @param {CanvasContext} ctx canvas上下文
   * @param {number} x 圆角矩形选区的左上角 x坐标
   * @param {number} y 圆角矩形选区的左上角 y坐标
   * @param {number} w 圆角矩形选区的宽度
   * @param {number} h 圆角矩形选区的高度
   * @param {number} r 圆角的半径
   */
  roundRect(ctx, x, y, w, h, r) {
    ctx.beginPath();
    ctx.fillStyle = "transparent";
    ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
    ctx.moveTo(x + r, y);
    ctx.lineTo(x + w - r, y);
    ctx.lineTo(x + w, y + r);
    ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
    ctx.lineTo(x + w, y + h - r);
    ctx.lineTo(x + w - r, y + h);
    ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);
    ctx.lineTo(x + r, y + h);
    ctx.lineTo(x, y + h - r);
    ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);
    ctx.lineTo(x, y + r);
    ctx.lineTo(x + r, y);
    ctx.fillStyle = "#fff";
    ctx.fill();
    ctx.clip();
  }

  /**
   * 中文数量
   * @param {*} val 
   */
  getZhongWenLen(val) {
    var re = /[\u4E00-\u9FA5]/g;
    if (re.test(val)) {
      return val.match(re).length
    }
    return 0;
  }
}
const EPS = 1e-4;
const BASE_DEVICE_WIDTH = 750;

function upx2px(number) {
  let isIOS = false;
  let deviceWidth = 0;
  let deviceDPR = 0;
  const {
    platform,
    pixelRatio,
    windowWidth
  } = wx.getSystemInfoSync();
  deviceWidth = windowWidth;
  deviceDPR = pixelRatio;
  isIOS = platform === "ios";
  number = Number(number);
  if (number === 0) {
    return 0;
  }
  let width = deviceWidth;
  let result = number / BASE_DEVICE_WIDTH * width;
  if (result < 0) {
    result = -result;
  }
  result = Math.floor(result + EPS);
  if (result === 0) {
    if (deviceDPR === 1 || !isIOS) {
      result = 1;
    } else {
      result = 0.5;
    }
  }
  return number < 0 ? -result : result;
}

exports.MDCanvas = MDCanvas;

支持功能:
  画线性渐变(支持多颜色)

  画图片(支持自动裁切圆角)

  画文字(支持自动换行、省略)

  画圆形

  画矩形(支持圆角)

使用:

1.页面,css是让canvas移除可是范围内

<view class="container">
<canvas class="canvas2" canvas-id="canvasId" id="canvasId" style="width: {{canvasWidth}}rpx; height:{{canvasHeight}}rpx;"></canvas>
</view>

//样式
.container {
    height: 1000rpx;
    width: 750rpx;
    position: fixed;
    left: 2000px;
    top: 2000px;
}

2.js导入

import {
  MDCanvas
} from "./md-canvas";

3.其他参数

data:{
    canvasWidth: 468, //canvas宽
    canvasHeight: 832, //canvas高
}

4.初始化canvas

onLoad(options) {
    const ctx = wx.createCanvasContext("canvasId", this);
    this.ctx = ctx;
}

5.代码使用

var canvasData=[{...}]
const mdCtx = new MDCanvas(ctx, "canvasId", canvasData);
 mdCtx.drawCanvas().then((res) => {
        console.log("临时图片地址", res);
        
      }).catch((err) => {
        console.error(err);
      });

备注:多海报生成 ,需要特殊处理一下

//封装 
 drawCanvas(canvasData) {
    var ctx = this.ctx;
    const mdCtx = new MDCanvas(ctx, "canvasId", canvasData);
    return new Promise((resolve) => {
      mdCtx.drawCanvas().then((res) => {
        console.log("临时图片地址", res);
        resolve(res);
      }).catch((err) => {
        console.error(err);
      });
    });
  }


//调用
var imgArray = [];
for (let element of canvasDataArray) {
    var res = await this.drawCanvas(element);
    imgArray.push(res)
}

参数

常规参数

参数名类型参考值说明

type

Stringbg、img、shape、text、lg

绘画内容,如下:

bg:背景

img:图片

shape:形状

lg:画线性渐变

text:文字

xNumber坐标轴X
yNumber坐标轴Y

width

Number内容宽度

height

Number内容高度

1.当type为bg:

参数名类型参考值说明
xNumber坐标轴X
yNumber坐标轴Y

width

Number内容宽度

height

Number内容高度
radiusNumber半径

2.当type为img:

参数名类型参考值说明
xNumber坐标轴X
yNumber坐标轴Y

width

Number内容宽度

height

Number内容高度
borderStringcircleborder的值为circle时 自动裁切成圆形
radiusNumber半径 border的值为circle时起效,不填写则剪切成圆
contentString图片Url地址

3.当type为shape:

参数名类型参考值说明
xNumber坐标轴X
yNumber坐标轴Y

width

Number内容宽度

height

Number内容高度
borderStringcircleborder的值为circle时 自动裁切成圆形
radiusNumber半径 border的值为circle时起效,不填写则剪切成圆

4.当type为text:

参数名类型默认值说明
xNumber坐标轴X
yNumber坐标轴Y

width

Number内容宽度
rowNumber字体占用行数
colorString文字颜色
fontSizeNumber30字体大小
textAlignString设置文字的对齐
contentString文字内容

bold

String

normal

是否加粗,默认normal

baseLine

String

normal

设置文字的竖直对齐

family

StringMicrosoft YaHei设置字体,默认Microsoft YaHei

 5.当type为lg:

参数名类型参考值说明
xNumber坐标轴X
yNumber坐标轴Y
x0Number渐变起点的 x 坐标
y0Number渐变起点的 y 坐标
x1Number渐变终点的 x 坐标
y1Number渐变终点的 y 坐标
colorsArray渐变颜色

colors参数详情:

参数名类型参考值说明
scopeNumber表示渐变中开始与结束之间的位置,范围 0-1
valueString色值,如#fff

参考来源:uniapp 封装 canvas - leeseett - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值