前端图片合成
如何合成一个二维码和背景图片:canvas,但是因为涉及到微信长按分享图片,所以不能使用canvas
放置图片,只能使用<img>
标签盛放canvas合成的图片。
文章目录
一、canvas介绍
<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)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6O0Skp30-1585535469974)(https://mdn.mozillademos.org/files/225/Canvas_drawimage.jpg)]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);
参数名 是否必填 类型 含义 image 是 CSSImageValue,HTMLImageElement,SVGImageElement,HTMLVideoElement,HTMLCanvasElement,ImageBitmap, OffscreenCanvas 绘制到上下文的元素 sx 否 number 需要绘制到目标上下文中的,image的矩形(裁剪)选择框的左上角 X 轴坐标。 sy 否 number 需要绘制到目标上下文中的,image的矩形(裁剪)选择框的左上角 Y 轴坐标。 sWidth 否 number 需要绘制到目标上下文中的,image的矩形(裁剪)选择框的宽度。如果不说明,整个矩形(裁剪)从坐标的sx和sy开始,到image的右下角结束。 sHeight 否 number 需要绘制到目标上下文中的,image的矩形(裁剪)选择框的高度。 dx 是 number image的左上角在目标canvas上 X 轴坐标。 dy 是 number image的左上角在目标canvas上 Y 轴坐标。 dWidth 否 number image在目标canvas上绘制的宽度。 允许对绘制的image进行缩放。 如果不说明, 在绘制时image宽度不会缩放。 dHeight 否 number image在目标canvas上绘制的高度。 允许对绘制的image进行缩放。 如果不说明, 在绘制时image高度不会缩放。
二、代码
1. 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);
2. 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>
3. 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());
}
}
4. 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; },
参考链接
[1] MDN Canvas教程
[2] 运动猿 VUE项目中解决mounted 钩子函数执行时 img 未加载导致页面布局问题
[3] MDN 使用图像Using images
[4] MDN drawImage
[5] Simorel 生成qrcode