最近工作中碰到一个需求,实现一个自定义的二维码
- 背景是一张自定义的图片
- 二维码居中展示
- 在微信里长按可识别
类似于下图
经过一番查资料和思考,整理如下
drawImage
canvas可以用这个方法讲一个图片或者视频画入画布中,drawImage函数有三种函数原型
drawImage(image, dx, dy)
drawImage(image, dx, dy, dw, dh)
drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
第一个参数image可以用HTMLImageElement,HTMLCanvasElement或者HTMLVideoElement作为参数。dx和dy是image在canvas中定位的坐标值;dw和dh是image在canvas中即将绘制区域(相对dx和dy坐标的偏移量)的宽度和高度值;sx和sy是image所要绘制的起始位置,sw和sh是image所要绘制区域(相对image的sx和sy坐标的偏移量)的宽度和高度值
一句话概括就是sx,sy是在原图上剪切的起始坐标点,sh,sw则是剪切的高宽.
而dx,dy就是把剪切的图放到画布中的位置,dh,dw则是放到画布的高宽.
说完了概念,现在来实操
需求一
先在html上放个canvas标签
<canvas id="myCanvas" class="my-canvas"></canvas>
然后通过canvas的API创建context对象
var c = document.getElementById ("myCanvas");
var ctx = c.getContext ("2d");
将背景图片绘入
var img = document.getElementById ("bgc");
ctx.drawImage (
img, //图片资源
0, 0, //截取图片的坐标点
img.width, img.height, //截取图片的宽高,这里是原宽高
0, 0, //放入画布中的坐标点
ctx.canvas.width, ctx.canvas.height //截取的图片放入画布的宽高 = 画布的宽高
);
上面的最后两个参数ctx.canvas.width, ctx.canvas.height
的作用是让截取的图片拉伸为画布大小,这样就填充了整个画布.
好了,到此,我们完成了第一个需求.
需求二
先生成一个二维码
var code = $ ('#qrcode').qrcode ({
text: "www.baidu.com",
render: "canvas",
width: 120,
height: 120,
typeNumber: -1,
background: '#FFFFFF',
foreground: '#000000'
});
接下来要怎么样把二维码居中放入画布呢?
我们先来思考绝对定位居中是怎么实现的
<div class="father">
<div class="sone"></div>
</div>
.father{
height:100px:
width:100px;
border:1px solid red;
}
.son{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
可以得出,先把son往下往右移动50%father的宽度和高度.再把son自己往上往左移动自己50%的宽度和高度.就可以实现居中.
同理,canvas的二维码居中也是可以这样实现的
ctx.drawImage (
qrcode,
0, 0,
qrcode.width, //二维码宽度
qrcode.height, //二维码高度
Math.abs (ctx.canvas.width - ctx.canvas.width * 0.5) * 0.5, //放入画布中的起始x坐标
Math.abs (ctx.canvas.height - ctx.canvas.height * 0.5) * 0.5, //放入画布中的起始y坐标
ctx.canvas.width * 0.5, //放入画布中的宽
ctx.canvas.height * 0.5 //放入画布中的高
);
我们用同样的计算方式可以获得放入画布中坐标点,这样就可以让二维码居中了
在微信里长按可识别
canvas得到的图片在微信里是不能被长按识别的,只要改成img就可以了
碰到的坑
1. 跨域图片
如果作为背景的图片是跨域的,那么在将context导出的时候会报错
var imgURL = c.toDataURL ();
报错信息
那什么是
tainted canvases
呢?字面意思就是污染的画布
尽管不通过 CORS 就可以在画布中使用图片,但是这会污染画布。一旦画布被污染,你就无法读取其数据。例如,你不能再使用画布的 toBlob(), toDataURL() 或 getImageData() 方法,调用它们会抛出安全错误。
这种机制可以避免未经许可拉取远程网站信息而导致的用户隐私泄露
但是加上下面的代码,就可以导出了
img.setAttribute ("crossOrigin", 'Anonymous');
暂时不明白这机制的意义?不加报错,加了就可以导出,还不是会导致用户隐私泄露吗
2.onload事件
因为背景图是个外链,所以画布需要等到背景图加载完成再渲染.
img.onload = function(){
//render canvas
}
貌似没什么问题,但是上传代码后发现ios端有问题,似乎onload事件没有执行.后来查了下,发现ios的Safari是不支持img的onload事件的.那只能自己写一个了
var backimg = document.getElementById ("bgc");
var imgurl = "/ug/mall/assest/images/boshi/qrcode/back_1_1.png";
//浏览器能力检测
if (!!backimg.onload) {
backimg.onload = function () {
rendQrcodeA (this);
};
backimg.src = imgurl;
} else {
//这样写才可以,不能直接给backimg绑定load事件
var img = new Image ();
img.addEventListener ('load', function () {
rendQrcodeA (this);
}, false);
img.setAttribute ("crossOrigin", 'Anonymous');
img.src = imgurl;
backimg.src = img.src;
}
3.屏幕像素比
在pc端调试的时候,canvas生成的图片是清晰的,但是放到手机端后就变模糊了
wtf?
查阅了相关资料发现这是屏幕像素比的原因.那什么是屏幕像素比呢?
通俗的讲,我们平常用的px是逻辑像素,什么是逻辑像素呢?它不是基于物理设备的.在电脑上,因为屏幕大,所以大部分的情况都是 1逻辑像素 = 1 物理像素 ,那么屏幕像素比 = 1
手机端的屏幕小,分辨率高.1逻辑像素 >= 1物理像素. 通常情况下1逻辑像素 = 2物理像素,那么屏幕像素比 = 2
window对象也提供了一个属性window.devicePixelRatio
可以获得当前设备的屏幕像素比.
好了,说完了前置知识, 我们来分析下为什么图片变模糊了
canvas.height = 100; //画布的实际高 100个物理像素
canvas.width = 100; //画布的实际宽 100个物理像素
canvas.style.height = "100px"; //画布的样式高 100个逻辑像素 = 100物理像素
canvas.style.width = "100px"; //画布的样式宽 100个逻辑像素 = 100物理像素
在pc上,也就是屏幕像素比=1 的情况下, 没什么问题.
canvas.height = 100; //画布的实际高 100个物理像素
canvas.width = 100; //画布的实际宽 100个物理像素
canvas.style.height = "100px"; //画布的样式高 100个逻辑像素 = 200物理像素
canvas.style.width = "100px"; //画布的样式宽 100个逻辑像素 = 200物理像素
在手机端上,因为屏幕像素比=2,也就是说1个逻辑像素=2个物理像素, 画布的实际宽高是 100 * 100, 样式展现变成了 200200. 好了,问题就出在这里.相当于原本是100100尺寸的图片被放在了200*200的画布上.所以图片变放大模糊了
那怎么规避这种问题呢,其实很简单,只要在设置画布宽高的时候 * 逻辑像素比
var c = document.getElementById ("ouputCanvas");
c.width = w * window.devicePixelRatio;
c.height = h * window.devicePixelRatio;
4.click点击事件
在实现点击图片放大的时候,发现ios设备没反应.后来查询资料发现,ios设备只对button的点击效果有效.
其实只要加一个样式就可以解决这个问题
cursor:pointer
这样其他元素也可以触发点击事件了.