Canvas

Canvas 是 HTML5 新增的组件,就像一个画板,用 js 这杆笔,在上面乱涂乱画

创建一个 canvas

<canvas id="stockGraph" width="150" height="150"></canvas>
或
let canvas = document.createElement("canvas");

渲染上下文

CanvasRenderingContext2D
使用 canvas.getContext('2d')方法让我们拿到一个 CanvasRenderingContext2D 对象,然后在这个上面画

//用getContext()判断是否支持canvas
if(canvas.getContext){
  let context = canvas.getContext('2d');
}

canvas 绘制文字

  • fillStyle = color 设置图形的填充颜色。
  • fillText(text,x,y,[, maxWidth]) 在指定的(x,y)位置填充指定的文本,绘制的最大宽度是可选的.
  • strokeText(text, x, y [, maxWidth]) 在指定的(x,y)位置绘制文本边框,绘制的最大宽度是可选的.

设置 font,同 css 的 fong 属性

context.font = "italic 1.2em "Fira Sans", serif";

css 的 font 属性是设置 font-style, font-variant, font-weight, font-size, line-height 和 font-family 属性的简写,或使用特定的关键字设置元素的字体为某个系统字体。

canvas 绘制图片

context.drawImage(img,x,y);
context.drawImage(img,x,y,width,height); //其中 image 是 image 或者 canvas 对象,x 和 y 是其在目标 canvas 里的起始坐标,width 和 height,这两个参数用来控制 当向 canvas 画入时应该缩放的大小
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);

canvas.toDataURL()

  • 如果该 canvas 的宽度或长度是 0,则会返回字符串"data:,".
  • 如果指定的 type 参数不是 image/png,但返回的字符串是以 data:image/png 开头的,则所请求的图片类型不支持.
  • Chrome 支持 image/webp 类型.
  • 如果 type 参数的值为 image/jpeg 或 image/webp,则第二个参数的值如果在 0.0 和 1.0 之间的话,会被看作是图片质量参数,如果第二个参数的值不在 0.0 和 1.0 之间,则会使用默认的图片质量.

一个在线的精简版 ps 直接网页打开就可以用了http://www.uupoop.com/

img 标签的 onError,onLoad,onAbort

onError:当图片加载出现错误,会触发 经常在这里事件里头写入 将图片导向默认报错图片,以免页面上出现红色的叉叉

onLoad:事件是当图片加载完成之后触发

onAbort:图片加载的时候,用户通过点击停止加载(浏览器上的红色叉叉)时出发,通常在这里触发一个提示:“图片正在加载”

先准备一张图

然后用 ps 切成 4 张图,分别的是东邪西毒南帝北丐

合成 canvas 为图片的函数

compose.html

...
 <div class="img-list">
      <div><img class="compose" src="./img/timg-(1)_01.png" alt="" /></div>
      <div><img class="compose" src="./img/timg-(1)_02.png" alt="" /></div>
      <div><img class="compose" src="./img/timg-(1)_03.png" alt="" /></div>
      <div><img class="compose" src="./img/timg-(1)_04.png" alt="" /></div>
  </div>
...

<script type="text/javascript">
    window.onload = function() {
      var compose = document.querySelectorAll(".compose");
      var src_arr = [];
      compose.forEach(node => {
        src_arr.push(node.src);
      });

      ComposeCanvas.compose(
        [400, 400],
        [
          {
            src: src_arr[0],
            type: "image",
            mode: "waiting",
            pos: [200, 0, 200, 200]
          },
          {
            src: src_arr[1],
            type: "image",
            mode: "waiting",
            pos: [200, 200, 200, 200]
          },
          {
            src: src_arr[2],
            type: "image",
            mode: "waiting",
            pos: [0, 0, 200, 200]
          },
          {
            src: src_arr[3],
            type: "image",
            mode: "waiting",
            pos: [0, 200, 200, 200]
          },
          {
            text: "我是南帝",
            type: "text",
            mode: "waiting",
            pos: [50, 100],
            style: {
              color: "#333",
              font: "30px serif"
            }
          },
          {
            text: "我是北丐",
            type: "text",
            mode: "waiting",
            pos: [0, 320],
            style: {
              color: "#333",
              font: "40px serif"
            }
          },
          {
            text: "中神通呢?",
            type: "text",
            mode: "waiting",
            pos: [120, 220],
            style: {
              color: "#333",
              font: "40px serif"
            }
          }
        ],
        function(img) {
          console.log(img);
          document.getElementById("hello").appendChild(img);
        }
      );
    };
  </script>

compose.js

(function(win) {
  var ComposeCanvas,
    layer = [],
    callback,
    imgList = [],
    canvasWH = [200, 200];
  var canvas = document.createElement("canvas");
  var context = canvas.getContext("2d");

  function backElement(back) {
    var $return;
    switch (back) {
      case "canvas":
        $return = canvas;
        break;
      case "image":
        $return = convertCanvasToImage(canvas);
        break;
      default:
        $return = canvas;
    }
    return $return;
  }

  /**
   * 用来处理宽高和图层,返回合成结果
   * @param {*} WH canvas的宽高
   * @param {*} options canvas的图层
   */
  function compose(WH, options, backEvent) {
    callback = backEvent;
    _handleWH(WH);
    _handleLayer(options);
  }

  function convertCanvasToImage(c) {
    c = c || canvas;
    var image = new Image();
    var src = c.toDataURL("image/png");
    image.setAttribute("src", src);
    image.setAttribute("crossOrigin", "anonymous");
    return image;
  }

  function _handleWH(WH) {
    canvasWH = WH || [200, 200];
    canvas.width = canvasWH[0];
    canvas.height = canvasWH[1];
    //https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D
    context.fillStyle = "transparent"; //画布填充颜色;
    context.fillRect(0, 0, canvasWH[0], canvasWH[1]);
  }

  function _handleLayer(options) {
    if (Object.prototype.toString.call(options) === "[object Array]") {
      layer = options;
    }

    var length = layer.length;

    for (var i = 0; i < length; i++) {
      if (layer[i]["type"] === "text") {
        layer[i]["mode"] = "complete";
        _checkLayerMode();
      }

      if (layer[i]["type"] === "image") {
        var pos = layer[i].pos;
        var src = layer[i].src;
        imgList[i] = new Image(pos[2], pos[3]);
        imgList[i].setAttribute("src", src);

        imgList[i].onload = (function() {
          _loadedImg(i);
        })();

        // imgList[i].onerror = (function() {
        //   _errorImg(i);
        // })();
      }
    }
  }

  function _loadedImg(index) {
    //https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage
    layer[index]["mode"] = "complete";
    layer[index]["element"] = imgList[index];
    _checkLayermode();
  }

  // function _errorImg(i) {
  //   console.log("图片加载出错");
  //   layer[i]["mode"] = "error";
  //   _checkLayermode();
  // }

  function _checkLayerMode() {
    var check = false;
    var current = 0;
    layer.forEach(function(item) {
      if (item.mode === "complete") {
        current++;
      }
      if (current === layer.length) {
        check = true;
      }
    });

    check && _draw();
  }

  function _draw() {
    layer.forEach(function(item) {
      if (item["type"] === "text") {
        context.fillStyle = item.style.color;
        context.font = item.style.font;
        context.fillText(item.text, item.pos[0], item.pos[1]);
      }
      if (item["type"] === "image" && item["element"]) {
        context.drawImage(item["element"], item.pos[0], item.pos[1], item.pos[2], item.pos[3]);
      }
    });

    var img = backElement("image");
    return callback && callback(img);
  }

  ComposeCanvas = {
    compose: compose,
    convert: convertCanvasToImage
  };

  if (typeof module !== `undefined` && typeof exports === `object`) {
    module.exports = ComposeCanvas; //commonjs
  } else if (typeof win.define === "function" && (win.define.amd || win.define.cmd)) {
    win.define("ComposeCanvas", [], function() {
      return ComposeCanvas;
    });
  } else {
    win.ComposeCanvas = ComposeCanvas;
  }
})(window);

效果,中神通呢

一个合成图片文字要求

需要做到

  1. 能够将多张图片进行合成
  2. 中间能够插入文字
  3. 按照顺序进行画图,上层图层会盖住下层

问题

  1. 图片加载完成事件是个异步的,文字同步画在画板上
  2. onload 和 onerror 事件
  3. 本地解决图片加载问题

我是在 vue 项目里面用(为了能够在没有任何编译环境下用,我没用 es6,如果是在 es6 中写这种 compose 可以用 class, promise , async 配合使用,要舒服很多)

amd,cmd,common 和 js 原生目前实现的模块不一样,是不兼容的,能够直接用 import 引入是 webpack 环境的功劳

可以直接复制粘贴上面的代码建个文件测试,也可以 npm install canvas-compose-image --save

实现的效果

合成了一张图片,在美化下字体就 ok 了,可以直接下载在手机相册中。

错误信息 Failed to execute 'drawImage'

你在使用 drawImage 方法的时候使用了不正确的元素,不如图片没有加载完或者没获取到正确的,是个 undefined,就会报错。可以试试将第一个参数设为 window,就会直接报错。

需要的节点在错误信息已经明确说了

错误信息 Failed to execute 'toDataURL' on 'HTMLCanvasElement'

依然是老朋友,跨域的问题,别双击 html 打开就行了,撘个静态文件服务器就好了。demo 里面有个 server.js,用 node 启动一下就好了。

错误信息 onload 和 onerror 都执行

没解决,只要有 onerror 一定会执行 onerror,注释掉就执行 onload,没明白

npm 发布插件步骤

  1. npm init
  2. 添加过滤文件
  3. npm publish

黑名单模式:.npmignore 文件,没有.npmignore 情况下使用.gitignore 文件。(.gitignore 优先级会高些,小心)

跟.gitignore 一样

白名单模式:package.json 里边配置 files 字段

"files": [
  "LICENSE",
  "History.md",
  "Readme.md",
  "index.js",
  "lib/"
]

白名单模式:pkg.files 配置 files 字段,只发布配置的文件或目录

{
  "files": [
      "index.js",
        "lib"
    ]
}

参考

MDN - canvas
使用 canvas 在前端实现图片水印合成
CSS3 混合模式 mix-blend-mode/background-blend-mode 简介
npm publish
canvas.toDataURL() SecurityError
浏览器同源政策及其规避方法
canvas 问题浅析

02-27 02:20