canvas

获取设备分辨率

getPixelRatio(context) {
  let backingStore = context.backingStorePixelRatio 
  || context.webkitBackingStorePixelRatio 
  || context.mozBackingStorePixelRatio 
  || context.msBackingStorePixelRatio 
  || context.oBackingStorePixelRatio 
  || context.backingStorePixelRatio || 1;
  return (window.devicePixelRatio || 1) / backingStore;
}

canvas 设置并画图

drawCanvas(imgs){
  const canvas = document.querySelector('#canvas');
  const context = canvas.getContext('2d');
  const ratio = this.getPixelRatio(context);
  canvas.width = cwidth;
  canvas.height = cheight;
  canvas.style.width=cwidth+"px";
  canvas.style.height = cheight+"px";
  // todo ...
}

canvas 画直线

context.beginPath();
context.moveTo(100, 200);
context.lineTo(100, 200);
context.lineWidth = 2;
context.strokeStyle = '#ffffff';
context.stroke();

canvas 画圆形图片

context.save();
context.beginPath();
context.arc(...imagesArr[i].roundParam);
context.clip();
context.drawImage(img, ...imagesArr[i].param);
context.restore();

canvas 设置文字阴影

// 加阴影
context.shadowColor = 'rgba(183, 67, 0, 0.75)';
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.shadowBlur = 8;
// 不加阴影
context.shadowColor = 'rgba(183, 67, 0, 0)';
context.shadowBlur = 0;

canvas 测量文字宽度

context.measureText(words).width

canvas 实现文字渐变色

let grd = context.createLinearGradient(left, top, left, top);
grd.addColorStop(0.3, 'rgb(155, 0, 0)');
grd.addColorStop(1, 'rgb(0, 155, 0)');
context.fillStyle = grd;

canvas 给图片换皮肤色

/**
 * @description: canvas皮肤换色
 * @param {String} img
 * @param {Array} colorNewArr ['50, 50, 50']
 * @param {Array} colorOriginArr ['150, 150, 150']
 * @return {*}
 */
canvasChangeColor(img, colorNewArr, colorOriginArr) {
  const { width, height } = img;
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = width;
  canvas.height = height;
  // 将源图片画到画布上
  ctx.drawImage(img, 0, 0, width, height);
  // 获取画布的像素信息
  const imageData = ctx.getImageData(0, 0, width, height);
  const { data: colorData } = imageData;
  // 循环像素点改变颜色【1像素=4通道】【共x像素:宽*高】【共x通道:宽*高*4】
  const pixelLength = width * height;
  const limit = 10; // 换色区间阈值
  for (let i = 0; i < pixelLength; i++) {
    const [cr, cg, cb] = [
      colorData[i * 4],
      colorData[i * 4 + 1],
      colorData[i * 4 + 2]
    ];
    const index = colorOriginArr.findIndex(
      (color) => +color[0] - limit < cr
        && cr < +color[0] + limit
        && +color[1] - limit < cg
        && cg < +color[1] + limit
        && +color[2] - limit < cb
        && cb < +color[2] + limit
    )
    if (index > -1 && colorNewArr[index]) {
      const [r, g, b] = colorNewArr[index];
      colorData[i * 4] = +r;
      colorData[i * 4 + 1] = +g;
      colorData[i * 4 + 2] = +b;
      colorData[i * 4 + 3] = +255;
    }
  }
  // 将修改后代码复制到画布
  ctx.putImageData(imageData, 0, 0);
  // 输出base64图
  return canvas.toDataUrl('image/png');
}

canvas 设置背景透明

const canvas = document.querySelector('#stage');
const ctx = canvas.getContext('webgl');
ctx.globalAlpha = 0;

加载图片

问题:加载图片资源失败问题(图片是异步加载的,不需要等待)。
原因:网速快或有缓存时,以至于没有运行到onload的时候,图片 img.src 已经被加载完毕了。
解决:因此,可以先告诉浏览器如何处理这张图片即先 img.onload 监听事件,然后在给 img.src 添加链接来源。 一般情况下,可以用complete来判断图片是否加载完毕,complete 根据图片是否显示过来判断,只有加载的图片显示后,complete属性值为true ,否则是false ,和是否加载过,是否缓存没有关系!
PS:JS、css、图片去除缓存方法,可添加随机字符串或时间戳。

loadImages(imgArr){
  let times = 0;
  for(let i = 0, len = imgArr.length; i < len; i++){
    let img = new Image();
    // 解决跨域问题,注意区分本地ip和域名
    // ios10系统生成canvas图片时会出错,base64不应设置跨域
    if (arr[i].url.indexOf('data:image/png;') === -1) {
      img.setAttribute('crossOrigin', 'anonymous');
    }
    // 先img.onload监听事件   
    img.onload = function () {
        times++;
        if(times === imgArr.length){}
    }
    img.onerror = function () {
        times++;
        if(times === imgArr.length){}
    }
    // 再img.src赋图片链接   
    img.src = imgArr[i];
  }
}

书写文字

封装方法

function drawText(text) {
  let {
    type, // 可用于区分是哪段文案
    words = '',
    top = 250,
    left = 250,
    maxWidth,
    lineHeight,
    textAlign,
    color,
    font,  // 'bold 24px'
  } = text;

  let line = '';
  let wordsArr = words.split('');
  context.font = font;
  context.textAlign = textAlign;
  context.fillStyle = color;
  // 添加空格模拟书信首行缩进
  if (type === 'content') {
    wordsArr.unshift(' ', ' ', ' ', ' ');
  }
  // 设置书信落款top值
  if (type === 'from') {
    top = baseFromLine;
  }
  // canvas书写文字
  for (let n = 0; n < wordsArr.length; n++) {
    let testLine = line + wordsArr[n];
    let metrics = context.measureText(testLine);
    let testWidth = metrics.width;
    if (testWidth > maxWidth && n > 0) {
      context.fillText(line, left, top);
      line = wordsArr[n];
      top += lineHeight;
    } else {
      line = testLine;
    }
  }
  // 记录书信内容最后位置计算落款位置
  if (type === 'content') {
    baseFromLine = top + 43;
  }
  context.fillText(line, left, top);
}

方法调用

const text = [{
  type: 'title',
  words: '给你的信',
  top: 432,
  left: 375,
  maxWidth: 390,
  lineHeight: 35,
  textAlign: 'center',
  color: '#db6502',
  font: 'bold 30px 黑体'
}, {
  type: 'to',
  words: '你好小白:',
  top: 470,
  left: 180,
  maxWidth: 390,
  lineHeight: 43,
  textAlign: 'left',
  color: '#db6502',
  font: 'normal 18px 黑体'
}, {
  type: 'content',
  words: '祝福是柔的夏的是清的雨秋的祝福是晶的露冬的祝福是白的雪我的祝福是真的心缘相遇,情相知,心相惜,这份真挚的友谊,让我真的好感动!感谢一路有您,感谢我们相扶相携走过的快乐时光。这份友谊我会永远铭记在心,感恩在心!',
  top: 505,
  left: 180,
  maxWidth: 390,
  lineHeight: 43,
  textAlign: 'left',
  color: '#db6502',
  font: 'normal 18px 黑体'
}, {
  type: 'from',
  words: 'by 小黑',
  top: 250,
  left: 570,
  maxWidth: 390,
  lineHeight: 43,
  textAlign: 'right',
  color: '#db6502',
  font: 'normal 18px 黑体'
}]

for (let i = 0; i < text.length; i++) {
  drawText(text[i]);
}