HYN慢慢琢磨一些技术。。。
首页/文章列表/Canvas 绘制海报的一些经验/
Canvas 绘制海报的一些经验
2021-01-15 04:47:15 Web 91人阅读

Canvas坐标系

原点位于左上角,X轴向右为正,Y轴向下为正。

绘制图片到canvas

drawImage,参考https://www.w3school.com.cn/html5/canvas_drawimage.asp

注意,图片需要先加载完成,才能绘制到canvas,建议采用以下方法

var bgImg = new Image();
bgImg.src = 'images/background.jpg';
bgImg.onload = function(){
    // 图片加载完成后方可绘制
  ctx1.drawImage(bgImg,0,0,绘制宽度,绘制高度);
}

在canvas中剪裁

本处例子为将图片裁剪为一个圆

ctx.save(); // 保存裁剪之前的Canvas状态,用于结束裁剪后恢复 
var d = 2 * r;
var cx = x + r;
var cy = y + r;
ctx.arc(cx, cy, r, 0, 2 * Math.PI); // 绘制裁剪路径
ctx.clip(); // 将当前路径设置为裁剪路径
ctx.drawImage(img, x, y, d, d); // 绘制被裁剪图片
ctx.restore(); // 恢复裁剪前Canvas状态,结束裁剪

绘制文字到canvas

ctx.font = `normal normal 11px "sans-serif"`; // 文字样式
ctx.fillStyle = "#ffffff"; // 文字颜色
ctx.fillText(words, 起点X坐标, 起点Y坐标); // 

canvas绘制多行文字

需要自己处理换行、溢出等状况,给一个例子:

// 绘制N行文字,第二行开始填满canvas宽度,可设置左右padding,第一行可设置起点坐标。第二行溢出部分以省略号结尾。
/**
 *
 * @param ctx 2d上下文对象
 * @param text 绘制文本
 * @param x 坐标轴x位置
 * @param y 坐标轴y位置
 * @param options 包含 maxWidth 最大宽度,lineHeight 文字行高,row 限制行数,textIndent 首行缩进,fontSize 文字大小
 */
function textEllipsis(ctx, text, x, y, options) {
  ctx.save();
  if (
    typeof text !== 'string' ||
    typeof x !== 'number' ||
    typeof y !== 'number'
  ) {
    return;
  }
  let defaultOpt = {
    maxWidth: 100, // 输出多行文字最大高度
    lineHeight: 14, // 行高
    row: 1000, // 最多行数
    textIndent: 0, // 首行缩进
    fontSize: 14, // 字体大小
  };
  let params = Object.assign({}, defaultOpt, options);
  // 分割文本
  let textArr = text.split('');
  // 文本最终占据高度
  let textHeight = 0;
  // 每行显示的文字
  let textOfLine = '';
  // 控制行数
  let limitRow = params.row;
  let rowCount = 0;
  // 循环分割的文字数组
  for (let i = 0; i < textArr.length; i++) {
    // 获取单个文字或字符
    let singleWord = textArr[i];
    // 连接文字
    let connectText = textOfLine + singleWord;
    // 计算接下来要写的是否是最后一行
    let isLimitRow = limitRow ? rowCount === limitRow - 1 : false;
    // 最后一行则显示省略符,否则显示连接文字
    let measureText = isLimitRow ? connectText + '...' : connectText;
    console.log(measureText);
    // 设置字体并计算宽度,判断是否存在首行缩进
    ctx.font = `normal 500 ${params.fontSize}px "sans-serif"`;
    let width = ctx.measureText(measureText).width; // 文字宽度
    // 首行需要缩进满足条件
    let conditionIndent = params.textIndent && rowCount === 0; // 判断是否需要缩进
    let measureWidth = conditionIndent ? width + params.textIndent : width; // 根据缩进情况设置行数
    // 大于限制宽度且已绘行数不是最后一行,则写文字
    // 大于宽度限制表示可以绘制
    if (measureWidth > params.maxWidth && i > 0 && rowCount !== limitRow) {
      // 如果是最后一行,显示计算文本
      measureText = measureText.slice(0, -4) + '...'; // 删除最后一个字符
      let canvasText = isLimitRow ? measureText : textOfLine;
      let xPos = conditionIndent ? x + params.textIndent : x; // 行首X轴坐标
      // 写文字
      ctx.fillStyle = '#000';
      console.log(666666666666);
      ctx.fillText(canvasText, xPos, y);
      // 下一行文字
      textOfLine = singleWord;
      // 记录下一行位置
      y += params.lineHeight;
      // 计算文本高度
      textHeight += params.lineHeight;
      rowCount++; // 绘制文字后行数加1

      if (isLimitRow) {
        // 行数到达限制
        break;
      }
    } else {
      // 不大于最大宽度,继续拼接
      textOfLine = connectText;
    }
  }
  if (rowCount !== limitRow) {
    // 单行文字走这边
    let xPos = params.textIndent && rowCount === 0 ? x + params.textIndent : x;
    ctx.fillStyle = '#000';
    console.log('989898989898');
    ctx.fillText(textOfLine, xPos, y);
  }
  // 计算文字总高度
  let textHeightVal = rowCount < limitRow ? params.lineHeight : textHeight;
  ctx.restore();
  return textHeightVal;
}

canvas设置底色

先绘制边框,再使用fill方法进行填充,给出一个绘制圆角矩形的例子:

// canvasContext, x坐标,y坐标,宽度,高度,圆角半径
function roundRect(ctx, x, y, width, height, radius, fill, stroke) {
  ctx.save();
  ctx.lineWidth = 1;
  if (typeof stroke === 'undefined') {
    stroke = true;
  }
  if (typeof radius === 'undefined') {
    radius = 5;
  }
  if (typeof radius === 'number') {
    radius = { tl: radius, tr: radius, br: radius, bl: radius };
  } else {
    var defaultRadius = { tl: 0, tr: 0, br: 0, bl: 0 };
    for (var side in defaultRadius) {
      radius[side] = radius[side] || defaultRadius[side];
    }
  }
  // 绘制边框
  ctx.beginPath();
  ctx.moveTo(x + radius.tl, y); // 第一段弧线的起点
  ctx.lineTo(x + width - radius.tr, y);
  ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr); // 圆角
  ctx.lineTo(x + width, y + height - radius.br);
  ctx.quadraticCurveTo(
    x + width,
    y + height,
    x + width - radius.br,
    y + height
  );
  ctx.lineTo(x + radius.bl, y + height);
  ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
  ctx.lineTo(x, y + radius.tl);
  ctx.quadraticCurveTo(x, y, x + radius.tl, y);
  ctx.closePath();
  if (fill) {
    // 填充底色
    ctx.fillStyle = '#F74B57';
    ctx.fill();
  }
  if (stroke) {
    // 填充边框颜色
    ctx.strokeStyle = '#F74B57';
    ctx.stroke();
  }
  ctx.restore();
}

保存Canvas到本地

function exportCanvasAsPNG(id, fileName) {
    var canvasElement = document.getElementById(id);
    var MIME_TYPE = "image/png";
    var imgURL = canvasElement.toDataURL(MIME_TYPE);
      var blob = dataURLtoBlob(imgURL);
    var objURL = URL.createObjectURL(blob);
    var link = document.createElement("a");
      link.download = fileName + ".png";
    link.href = objURL;
    link.click();
  
      function  dataURLtoBlob(dataurl: string) {
        var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
          bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
        while(n--){
              u8arr[n] = bstr.charCodeAt(n);
        }
        return new Blob([u8arr], {type:mime});
      }
}
前端~
文章目录
Canvas坐标系
绘制图片到canvas
在canvas中剪裁
绘制文字到canvas
canvas绘制多行文字
canvas设置底色
保存Canvas到本地