canvas画中画

最近工作中碰到一个需求,实现一个自定义的二维码

  1. 背景是一张自定义的图片
  2. 二维码居中展示
  3. 在微信里长按可识别

类似于下图


经过一番查资料和思考,整理如下

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

这样其他元素也可以触发点击事件了.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343