<canvas>标签的一些应用
最近刚接触到<canvas>标签,虽然知道它可以对像素进行一些处理,但却想不出实战中能做点什么。百度找到了haorooms博客中的一篇文章,还挺实用的,就用自己的理解给他的实例做个注释方便我这个沙比以后回顾时能快点想起来代码的作用和这些功能的原理。
注意:使用跟ImageData对象相关的功能的时候,Chrome本地运行会出现跨域的问题,导致JS代码不生效。有一个解决方法就是把文件丢到Web服务器上,然后通过域名去访问这个网页就没问题了,但这得会搭建服务器,还得开着服务器(虚拟机啊,实体机啊什么的),还是挺麻烦的。用IE、Edge和Firefox(我电脑就这四个浏览器)就不会出现这个问题,代码能够正常运行。反正下面的内容要是在本地测试的话别用Chrome打开就好了!
应用1:颜色选择器(拾色器)
原文的作者称呼这个功能叫颜色选择器,我因为有时候会用到FSCapture中的拾色器功能,就感觉效果很相近,都是获取一个区域中的某个颜色值,还是下意识的叫它拾色器,标记一下,虽然知道不是一种东西。
原理分析:
我们写一个JS函数绑定画布上的鼠标指针移动事件(onmousemove),这个函数会获取当前鼠标的坐标,然后以鼠标的坐标作为ImageData区域的左上角定位点,绘制一个1px的ImageData区域,然后提取这个区域中的颜色值(ImageData.data),得到颜色值之后就可以修改文本和指定区域的背景颜色了。
1 <body> 2 <!-- 显示在画布上的图片 --> 3 <img id="isImg" src = "image/001.png" style="display:none"/> 4 5 <!-- 画布区域 --> 6 <canvas id="drawEle"> 7 您的浏览器不支持该标签 8 </canvas> 9 10 <!-- 显示颜色的区域 --> 11 <div id="color"> 12 还没开始 13 </div> 14 15 <script> 16 //获取画布 17 var canvas = document.getElementById("drawEle"); 18 var ctx = canvas.getContext("2d"); 19 //获取图像 20 var getImg = document.getElementById("isImg"); 21 //在图像加载完成后再将图像绘制到画布上 22 getImg.onload = function() { 23 ctx.drawImage(getImg,0,0,300,150); 24 } 25 26 //获取显示颜色的div区域 27 var color = document.getElementById("color"); 28 29 //创建一个函数,用于获取鼠标放在元素上面时获取指定位置像素颜色参数 30 function getColor(event) { 31 //layerX、layerY:鼠标在触发元素上的坐标 32 var x = event.layerX; 33 var y = event.layerY; 34 //鼠标的坐标为基点,获取一个1px大小的像素区域 35 var pixel = ctx.getImageData(x, y, 1, 1); 36 //因为只有1px大小的区域,所以data中正好是一组RGBA值 37 var data = pixel.data; 38 //在控制台打印,测试用 39 console.log(data); 40 //将data中分散的RGBA值整合成一个字符串(原本是data[0]对应R,data[1]对应G这样的格式) 41 var rgbaValue = "rgba("+ data[0] + "," + data[1] + "," + data[2] + "," + data[3] + ")" 42 //设置显示颜色div区域的背景样式(修改背景颜色) 43 color.style.background = rgbaValue; 44 //修改显示颜色div区域中的内容 45 color.textContent = rgbaValue; 46 } 47 48 //将函数绑定在鼠标移动事件(mousemove)上 49 canvas.addEventListener('mousemove',getColor); 50 51 </script> 52 </body>
运行效果如下:
(这个就没有haorooms加注释版了,因为我觉得两个版本都差不多就放一个就行了)
应用2:过滤纯色背景
haorooms中这个应用是过滤视频的纯色背景,这里我就先尝试过滤图片背景颜色先。
在看到标题的时候我脑中就有这样一个想法:在使用getImageData获取到这个元素之后,使用for循环遍历一遍所有像素,然后用if判断哪个像素是我们要排除的颜色,然后设置Alpha通道的值为0来隐藏这个像素。既然有自己的想法就先做一做试试看:
1 <head> 2 <meta charset="utf-8" /> 3 <title>过滤纯色背景(灵光一现版)</title> 4 <style> 5 body { 6 background-color:#AAAAAA; 7 } 8 canvas { 9 background-color:#CCCCCC; 10 margin:0px 5px; 11 } 12 #newDrawEle { 13 background-image:url(image/002.jpg); 14 background-size:cover; 15 } 16 </style> 17 </head> 18 19 <body> 20 <!-- 显示在画布上的图片 --> 21 <img id="isImg" src = "image/001.png" style="display:none"/> 22 23 <!-- 画布区域 --> 24 <canvas id="drawEle"> 25 您的浏览器不支持该标签 26 </canvas> 27 28 <!-- 过滤后图像在的区域 --> 29 <canvas id="newDrawEle"> 30 31 </canvas> 32 33 <script> 34 //获取画布1(过滤前的画布) 35 var canvas1 = document.getElementById("drawEle"); 36 var ctx1 = canvas1.getContext("2d"); 37 38 //获取画布2(过滤后的画布) 39 var canvas2 = document.getElementById("newDrawEle"); 40 var ctx2 = canvas2.getContext("2d"); 41 42 //获取图像 43 var getImg = document.getElementById("isImg"); 44 //在图像加载完成后再将图像绘制到画布上 45 getImg.onload = function() { 46 ctx1.drawImage(getImg,0,0,300,150); 47 48 //获取画布1上的全部区域 49 var zone = ctx1.getImageData(0,0,300,150); 50 51 //遍历一遍区域中的像素 52 for(var i = 0;i<zone.data.length;i+=4) { 53 //将RGBA值格式化 54 var rgbaValue = "rgba(" + zone.data[i] + "," + zone.data[i+1] + "," + zone.data[i+2] + "," + zone.data[i+3] + ")"; 55 //判断RGBA值 56 if(rgbaValue == "rgba(0,255,0,255)") { 57 //符合过滤条件就把指定元素的Alpha通道调成0(透明) 58 zone.data[i+3] = 0; 59 } 60 } 61 62 //将画布1上的区域绘制到画布2上面 63 ctx2.putImageData(zone,0,0); 64 } 65 </script> 66 </body>
随便搞了张游戏截图,然后把背景搞成了纯色绿色背景(rgba(0,255,0,255))试试效果:
有没处理干净的绿色围绕在人物身边,效果并不是很完美。我的身上有种不可思议的光线
目前想到的解决的方法就是增加颜色判定范围,比如g的值小于多少多少这样,于是诞生了下面这个版本的代码(仅修改了for循环中的内容):
1 //遍历一遍区域中的像素 2 for(var i = 0;i<zone.data.length;i+=4) { 3 //获取rgb值 4 let r = zone.data[i + 0]; 5 let g = zone.data[i + 1]; 6 let b = zone.data[i + 2]; 7 8 //判断RGBA值 9 if(g > 240 && r >= 0 && b >= 0) { 10 //符合过滤条件就把指定元素的Alpha通道调成0(透明) 11 zone.data[i+3] = 0; 12 } 13 }
效果如下:
跟之前的比起来,人物周围绿色的部分是少了许多,再调整精细点。。。我得去学点美术相关的知识才好做咕咕咕。
原理分析:
首先它创建了一个对象名为processor(处理器),所有对视频的处理的方法等相关的一切都放在processor对象中。
processor中第一个方法:doLoad(进行加载),用于初始化参数,给标签绑定事件函数。在这个方法中,在js中获取要进行操作的元素,如<video>标签、画布。给<video>标签的play事件(视频播放事件)绑定函数,绑定的函数设置视频绘制到画布上时的宽度和高度,然后执行一次timerCallback()(计时器回调)方法。
processor中第二个方法:timerCallback(计时器回调)。在里面做判断,id=video的标签是否处于暂停播放(this.video.paused)或者(||)停止播放(this.video.ended)的状态,是则跳出timerCallback(),不执行后面的内容。如果为false,则继续执行后面的内容。为false,调用一次computeFrame(计算框架)来过滤颜色,然后使用setTimeout()方法来循环调用自身(timerCallback()方法),直到视频停止播放或者暂停播放为止。
processor中第三个方法:computeFrame(计算框架),该方法用于过滤视频纯色背景。首先绘制id=video中的画面到画布1(不做纯色过滤的画布)上,然后创建一个ImageData对象,对象的内容就是画布1上的内容。创建一个变量计算区域内像素的数量。然后使用for遍历一遍区域内的所有像素,使用if对像素进行判断,判断要过滤的颜色。如果if为true,则将这个像素的alpha通道值改为0(透明)。过滤完毕后将处理后的内容打印到画布2上。
最终,给<body>元素的onload事件绑定processor.doLoad()方法。
下面是加了注释的源码:
1 <head> 2 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 3 <title>haorooms过滤视频纯色背景</title> 4 <style> 5 body { 6 background: black; 7 color:#CCCCCC; 8 } 9 div { 10 float: left; 11 border :1px solid #444444; 12 padding:10px; 13 margin: 10px; 14 background:#3B3B3B; 15 } 16 </style> 17 <script> 18 //创建一个processor对象,里面包含所有的要处理的内容 19 let processor = { 20 //processor对象中的第一个方法:doLoad(进行加载) 21 doLoad: function() { 22 //获取<video>标签 23 this.video = document.getElementById("video"); 24 //获取第一个画布:不做纯色过滤的画布 25 this.c1 = document.getElementById("c1"); 26 this.ctx1 = this.c1.getContext("2d"); 27 //获取第二个画布:会做纯色过滤的画布 28 this.c2 = document.getElementById("c2"); 29 this.ctx2 = this.c2.getContext("2d"); 30 //储存this指向processor内部的状态(不知道该怎么介绍这个) 31 let self = this; 32 //绑定id="video"对象 33 this.video.addEventListener("play", function() { 34 //设置视频绘制到画布上时的宽度和高度 35 self.width = self.video.videoWidth / 2; 36 self.height = self.video.videoHeight / 2; 37 //执行一次timerCallback()函数 38 self.timerCallback(); 39 }, false); //冒泡阶段执行 40 }, 41 42 //processor对象中的第二个方法:timerCallback(计时器回调) 43 timerCallback: function() { 44 //判断video标签是否处于暂停或者停止播放状态,是跳出该函数 45 if (this.video.paused || this.video.ended) { 46 return; 47 } 48 //调用computeFrame(计算框架)做颜色过滤 49 this.computeFrame(); 50 //储存this指向processor内部的状态(不知道该怎么介绍这个) 51 let self = this; 52 53 setTimeout(function () { 54 self.timerCallback(); 55 }, 0); 56 }, 57 58 //processor对象中的第三个方法:computeFrame(计算框架) 59 computeFrame: function() { 60 //绘制video中的画面到画布1(不做纯色过滤的画布)上 61 this.ctx1.drawImage(this.video, 0, 0, this.width, this.height); 62 //创建一个ImageData对象,这个对象的内容等于画布1上的内容 63 let frame = this.ctx1.getImageData(0, 0, this.width, this.height); 64 //计算区域内像素的数量(除以4是因为一个完整的RGBA值由四个参数组成) 65 let l = frame.data.length / 4; 66 //遍历一遍区域内的像素 67 for (let i = 0; i < l; i++) { 68 let r = frame.data[i * 4 + 0]; 69 let g = frame.data[i * 4 + 1]; 70 let b = frame.data[i * 4 + 2]; 71 //判断是否符合过滤颜色条件,是则修改alpha通道参数值 72 if (g > 100 && r > 100 && b < 43) { 73 frame.data[i * 4 + 3] = 0; 74 } 75 } 76 //打印到画布上 77 this.ctx2.putImageData(frame, 0, 0); 78 return; 79 } 80 }; 81 </script> 82 </head> 83 84 <body onload="processor.doLoad()"> 85 <div> 86 <video id="video" src="video/haorooms.ogv" controls="true"> 87 </video></div> 88 <div> 89 <canvas id="c1" width="160" height="96"></canvas> 90 <canvas id="c2" width="160" height="96"></canvas> 91 </div> 92 </body>
效果如下(视频还是用人家的视频,找新的视频还得设置颜色范围,我懒_(:з」∠)_):
应用3:图片灰度和反相颜色
图片灰度和反相颜色就像下面这样:
实现这个只需要将RGB值计算后再绘制到画布上就行了,关键就是RGB值的计算公式。
转换成灰度图:
R = (R + G + B) /3
G = (R + G + B) /3
B = (R + G + B) /3
将图片反色:
R = 255 - R
G = 255 - G
B = 255 - B
灰度图的公式也可以用加权运算平衡,详细可百度,这里使用相加然后除以3。
haorooms中的例子是用两个按钮来控制将图片反色或者转换成灰度图,但并不会将图片还原。这里我们先给haorooms中的例子加上注释,然后自己再写一个像上面展示的什么是灰度图什么是反色图那样,同时显示原图、灰度图、反色图。
原理分析:
首先先实例化一个Image对象,然后给它指定一张图片。
然后创建一个方法draw(),这个方法调用的时候需要传递一个img类型的参数。
在draw()内,我们引用画布,将图像绘制到画布上,然后把原图隐藏(display:none)。创建ImageData对象,内容为画布上的所有像素。
在draw()内创建一个函数invert(),这个函数内使用for循环将所有像素的RGB值转换成反色值,然后绘制到画布上。在draw()内创建第二个函数grayscale(),这个函数内使用for循环将RGB值转换成灰度值,然后绘制到画布上。
最后将这两个函数绑定到按钮的click事件(点击事件)上
1 <body> 2 <canvas id="canvas" width="600" height="300"></canvas> 3 <br> 4 <input type="button" value="灰度" id="grayscalebtn"> 5 <input type="button" id="invertbtn" value="反向"> 6 7 <script> 8 //创建一个img对象 9 var img = new Image(); 10 //给img对象指定图片 11 img.src = 'image/001.png'; 12 //给img对象的onload事件绑定一个函数,这个函数调用方法draw() 13 img.onload = function () { 14 //调用draw(),传递一个img参数 15 draw(this); 16 }; 17 18 //创建方法draw 19 function draw(img) { 20 //引用画布 21 var canvas = document.getElementById('canvas'); 22 var ctx = canvas.getContext('2d'); 23 //绘制图像 24 ctx.drawImage(img, 0, 0); 25 //隐藏原图 26 img.style.display = 'none'; 27 //创建ImageData对象,获取画布上的全部内容 28 var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 29 //获取ImageData中的data集合 30 var data = imageData.data; 31 32 //转换成反色的方法 33 var invert = function () { 34 for (var i = 0; i < data.length; i += 4) { 35 data[i] = 225 - data[i]; // red 36 data[i + 1] = 225 - data[i + 1]; // green 37 data[i + 2] = 225 - data[i + 2]; // blue 38 } 39 //绘制图像 40 ctx.putImageData(imageData, 0, 0); 41 }; 42 43 //转换成灰度的方法 44 var grayscale = function () { 45 for (var i = 0; i < data.length; i += 4) { 46 var avg = (data[i] + data[i + 1] + data[i + 2]) / 3; 47 data[i] = avg; // red 48 data[i + 1] = avg; // green 49 data[i + 2] = avg; // blue 50 } 51 //绘制图像 52 ctx.putImageData(imageData, 0, 0); 53 }; 54 55 //获取反色按钮 56 var invertbtn = document.getElementById('invertbtn'); 57 //绑定点击事件 58 invertbtn.addEventListener('click', invert); 59 //获取灰度按钮 60 var grayscalebtn = document.getElementById('grayscalebtn'); 61 //绑定点击事件 62 grayscalebtn.addEventListener('click', grayscale); 63 } 64 </script> 65 </body>
运行效果如下:
那接下来我们自己做一个三个同时显示的版本练练手吧:
1 <body> 2 <!-- 原图 --> 3 <div> 4 <img id="isImg" src = "image/001.jpg" /> 5 </div> 6 <!-- 灰度图的画布 --> 7 <canvas id="gray-scale"> 8 您的浏览器不支持该标签 9 </canvas> 10 <!-- 反色图的画布 --> 11 <canvas id="invert-color"> 12 您的浏览器不支持该标签 13 </canvas> 14 15 <script> 16 //获取图像 17 var getImg = document.getElementById("isImg"); 18 19 //在图像加载完成后再将图像绘制到画布上 20 getImg.onload = function() { 21 draw(this); 22 } 23 24 //绘制图像的函数 25 function draw(img) { 26 27 //获取灰度图的画布 28 var c1 = document.getElementById("gray-scale"); 29 var ctx1 = c1.getContext("2d"); 30 31 //获取反色图的画布 32 var c2 = document.getElementById("invert-color"); 33 var ctx2 = c2.getContext("2d"); 34 35 //设置两个画布绘图区域的大小 36 c1.width = "100"; 37 c1.height = "100"; 38 c2.width = "100"; 39 c2.height = "100"; 40 41 //绘制图像到两个画布上 42 ctx1.drawImage(getImg,0,0,100,100); 43 ctx2.drawImage(getImg,0,0,100,100); 44 45 //获取灰度图中的内容 46 var inGrayscale = ctx1.getImageData(0,0,c1.width,c1.height); 47 //遍历灰度图中的内容 48 for(var i = 0;i<inGrayscale.data.length;i+=4) { 49 //计算转换成灰度值后的颜色值 50 var avg = (inGrayscale.data[i+0] + inGrayscale.data[i+1] + inGrayscale.data[i+2]) /3; 51 //设置成灰度图 52 inGrayscale.data[i + 0] = avg; 53 inGrayscale.data[i + 1] = avg; 54 inGrayscale.data[i + 2] = avg; 55 } 56 //绘制到灰度图的画布上 57 ctx1.putImageData(inGrayscale,0,0); 58 59 //获取反色图中的内容 60 var inInvertcolor = ctx2.getImageData(0,0,c2.width,c2.height); 61 //遍历反色图中的内容 62 for(var i = 0;i<inInvertcolor.data.length;i+=4) { 63 //进行反色处理 64 inInvertcolor.data[i + 0] = 255 - inInvertcolor.data[i + 0]; 65 inInvertcolor.data[i + 1] = 255 - inInvertcolor.data[i + 1]; 66 inInvertcolor.data[i + 2] = 255 - inInvertcolor.data[i + 2]; 67 } 68 //绘制到反色图的画布上 69 ctx2.putImageData(inInvertcolor,0,0); 70 } 71 72 </script> 73 </body>
最终运行效果如下:
(没错开始的图片就是我自己做版本的效果哒!)
应用4:缩放和反锯齿
缩放的做法其实很好猜到,截取画布a上的一部分,在画布b上绘制a的一部分时放大显示。
反锯齿其实不太好猜。实际上<canvas>中有自带的属性可以设置是否开启反锯齿。imageSmoothingEnable属性,设置为true表示图片平滑(默认值),为false表示图片不平滑。
原理分析:
首先先实例化一个Image对象,然后给它指定一张图片。
然后创建一个方法draw(),这个方法调用的时候需要传递一个img类型的参数。
在draw()内,我们引用原图画布,将图像绘制到原图画布上,然后把原图隐藏(display:none)。引用显示缩放内容区域,还有控制反锯齿是否启动的开关。
在draw()内创建一个函数toggleSmoothing()用于进行反锯齿的设置。这个函数中将反锯齿开关的布尔值设置成imageSmoothingEnable的值,为了兼容性还要给每种内核的imageSmoothingEnable都赋上同样的值。然后给反锯齿开关绑定toggleSmoothing()函数。
在draw()内创建一个函数zoom()用于坐标缩放。先获取鼠标的坐标,然后将鼠标的坐标作为裁剪区域的中心点(做减法运算,让左上角的位置变成裁剪区域中心,用Math.abs取绝对值保证不出现负数)。然后把zoom()函数绑定在原图画布的mousemove(鼠标移动事件)上。
1 <body> 2 <canvas id="canvas" width="300" height="227"></canvas> 3 <canvas id="zoom" width="300" height="227"></canvas> 4 <div> 5 <label for="smoothbtn"> 6 <input type="checkbox" name="smoothbtn" checked="checked" id="smoothbtn"> 7 Enable image smoothing 8 </label> 9 </div> 10 <script type="text/javascript"> 11 //创建图片对象 12 var img = new Image(); 13 //给img对象指定图片 14 img.src = 'image/001.jpg'; 15 //给img对象的onload事件绑定一个函数,这个函数调用方法draw() 16 img.onload = function () { 17 //调用draw(),传递一个img参数 18 draw(this); 19 }; 20 21 //创建方法draw 22 function draw(img) { 23 //引用原图像的画布 24 var canvas = document.getElementById('canvas'); 25 var ctx = canvas.getContext('2d'); 26 //绘制图像 27 ctx.drawImage(img, 0, 0); 28 //隐藏原图 29 img.style.display = 'none'; 30 //引用显示缩放内容的区域 31 var zoomctx = document.getElementById('zoom').getContext('2d'); 32 //引用反锯齿开关 33 var smoothbtn = document.getElementById('smoothbtn'); 34 35 //对显示缩放区域的内容做反锯齿处理 36 var toggleSmoothing = function (event) { 37 //imageSmoothingEnabled:设置图片是否平滑,true表示平滑,false表示不平滑 38 zoomctx.imageSmoothingEnabled = this.checked; 39 //下面是对不同浏览器的兼容imageSmoothingEnable设置,效果一样 40 //moz:火狐内核。-moz代表火狐的私有属性 41 zoomctx.mozImageSmoothingEnabled = this.checked; 42 //webkit:webkit内核,常用于safari和chrome。-webkit代表Safari和chrome的私有属性 43 zoomctx.webkitImageSmoothingEnabled = this.checked; 44 //ms:IE内核。-ms代表IE的私有属性 45 zoomctx.msImageSmoothingEnabled = this.checked; 46 }; 47 //给反锯齿开关的change(input标签发生变化时触发)事件绑定反锯齿处理的函数 48 smoothbtn.addEventListener('change', toggleSmoothing); 49 50 //缩放功能的函数 51 var zoom = function (event) { 52 //获取鼠标坐标 53 var x = event.layerX; 54 var y = event.layerY; 55 /* 在显示缩放区域的位置上绘制图像 56 裁剪区域左上角的点为鼠标当前坐标-5(Math.abs) 57 裁剪范围为10x10 58 在显示缩放区域的画布上左上角坐标为0,0 59 大小为200x200 60 */ 61 zoomctx.drawImage(canvas, 62 Math.abs(x - 5), 63 Math.abs(y - 5), 64 10, 10, 65 0, 0, 66 200, 200); 67 }; 68 //给显示原图像的画布的mousemove(鼠标移动事件)绑定缩放功能的函数 69 canvas.addEventListener('mousemove', zoom); 70 } 71 </script> 72 </body>
运行效果如下:
接下来就是自己动手环节了
1 <body> 2 <!-- 显示在画布上的图片 --> 3 <img id="isImg" src = "image/001.jpg" style="display:none"/> 4 5 <!-- 显示图片的画布区域 --> 6 <canvas id="drawEle"> 7 您的浏览器不支持该标签 8 </canvas> 9 10 <!-- 显示放大内容的画布区域 --> 11 <canvas id="zoom"> 12 您的浏览器不支持该标签 13 </canvas> 14 15 <!-- 反锯齿开关 --> 16 <input type="checkbox" name="smoothBtn" id="smoothBtn" checked='checked'> 17 反锯齿开关 18 </input> 19 20 <script> 21 //获取图像 22 var getImg = document.getElementById("isImg"); 23 24 //在图像加载完成后执行一系列操作 25 getImg.onload = function() { 26 draw(getImg); 27 } 28 29 //实现缩放功能和反锯齿功能的函数draw() 30 function draw(img) { 31 //获取图像处理前的画布 32 var canvas1 = document.getElementById("drawEle"); 33 var ctx1 = canvas1.getContext("2d"); 34 35 //将图像绘制到图像处理前的画布上 36 ctx1.drawImage(getImg,0,0,300,150); 37 38 //获取缩放区域的画布 39 var canvas2 = document.getElementById("zoom"); 40 var ctx2 = canvas2.getContext("2d"); 41 42 //缩放功能的函数 43 var zoom = function(event) { 44 //获取鼠标坐标 45 var x = event.layerX; 46 var y = event.layerY; 47 /* 在显示缩放区域上绘制图像 48 裁剪区域左上角的点坐标偏移,为了让裁剪中心点处于裁剪区域的中间(用Math.abs求绝对值,防止出现负数) 49 裁剪范围为10x10 50 显示在缩放画布上左上角坐标为0,0 51 大小为150x150 52 */ 53 ctx2.drawImage(canvas1, Math.abs(x - 5), Math.abs(y - 5), 10, 10, 0, 0, 150, 150); 54 } 55 //将缩放功能的函数绑定在图像处理前画布的mousemove(鼠标移动事件)上 56 canvas1.addEventListener("mousemove",zoom); 57 58 //获取反锯齿开关 59 var smoothBtn = document.getElementById("smoothBtn"); 60 61 //对显示缩放区域中的画面进行反锯齿处理 62 var toggleSmoothing = function(event) { 63 //设置通用的imageSmoothingEnabled属性,true为平滑,false为不平滑 64 ctx2.imageSmoothingEnabled = this.checked; 65 //设置兼容火狐的imageSmoothingEnabled属性 66 ctx2.mozImageSmoothingEnabled = this.checked; 67 //设置兼容Safari和Chrome的imageSmoothingEnabled属性 68 ctx2.webkitImageSmoothingEnabled = this.checked; 69 //设置兼容IE的imageSmoothingEnabled属性 70 ctx2.msImageSmoothingEnabled = this.checked; 71 } 72 //将反锯齿开关功能的函数绑定在反锯齿开关的change(表单发生变化)事件上 73 smoothBtn.addEventListener("change",toggleSmoothing); 74 } 75 </script> 76 </body>
应用5:在canvas中手绘并下载图片
在体验了haorooms中实例的效果后,突然有了灵感,就先按照自己的想法做一遍试试看效果。
关于下载画布上内容这个功能,我们会用到canvas中的一个方法:toDataURL(),该方法返回一个包含画布上内容的data URL,可以传递参数指定导出图片类型,默认格式为png,分辨率为96dpi。我们就用这个方法来下载我们绘制到画布上的内容。
原理分析:
首先先获取画布,然后获取下载用的a标签。创建一个变量brushesDown,初始值为false。这个变量是用来判断画笔是处于激活(按下鼠标左键)还是禁用(抬起鼠标左键)的状态。
接下来定义一个函数burshesMove(),在这个函数中先获取鼠标的x轴和y轴坐标,然后创建一个ImageData对象(createImageData())作为笔刷的样式,用遍历设置一遍这个对象中的像素为#000000(黑色)。做一个if判断,判断笔刷是否激活(if(brushesDown)),激活则使用putImageData()函数在画布上鼠标指定的位置绘制内容。最后更新下载用的a标签的链接。
将burshesMove()函数绑定在画布的mousemove(鼠标移动)事件上。
给画布的mousedown(鼠标按下事件)设置一个函数:当鼠标按下时,brshesDown设置为true。
给画布的mouseup(鼠标抬起事件)设置一个函数:当鼠标抬起时,brshesUp设置为false。
1 <body> 2 <canvas id="drawEle"> 3 您的浏览器不支持该标签 4 </canvas> 5 <a id="drawDownload" download="在canvas中手绘并下载图片.png">下载图片</a> 6 <script> 7 //引用画布 8 var canvas = document.getElementById("drawEle"); 9 var ctx = canvas.getContext("2d"); 10 11 //获取a标签 12 var imageDownload = document.getElementById("drawDownload"); 13 14 //判断画笔的状态,是按下去还是抬起来 15 var brushesDown = false; 16 //鼠标在画布上绘制图形的方法 17 function brushesMove(event){ 18 //获取鼠标坐标 19 var x = event.pageX; 20 var y = event.pageY; 21 22 //创建笔刷(伪),笔刷大小为3x3 23 var brushes = ctx.createImageData(3,3); 24 //设置笔刷的样式 25 for(var i = 0;i<brushes.data.length;i+=4) { 26 brushes.data[i + 0] = 0; 27 brushes.data[i + 1] = 0; 28 brushes.data[i + 2] = 0; 29 brushes.data[i + 3] = 255; 30 } 31 //判断画笔有没有激活 32 if(brushesDown) { 33 //在画布上绘制内容 34 ctx.putImageData(brushes,Math.abs(x-10),Math.abs(y-10)); 35 } 36 37 //更新下载链接URL 38 imageDownload.href = canvas.toDataURL(); 39 40 } 41 42 //绑定在画布上绘制图形的方法在画布的mousemove(鼠标移动事件)上 43 canvas.addEventListener("mousemove",brushesMove); 44 45 //鼠标按下时激活画笔 46 canvas.onmousedown = function() { 47 brushesDown = true; 48 } 49 50 //鼠标抬起时禁用画笔 51 canvas.onmouseup = function() { 52 brushesDown = false; 53 } 54 </script> 55 </body>
运行效果如下:
下载的图片:
定位鼠标的时候有对坐标进行减法运算,是因为我在实际测试的时候,如果直接使用鼠标坐标,返回的坐标是鼠标的中心点,我希望笔刷的中心点在鼠标箭头上,所以就做了个减法运算。
接下来我们来分析haorooms中的例子吧:
(这个例子我下载在本地运行的时候是会显示两个a标签的,第二个a标签是用js生成的。在它网站上运行的例子没有两个a标签,本地就有。)
1 <head> 2 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 3 <title>haorooms_canvas应用</title> 4 <style type="text/css"> 5 /* 设置画布样式 */ 6 canvas { 7 /* 设置画布的背景颜色为桃红色 */ 8 background: peachpuff; 9 } 10 11 /* 设置有download属性的a标签 */ 12 a[download]{ 13 display: block; 14 width: 280px; 15 background: #369; 16 color: #fff; 17 text-decoration: none; 18 padding: 5px 10px; 19 } 20 21 /* 设置有download属性的a标签和 22 h1标签中的span标签 */ 23 a[download], h1 span { 24 opacity: 0; 25 } 26 27 /* 设置painted类中 有download属性的a标签和 28 painted类中 h1标签下的 span标签*/ 29 .painted a[download], .painted h1 span { 30 opacity: 1; 31 transition: 0.5s; 32 } 33 </style> 34 </head> 35 36 <body class="painted"> 37 38 <a href="http://resource.haorooms.com/uploads/demo/canvas/drawanddownload.html#" download="haorooms.png">下载图片</a> 39 <canvas width="300" height="300"></canvas> 40 41 <script> 42 //给浏览器窗口的load(加载完成)事件绑定函数 43 window.addEventListener('load', function(ev) { 44 //使用css选择器查找文档中第一个img元素(可是这个变量后面没用上啊= =) 45 var sourceimage = document.querySelector('img'); 46 //获取文档中第一个canvas标签 47 var canvas = document.querySelector('canvas'); 48 //获取文档中第一个a标签 49 var link = document.querySelector('a'); 50 //获取画布的绘图环境 51 var context = canvas.getContext('2d'); 52 //声明五个变量并赋值:mouseX(鼠标x轴坐标)、mouseY(鼠标y轴坐标)、width(画布宽度)、height(画布高度)、mousedown(鼠标是否按下) 53 var mouseX = 0, mouseY = 0, 54 width = 300, height = 300, 55 mousedown = false; 56 //设置画布的宽度和高度 57 canvas.width = width; 58 canvas.height = height; 59 //设置画笔的颜色 60 context.fillStyle = 'hotpink'; 61 //定义在画布上鼠标按下(mousedown)后绘制线条的方法 62 function draw(ev) { 63 //判断mousedown(鼠标是否按下)是否为true 64 if (mousedown) { 65 //获取鼠标的坐标,作为画笔的中心点 66 var x = ev.layerX; 67 var y = ev.layerY; 68 //修改中心点的位置,原本鼠标的中心点在鼠标正中间,这样计算后在Chrome中是在鼠标左上角,但在Firefox中在鼠标下方(个人测试) 69 x = (Math.ceil(x / 10) * 10) - 10; 70 y = (Math.ceil(y / 5) * 5) - 5; 71 //绘制内容到画布上,连续拖拽就可以形成线条。这个画笔宽10px,高5px。 72 context.fillRect(x, y, 10, 5); 73 } 74 } 75 //创建一个a标签 76 var link = document.createElement('a'); 77 //设置a标签显示的文本 78 link.innerHTML = '下载图片'; 79 //设置a标签的链接 80 link.href = "#"; 81 //设置这个a标签点击后会下载文件,这个文件的名字为haorooms.png 82 link.download = "haorooms.png"; 83 //在body元素中插入我们刚刚创建的a标签,插入在canvas标签前面 84 document.body.insertBefore(link, canvas); 85 //给画布的mouseover(鼠标放置在元素上方)事件绑定一个函数 86 canvas.addEventListener('mouseover', function(ev) { 87 //给body元素插入名称为"painted"的类(可是你body已经属于painted类了啊= =) 88 document.body.classList.add('painted'); 89 }, false); //冒泡阶段运行 90 91 //给画布的mousemnove(鼠标移动事件)事件绑定draw(鼠标按下后绘制线条的函数)函数,冒泡阶段运行 92 canvas.addEventListener('mousemove', draw, false); 93 //给画布的mousedown(鼠标按下事件)事件绑定一个函数,这个函数修改mousedown(鼠标是否按下)的值 94 canvas.addEventListener('mousedown', function(ev) { 95 //修改mousedown(鼠标是否按下)的值为true 96 mousedown = true; 97 }, false ); //冒泡阶段运行 98 99 //给画布的mouseup(鼠标抬起事件)事件绑定一个函数,这个函数修改mousedown(鼠标是否按下)的值 100 canvas.addEventListener('mouseup', function(ev) { 101 //修改a标签的下载链接为画布内容的dataURL,点击后就可以下载画布的内容 102 link.href = canvas.toDataURL(); 103 //修改mousedown(鼠标是否按下)的值为false 104 mousedown = false; 105 }, false ); //冒泡阶段运行 106 } ,false); //冒泡阶段运行 107 </script> 108 </body>
在Chrome中运行效果如下:
在Firefox中运行效果如下:
下载下来是没有什么问题,就不展示了。
好的,在haorooms上的五个例子都分析过了一遍,虽然有些功能并不是很完美,但咱这篇文章的目的并不是完善这些功能,就到此为止啦。我哪里注释写错了啊或者是哪里代码有问题请留言告诉我,大家相互交流,一起进步嘛!
参考资料:haorooms博客 - canvas的ImageData对象的介绍:https://www.haorooms.com/post/canvas_imageData
MDN - 像素操作:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas
MDN - CanvasRenderingContext2D.imageSmoothingEnable:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality
MDN - HTMLCanvasElement.toDataURL:https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL