一、canvas介紹
屬於一個可以使用JS腳本繪製圖像的HTML元素
可以爲canvas設置class屬性定義其長寬(替代默認的300px*150px)
注意: 該元素可以使用CSS來定義大小,但在繪製時圖像會伸縮以適應它的框架尺寸:如果CSS的尺寸與初始畫布的比例不一致,它會出現扭曲。
名詞解釋
畫布
如:
// 通過使用 document.getElementById() 方法來爲 <canvas> 元素得到DOM對象
let canvas = document.getElementById('tutorial');
這裏的canvas就是畫布
渲染上下文
可以理解爲“畫筆”
如:
// 使用它的getContext() 方法來訪問繪畫上下文
let ctx = canvas.getContent('2d');
getContext()只有一個參數,上下文的格式
方法
drawImage(image, x, y)
void ctx.drawImage(image, dx, dy);
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
二、代碼
- DEMO-1
html:
<canvas id="canvas"></canvas>
<div style="display:none;">
<img id="source" src="https://mdn.mozillademos.org/files/5397/rhino.jpg" width="300" height="227">
</div>
js:
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
let image = document.getElementById('source');
ctx.drawImage(image, 33, 71, 104, 124, 21, 20, 87, 104);
- DEMO-2 圖片合成(正戲上演)
此示例雖好,但是有一個問題,就是緩存的圖片,再重新進入頁面時會出現白屏問題,如果出現這個問題,請參照下個標題的‘DEMO-3’。
vue:
<template>
<img id="merge" class="share-container"/>
<!-- 背景圖片不展示 -->
<div style="display:none;">
<img id="source" src="./backgroundImage3.png">
</div>
</template>
/* 放置合成圖片的尺寸 */
.share-container {
width: 353.5px;
height: 525px;
}
<script>
export default {
mounted() {
// HACK:加上延時避免 mounted 方法比頁面加載早執行
this.$nextTick(() => {
setTimeout(() => {
this.onCreateCanvas();
}, 100);
});
},
methods: {
/** 根據鏈接生成動態二維碼(base64格式) */
async generateQR() {
let url = 'https://www.baidu.com/'; // 要生成二維碼的文本
try {
let base64QRCode = await QRCode.toDataURL(url, {
margin: 0 // 生成的二維碼去除白邊框
});
return base64QRCode;
} catch (err) {
console.error(err);
}
},
/** 生成合成圖片 */
async onCreateCanvas() {
// 圖片尺寸常量
const backImgWidth = 1000;
const backImgHeight = 1000;
const qrcodeX = 300;
const qrcodeY = 600;
const qrcodeWidth = 300;
const qrcodeHeight = 300;
// base64位格式二維碼
let base64QRCode = await this.generateQR();
let image = document.getElementById('source'); // 背景圖片
// 準備畫布,設置長寬
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
canvas.width = backImgWidth; // 設置背景圖片長
canvas.height = backImgHeight; // 設置背景圖片寬
// qrcode圖片初始化
let qrcodeImg = new Image();
qrcodeImg.onload = function() {
ctx.drawImage(image, 0, 0); // 設置背景圖片
ctx.drawImage(qrcodeImg, qrcodeX, qrcodeY, qrcodeWidth, qrcodeHeight); // X座標,Y座標,二維碼長,二維碼寬
let pageTheme = document.getElementById('theme');
pageTheme.src = canvas.toDataURL(); // 因爲微信圖片長按才能分享給朋友,canvas長按是無法分享給朋友的。
};
qrcodeImg.src = base64QRCode;
}
}
}
</script>
- DEMO-3 多張背景圖合成多張圖片
爲了避免頁面加載完畢背景圖片還沒加載完畢,或者圖片早已緩存成功的問題,就需要用到 image.complete 進行判斷,同時也不需要在vue生命週期 mounted 內執行 this.$nextTick 操作。
什麼你問爲什麼使用img加載的方式,那就說來話長了(別動手),因爲我們的項目部署在騰訊雲cos上,所以這裏使用toDataURL就涉及到了跨域問題,怎麼配置cos都不起作用,只好轉而使用服務器資源上的圖片。這些圖片是存放於static文件夾內的。
<img
v-for="(item, index) in list"
:key="index"
class="head-img"
:src="item"
>
<div v-show="false">
<img ref="backgroundImg1" src="imgSrc1" />
<img ref="backgroundImg2" src="imgSrc2" />
<img ref="backgroundImg3" src="imgSrc3" />
</div>
<style lang="stylus" scoped>
// author: simorel
.head-img
width 200px
height 200px
</style
mounted() {
this._getImageList();
},
methods: {
/** 獲取圖像列表 */
_getImageList() {
this.qrcodeUrl = 'https://www.baidu.com'; // 要生成二維碼圖片的鏈接
this._createCanvas();
},
/** 根據鏈接生成動態二維碼(base64) */
async _getGenerateQR() {
let base64QRCode = '';
try {
base64QRCode = await QRCode.toDataURL(this.qrcodeUrl, {
margin: 0 // 無白邊框的二維碼圖片
});
} catch (err) {
console.error('_getGenerateQR()', '生成二維碼失敗', err);
}
return base64QRCode;
},
/** 生成合成圖片 */
async _createCanvas() {
// base64位格式二維碼
let base64QRCode = await this._getGenerateQR();
this._getImageLoadState(base64QRCode, this.$refs.backgroundImg1);
this._getImageLoadState(base64QRCode, this.$refs.backgroundImg2);
this._getImageLoadState(base64QRCode, this.$refs.backgroundImg3);
},
/**
* 判斷圖片加載狀態!!!
* 判斷圖片加載狀態!!!
* 判斷圖片加載狀態!!!
* 重要的事情說三遍。。。。。。
*/
_getImageLoadState(base64QRCode, backgroundImg) {
let qrcodeImg = new Image();
qrcodeImg.src = base64QRCode;
if (backgroundImg.complete) { // 如果圖片已經存在於瀏覽器緩存,直接調用回調函數
if (qrcodeImg.complete) {
this._drawImage(qrcodeImg, backgroundImg);
} else {
qrcodeImg.onload = () => {
this._drawImage(qrcodeImg, backgroundImg);
};
}
} else {
backgroundImg.onload = () => {
if (qrcodeImg.complete) {
this._drawImage(qrcodeImg, backgroundImg);
} else {
qrcodeImg.onload = () => {
this._drawImage(qrcodeImg, backgroundImg);
};
}
};
}
},
/** 繪製帶二維碼的背景圖片 */
_drawImage(qrcodeImg, backgroundImg) {
// 圖片尺寸常量
const backImgWidth = 750;
const backImgHeight = 1200;
const qrcodeX = 325;
const qrcodeY = 500;
const qrcodeWidth = 249;
const qrcodeHeight = 249;
// 準備畫布,設置長寬
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
canvas.width = backImgWidth; // 背景圖片長
canvas.height = backImgHeight; // 背景圖片寬
ctx.drawImage(backgroundImg, 0, 0); // 設置背景圖片
ctx.drawImage(qrcodeImg, qrcodeX, qrcodeY, qrcodeWidth, qrcodeHeight); // X座標,Y座標,二維碼長,二維碼寬
this.list.push(canvas.toDataURL());
}
}
- DEMO-4 完全使用CDN上的圖片
經過我一番不懈努(bai)力(du),我找到了這篇神文,大神就是大神孤獨的二向箔-canvas圖片問題淺析,其實配置我之前的實驗配置跨域起作用了,但是CDN上面的緩存導致我拿不到圖片,綜合來說需要兩方面配置:
設置圖片跨域:img.setAttribute(‘crossOrigin’, ‘anonymous’);
圖片若加載失敗,添加時間戳後綴再試一次:
img.onerror = (err) => {
let timeStamp = new Date().getTime();
img.src = `${src}?${timeStamp}`;
}
還有最最重要的一點CDN必須配置相關跨域規則,被自己蠢哭了
具體就是修改DEMO-3中的部分代碼即可。接下來我只展示不同點:
html(移除之前v-show=false的部分)
<img
v-for="(item, index) in list"
:key="index"
class="head-img"
:src="item"
js(修改一下 _createCanvas 函數)
/** 生成合成圖片 */
async _createCanvas() {
// base64位格式二維碼
let base64QRCode = await this._getGenerateQR();
this._getImageLoadState(base64QRCode, this._requestImg(require('./backgroundImg1.png'))); // 這裏require的其實實際上是CDN上的圖片
this._getImageLoadState(base64QRCode, this._requestImg(require('./backgroundImg2.png')));
this._getImageLoadState(base64QRCode, this._requestImg(require('./backgroundImg3.png')));
},
/** 請求COS圖片 */
_requestImg(src) {
// [canvas圖片問題淺析](https://www.jianshu.com/p/c3aa975923de)
let img = new Image();
img.setAttribute('crossOrigin', 'anonymous'); // 使得瀏覽器允許跨域
img.src = src;
img.onerror = (err) => {
let timeStamp = new Date().getTime();
img.src = `${src}?${timeStamp}`;
}
return img;
},