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>

 實現效果:

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM