uniapp 封装 canvas

简单的对一些常用的canvas api进行了封装

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

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

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

  画圆形

  画矩形(支持圆角)

  

import * as config from '@/config/index.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() {
    uni.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 'lg': // 线性渐变
              {
                const lrd = ctx.createLinearGradient(uni.upx2px(item.x0), uni.upx2px(item.y0),
                  uni.upx2px(item.x1), uni.upx2px(item.y1));
                item.colors.forEach(x => {
                  lrd.addColorStop(x.scope, x.value);
                });
                ctx.setFillStyle(lrd);
                ctx.fillRect(uni.upx2px(item.x), uni.upx2px(item.y), uni.upx2px(item.width),
                  uni.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(uni.upx2px(item.x + item.width / 2), uni.upx2px(item.y + item.height / 2),
                  uni.upx2px(radius / 2), 0, 2 * Math.PI);
                ctx.setFillStyle('#fff');
                ctx.fill();
                ctx.clip();
              }
              ctx.drawImage(item.content, uni.upx2px(item.x), uni.upx2px(item.y), uni.upx2px(item.width),
                uni.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(uni.upx2px(item.fontSize))}px ${family}`;
                const arr = this.newLine(item.content, uni.upx2px(item.width), item.row); // 换行
                const lineHeight = item.lineHeight ? item.lineHeight : Number(item.fontSize || 30) * 1.2; // 字体大小默认30
                arr.forEach((itemText, key) => {
                  ctx.fillText(itemText, uni.upx2px(item.x), uni.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(uni.upx2px(item.x), uni.upx2px(item.y), uni.upx2px(item.radius), 0, 2 * Math.PI);
                  ctx.fill();
                  break;
                case 'rect': // 四变形
                  ctx.fillRect(uni.upx2px(item.x), uni.upx2px(item.y), uni.upx2px(item.width), uni.upx2px(
                    item.height));
                  break;
                case 'round': // 带圆角的形状
                  {
                    const radius = item.radius ? uni.upx2px(item.radius) : 4;
                    ctx.arc(uni.upx2px(item.x) + radius, uni.upx2px(item.y) + radius, radius, Math.PI, (
                      Math.PI *
                      3) / 2);
                    ctx.lineTo(uni.upx2px(item.width) - radius + uni.upx2px(item.x), uni.upx2px(item.y));
                    ctx.arc(uni.upx2px(item.width) - radius + uni.upx2px(item.x), radius + uni.upx2px(
                      item.y),
                    radius, (Math.PI * 3) / 2, Math.PI * 2);
                    ctx.lineTo(uni.upx2px(item.width) + uni.upx2px(item.x), uni.upx2px(item.height) + uni
                      .upx2px(
                        item.y) - radius);
                    ctx.arc(
                      uni.upx2px(item.width) - radius + uni.upx2px(item.x),
                      uni.upx2px(item.height) - radius + uni.upx2px(item.y),
                      radius,
                      0,
                      (Math.PI * 1) / 2
                    );
                    ctx.lineTo(radius + uni.upx2px(item.x), uni.upx2px(item.height) + uni.upx2px(item.y));
                    ctx.arc(radius + uni.upx2px(item.x), uni.upx2px(item.height) - radius + uni.upx2px(
                      item.y),
                    radius, (Math.PI * 1) / 2, Math.PI);
                    ctx.closePath();
                    ctx.fill();
                    ctx.strokeStyle = item.background;
                    ctx.stroke();
                  }
                  break;
              }
              ctx.restore();
              break;
          }

        });
        uni.showToast({
          title: '正在生成..',
          icon: 'loading',
          mask: true,
          duration: 2 * 1000
        });
        ctx.draw(false, () => {
          setTimeout(() => {
            uni.canvasToTempFilePath({
              canvasId: canvasId,
              success: res => {
                resolve(res.tempFilePath);
              },
              fail: err => {
                reject(err);
              },
              complete() {
                uni.hideToast();
              }
            });
          }, 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);
      });
    }

    if (url.indexOf('qlogo.cn') > -1 || useGet) { // 微信头像从服务器获取
      url = `${config.interfaceUrl}/common/img/wechat/avatar?urlHttp=${encodeURIComponent(url)}`;
    } else {
      url += `?r=${+new Date()}`; // 加上随机数防止微信缓存导致canvas画图时出错
    }

    return new Promise((resolve, reject) => {
      uni.downloadFile({
        url,
        success: res => {
          resolve(res.tempFilePath);
        },
        fail: err => {
          console.error('下载文件出错', err, url);
          reject(err);
          uni.showModal({
            title: '提示',
            showCancel: false,
            content: err.errMsg
          });
        }
      });
    });
  }
}


module.exports = MDCanvas;

config 文件可自行创建进行引用或者将服务器请求路径写死可以不必引用该文件 

 使用demo:

<template>
  <view class="container"><canvas class="canvas" canvas-id="canvasId"></canvas></view>
</template>

<script>
import MDCanvas from '@/common/md-canvas.js';
export default {
  onReady() {
    // 创建canvas实例
    const ctx = uni.createCanvasContext('canvasId', this);
    // 画布步骤数组
    const canvasData = [
      {
        type: 'lg', // 画线性渐变
        x0: 0, // 渐变起点的 x 坐标
        y0: 0, // 渐变起点的 y 坐标
        x1: 0, // 渐变终点的 x 坐标
        y0: 750, // 渐变终点的 y 坐标
        x: 0, // 右上角横坐标
        y: 0, // 右上角纵坐标
        width: 750, //
        height: 750, //
        colors: [ // 渐变颜色
          {
            scope: 0, // 表示渐变中开始与结束之间的位置,范围 0-1
            value: '#fff' // 色值
          },
          {
            scope: 0.5,
            value: '#f00'
          },
          {
            scope: 1,
            value: '#fff'
          },
        ]
      },
      {
        type: 'img', // 画图片
        border: 'circle', // border的值为circle时 自动裁切成圆形
        content: `https://f.modo5.com/third/wxminiprogram/oppein/share_poster_seckill_bg.png`, // 图片Url地址
        x: 18, // 右上角横坐标
        y: 0,// 右上角纵坐标
        width: 714, //
        height: 736 //
      },
      {
        type: 'text', // 画文字
        content: 'This is text.', // 文字内容
        color: '#fff', // 文字颜色
        x: 120, // 右上角横坐标
        y: 120, // 右上角纵坐标
        width: 180, // 文字占用宽
        fontSize: 30, // 字体大小
        row: 1 // 字体占用行数
      },
      {
        type: 'shape', // 画形状
        background: '#fff', // 背景颜色
        shape: 'circle', // 指定形状为圆形
        x: 150, // 右上角横坐标
        y: 150, // 右上角纵坐标
        radius: 10 // 半径
      },
      {
        type: 'shape', // 画形状
        background: '#ccc', // 背景颜色
        shape: 'rect', // 指定形状为矩形
        x: 200, // 右上角横坐标
        y: 150, // 右上角纵坐标
        width: 20, //
        height: 20 //
      },
      {
        type: 'shape', // 画形状
        background: '#3fd', // 背景颜色
        shape: 'round', // 指定形状为带圆角的矩形
        x: 250, // 右上角横坐标
        y: 150, // 右上角纵坐标
        width: 40, //
        height: 40, //
        radius: 10 // 圆角值
      }
    ];
    // 初始化
    const mdCtx = new MDCanvas(ctx, 'canvasId', canvasData);
    // 画矩形
    mdCtx
      .drawCanvas()
      .then(res => {
        console.log("临时图片地址",res);
      })
      .catch(err => {
        console.error(err);
      });
  }
};
</script>

<style lang="scss">
.container {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  width: 750rpx;
}

.canvas {
  width: 750rpx;
  height: 750rpx;
}
</style>

 实现效果:

猜你喜欢

转载自www.cnblogs.com/Leesttee/p/13391552.html
今日推荐