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);
名稱 | 作用 |
---|---|
img | 用來被繪制的圖像、畫布或視頻 |
sx | 可選。img 被繪制區域的起始左上 x 坐標 |
sy | 可選。img 被繪制區域的起始左上 y 坐標 |
swidth | 可選。img 被繪制區域的寬度(如果沒有后面的 width 或 height 參數,則可以伸展或縮小圖像) |
sheight | 可選。img 被繪制區域的高度(如果沒有后面的 width 或 height 參數,則可以伸展或縮小圖像) |
x | 畫布上放置 img 的起始 x 坐標 |
y | 畫布上放置 img 的起始 y 坐標 |
width | 可選。畫布上放置 img 提供的寬度(可能會有圖片剪裁效果) |
height | 可選。畫布上放置 img 提供的高度(可能會有圖片剪裁效果) |
使用 drawImage 進行切片 drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight),這個在圖片合成的時候是利器,該實現中沒用過(可以不用美工切好圖再用,而是前端直接自己切圖)
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);
效果,中神通呢
一個合成圖片文字要求
需要做到
- 能夠將多張圖片進行合成
- 中間能夠插入文字
- 按照順序進行畫圖,上層圖層會蓋住下層
問題
- 圖片加載完成事件是個異步的,文字同步畫在畫板上
- onload 和 onerror 事件
- 本地解決圖片加載問題
我是在 vue 項目里面用(為了能夠在沒有任何編譯環境下用,我沒用 es6,如果是在 es6 中寫這種 compose 可以用 class, promise , async 配合使用,要舒服很多)
amd,cmd,common 和 js 原生目前實現的模塊不一樣,是不兼容的,能夠直接用 import 引入是 webpack 環境的功勞
可以直接復制粘貼上面的代碼建個文件測試,也可以 npm install canvas-compose-image --save
實現的效果
合成了一張圖片,在美化下字體就 ok 了,可以直接下載在手機相冊中。
項目圖還是去掉了,怕收到律師函
錯誤信息 Failed to execute 'drawImage'
Uncaught TypeError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLImageElement or SVGImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap or OffscreenCanvas)'
你在使用 drawImage 方法的時候使用了不正確的元素,不如圖片沒有加載完或者沒獲取到正確的,是個 undefined,就會報錯。可以試試將第一個參數設為 window,就會直接報錯。
需要的節點在錯誤信息已經明確說了
錯誤信息 Failed to execute 'toDataURL' on 'HTMLCanvasElement'
Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
依然是老朋友,跨域的問題,別雙擊 html 打開就行了,撘個靜態文件服務器就好了。demo 里面有個 server.js,用 node 啟動一下就好了。
錯誤信息 onload 和 onerror 都執行
沒解決,只要有 onerror 一定會執行 onerror,注釋掉就執行 onload,沒明白
npm 發布插件步驟
- npm init
- 添加過濾文件
- 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"
]
}
Docs
MDN - canvas
使用 canvas 在前端實現圖片水印合成
CSS3 混合模式 mix-blend-mode/background-blend-mode 簡介
npm publish
canvas.toDataURL() SecurityError
瀏覽器同源政策及其規避方法
canvas 問題淺析
使用canvas在前端實現圖片水印合成
JS判斷單、多張圖片加載完成