前端图片合并

51 篇文章 0 订阅
35 篇文章 0 订阅

前端图片合成

如何合成一个二维码和背景图片:canvas,但是因为涉及到微信长按分享图片,所以不能使用canvas放置图片,只能使用<img>标签盛放canvas合成的图片。



一、canvas介绍

<canvas>属于一个可以使用JS脚本绘制图像的HTML元素
可以为canvas设置class属性定义其长宽(替代默认的300px*150px)

注意: 该元素可以使用CSS来定义大小,但在绘制时图像会伸缩以适应它的框架尺寸:如果CSS的尺寸与初始画布的比例不一致,它会出现扭曲。

名词解释

  1. 画布
    如:

    // 	通过使用 document.getElementById() 方法来为 <canvas> 元素得到DOM对象
    let canvas = document.getElementById('tutorial');
    

    这里的canvas就是画布

  2. 渲染上下文
    可以理解为“画笔”
    如:

    // 使用它的getContext() 方法来访问绘画上下文
    let ctx = canvas.getContent('2d');
    

    getContext()只有一个参数,上下文的格式

方法

  1. 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);
    
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6O0Skp30-1585535469974)(https://mdn.mozillademos.org/files/225/Canvas_drawimage.jpg)]
    参数名是否必填类型含义
    imageCSSImageValue,HTMLImageElement,SVGImageElement,HTMLVideoElement,HTMLCanvasElement,ImageBitmap, OffscreenCanvas绘制到上下文的元素
    sxnumber需要绘制到目标上下文中的,image的矩形(裁剪)选择框的左上角 X 轴坐标。
    synumber需要绘制到目标上下文中的,image的矩形(裁剪)选择框的左上角 Y 轴坐标。
    sWidthnumber需要绘制到目标上下文中的,image的矩形(裁剪)选择框的宽度。如果不说明,整个矩形(裁剪)从坐标的sx和sy开始,到image的右下角结束。
    sHeightnumber需要绘制到目标上下文中的,image的矩形(裁剪)选择框的高度。
    dxnumberimage的左上角在目标canvas上 X 轴坐标。
    dynumberimage的左上角在目标canvas上 Y 轴坐标。
    dWidthnumberimage在目标canvas上绘制的宽度。 允许对绘制的image进行缩放。 如果不说明, 在绘制时image宽度不会缩放。
    dHeightnumberimage在目标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 多张背景图合成多张图片

  1. 为了避免页面加载完毕背景图片还没加载完毕,或者图片早已缓存成功的问题,就需要用到 image.complete 进行判断,同时也不需要在vue生命周期 mounted 内执行 this.$nextTick 操作。

  2. 什么你问为什么使用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

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值