[JavaScript] canvas 合成圖片和文字
Canvas
Canvas 是 HTML5 新增的元件,就像一個畫板,用 js 這杆筆,在上面亂塗亂畫
建立一個 canvas
<canvas id="stockGraph" width="150" height="150"></canvas> 或 let canvas = document.createElement("canvas");
渲染上下文
使用 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" ] }