首页 > 开发 > 前端 > 正文

让自己也能使用Canvas

2016-04-06 13:07:30  来源:极客头条

   是 HTML5 新增的元素,可使用JavaScript脚本来绘制图形。例如:画图,合成照片,创建动画甚至实时视频处理与渲染。
  兼容性方面,除了一些骨灰级浏览器IE6、IE7、IE8等,大部分现代浏览器都能支持。
  
一、属性与方法   1)属性
   看起来和 元素很相像,唯一的不同就是它并没有 src 和 alt 属性。实际上, 标签只有两个属性—— width和height。
  还有些默认的属性,id、style等
  2)方法
  1. getContext(in DOMString contextId)
  canvas起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。这个方法是用来获得渲染上下文和它的绘画功能。
  
  contextType中可选的参数有“2d”、“webgl”、“webgl2”、“bitmaprenderer”。
  如果是“2d”,就会返回 CanvasRenderingContext2D 对象。如果是“webgl”,就会返回 WebGLRenderingContext 对象。
  contextAttributes属性会根据 “2d” 或 “webgl” 会需要不同的参数。
var canvas = document.getElementById('strick'); var ctx = canvas.getContext('2d');   2. toDataURL(in optional DOMString type, in any ...args)
  
  返回一个data: URL,将canvas中的图片编码成字符串形式,有多种格式选择,type参数的默认值为image/png。
  曾做过一个图片合成的功能,就使用到了这个功能。这里要注意一个“画布污染”。
  就是嵌入的图片是跨域的,那么就不能使用这个方法。在《预览、旋转、合成》做过简单的分析。
  3. toBlob(in Function callback, in optional DOMString type, in any ...args)
  
  返回一个Blob(binary large object)对象。Blob代表了一段二进制数据,就是一个包含只读原始数据的类文件对象。
  在《移动端图片操作(一)——上传》曾做过简单的介绍。
二、绘制2D图形   这里绘制的是2D图形,会用到 CanvasRenderingContext2D 对象中的属性或方法。
  在MDN上面有个基础教程《Canvas教程》,覆盖面蛮全的。
  1)坐标空间
  
  画布的起点为左上角,这个起始点通过方法 translate 可以自定义,例如做旋转缩放等操作。
  上图所示,canvas的坐标轴与普通的坐标轴是相反的。
  
  所以顺时针是正值,逆时针是负值。
  2)绘制形状
  矩形是canvas支持的唯一一种原生的图形绘制。要画其他形状,就需要通过绘制路径实现。绘制矩形提供了3个方法。
  路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。有操纵路径和绘制路径(包含贝塞尔曲线)的方法。
  操作过程大致为4步,先创建路径起始点,再画出路径,然后闭合路径,最后填充。
  1. 简单图形
  下图中是些简单的图形,两个三角形中有一个是旋转了画布的,逆时针画的半圆,三个圆圈与一个半圆组成的笑脸,用贝塞尔曲线画的对话气泡,可以在线调试下面的效果。
  
  2. 复杂图形
  在CSS中,边框、字体等都能设置宽度、大小、颜色等,高级点的还有阴影、渐变、rgba等。
  在canvas中也有相应的操作,绘制文本、线型、文本样式、填充和描边样式、渐变和图案、阴影。
  有网友画了头灰太狼,非常逼真,可以在线调试,通过查看源码,里面用到的就是beginPath、moveTo、quadraticCurveTo等路径相关的方法或属性。
  
  3)使用图片
  canvas可以对图片进行合成、缩放、裁剪、旋转、变形等操作。
  a. 简单的合成
  现在有些网站会让你DIY做张海报,然后分享到朋友圈。前段时间做了个简易的海报,仅仅是将图片合成在一起,没有做涂鸦等操作,详细的介绍可以查看《移动端图片操作系列》
  b. 高级点的合成
  高级点的制作海报,能够输入自定义的文字,用到了上面所说的绘制形状的一些概念,在看源码的时候,发现引用了脚本“hidpi-canvas-polyfill”,解决canvas 在高清屏下绘制图片变模糊。
简单合成 高级合成   除了基础的操作,还有高级的像素操作,获取图片中某一像素的RGBA,然后修改其中的R、G、B或A的值,来修改颜色或透明度。
  例如鼠标移动获取RGBA值,将彩色照变成黑白照,打马赛克等。
  第一张图片是获取像素值,第二张图是变灰。
   
  除了能操纵图片,canvas还能操纵视频,也就是标签。
  4)变换矩阵
  CSS3中的transform有个矩阵的概念,旋转、平移、扭曲、缩放等都可以用矩阵来实现。关于CSS3的动画可以参考《CSS3中的动画效果记录》
  canvas中有一个rotate()方法,实现旋转,但其实旋转的是canvas画布,并不是旋转画出来的那个图形。
  如果用transform()方法的话,就可以实现旋转图形。这边有个对比实例,可以在线调试。
  2D渲染的上下文矩阵如下,可以忽略最后一行:
  
  2D渲染的坐标计算如下,关于计算过程可以研究下线性代数,简单点说就是a和x、y、1分别相乘。
  
  rotate与skew的矩阵计算会涉及到三角函数中的正弦、余弦还有正切。
//scale()对应的矩阵 下面是CSS3中的写法,对应的方法是CanvasRenderingContext2D.transform matrix(sx,0,0,sy,0,0);/*sx和sy分别对应X轴和Y轴的缩放比率*/ //rotate()对应的矩阵 //在JS中θ对应的是弧度转换公式为 弧度= 2 * PI / 360 * 角度 matrix(cosθ,sinθ,-sinθ,cosθ,0,0); //skew()对应的矩阵 //θy对应的是Y轴的弧度 θx对应的是X轴的弧度 matrix(1,tan(θy),tan(θx),1,0,0)   矩阵计算还会涉及到很多其他的数学知识,例如一次函数,我基本都已经忘记了,囧,都得重新查看了。
  有个在线编辑matrix的网站,可以在线制作。
  5)相关的计算公式
  a. 三角函数基础公式
  
  JavaScript中有两个反正切函数,Math.atan(ratio)与Math.atan2(y, x)。
  第一个方法返回一个 -pi/2 到 pi/2 弧度之间的数值,第二个方法返回一个 -pi 到 pi 之间的数值。
  b. 角度与弧度的互转
  在JavaScript中,三角函数等用的是弧度,例如Math.sin、Math.cos等方法;旋转是用角度。所以两者之间是需要换算的。
  
  radians是弧度,degrees是角度
  c. 两点间的距离
  使用了直角三角形的勾股定理。坐标轴中的P1与P2,就相当于公式中的A与B。
   
三、canvas动画   要实现动画就需要用JavaScript实现很多物理概念。
  关于canvas动画可以参考两本书《HTML5 JavaScript动画基础》和《HTML5 Canvas基础教程》。pdf和源码都已经分享了出来。

  1)速度向量
  速度(speed)是速度向量(velocity)中的一部分,速度向量还包括方向。
  用vx表示x轴上的速度向量,有vy表示y轴上的速度向量。还可以表示角度旋转,用vr表示。
  vx为正数表示向右,负数表示向左。vy为正数表示向下,负数表示向上。
  下图就是一个向右移动的球,vx=1,详细代码可以参考这里。
  
  2)角速度
  假设物体以1个像素的速度向45°方向移动,那么vx和vy可以通过余弦与正弦获取。
  
  速度和方向映射成一个直角三角形。
  vx = Math.cos(angle)*speed;
  vy = Math.sin(angle)*speed;
  
  上图就是经过计算后的角速度示例。
  3)加速度
  速度向量改变的是物体的物理位置,加速度改变的是速度向量。
  下面的图片与速度向量中的gif内容是相同的,只是每次循环给vx加了0.1个值。
  
  4)边界
  处理边界,有多种选择,移除、置回边界内、屏幕环绕、反弹回边界内等。
  左图是置回边界内,右图是反弹回边界内的效果。
    
  5)摩擦力
  一个比较简单的实现是,设置一个friction(摩擦力变量,一个小于1的数字),将这个数与vx和vy分别相乘,获取新的值。
  这两个值会越来越小,最终停止。
  
  除了上面几种基础的动画,还有些高级的动画,缓动、弹动、碰撞检测等。
四、canvas的优缺点   1)优点:
  1. 在呈现图像、文本和动画的时候,由于 canvas 不存在与解析 HTML 和维护分层文档模型有关的开销,因此,在 canvas 中这些任务总是要比在 HTML 中快。
  2. 可以实现一致的、跨平台的呈现。例如浏览器的transform属性,不同浏览器可能就需要使用自己独特的前缀。
  3. 画出来的图形可以直接保存为 .png 或者 .jpg的图形。
  4. 最适合于画光栅图像(如游戏和不规则几何图形等),编辑图片还有其他基于像素的图形操作。
  2)缺点:
  1. 由于 canvas 里面没有dom节点,当某个元素需要执行交互事件(如click)的时候只能是通过坐标来判断。
  2. 没有实现动画的API,你必须依靠定时器和其他事件来更新 canvas。
  3. 对文本的渲染支持是比较差,例如自动换行。canvas中也不存在超链接的概念。
  4. 由于在 canvas 上以编程方式显示的文本其实就是位图,因此搜索爬行器将完全忽略文本。文本内容也无法被屏幕阅读器识别。
  参考资料:
  轻量级的Canvas封装
  知名Html5 Canvas Javascript库对比
  HTML5 画布
  7个案例教你学会制作HTML5 Canvas动画
  canvas 在高清屏下绘制图片变模糊的解决方法
  理解CSS3 transform中的Matrix(矩阵)
  CSS3 2D Transform
  使用 HTML 标记来补充 canvas,第 2 部分: 动画和文本渲染 
  使用 HTML 标记来补充 canvas,第 1 部分 : 混合使用 canvas API 和 HTML/CSS 模型
  Canvas 最佳实践(性能篇)
 posted on 2016-04-06 10:21 咖啡机(K.F.J) 阅读(...) 评论()  编辑 收藏   上传我们一般都是用“input[type=file]”控件。当你用此控件时,你就授权了网页和服务器访问对应的文件,就可以得到File对象。
  友情提示在,在Android手机webview中,是不支持上传文件的,网上说是修改Android端的代码,但我没试过,我们这边是使用客户端提供的接口来实现上传的。
  下面的示例代码可以在这里查看到。
一、accept属性   该属性表明了服务器端可接受的文件类型,可以限制你手机选择相关的文件,如果限制多个,可以用逗号分割,下面的代码就表示只能选择图片与音频相关的文件:
  
  在移动端,点击后会让你选择拍照或相册,还是蛮高大上的。下图是UC浏览器中:
  
二、change事件   一般选择文件都会使用“change”事件,下面的代码就是绑定了change事件,弹出文件大小:
var upload = document.getElementById('upload'); upload.addEventListener('change', function() {  var file = upload.files[0];  alert(file.size); }, false);   1) 有些手机浏览器在点击的时候,会弹出键盘选择,我用onfocus="this.blur()",来强制失去焦点。
  2) 当选择过一次后,再次选择同一个文件,“change”事件不会触发,因为value没有改变,在网上看到个方法,我还没有在实际项目中使用,兼容性有待考证。
  使用“Node.cloneNode”复制上传元素,再用“Node.replaceChild”替换节点。
  这里注意下:克隆一个元素节点会拷贝它所有的属性以及属性值,但不会拷贝那些使用addEventListener()方法或者node.onclick = fn用JavaScript动态绑定的事件。
upload.addEventListener('change', function() {  var upload = document.getElementById('upload'); //每次要动态获取  var file = upload.files[0];  console.log(file.size);  //解决上传相同文件不触发onchange事件    var clone = upload.cloneNode(true);  clone.onchange = arguments.callee; //克隆不会复制动态绑定事件  clone.value = '';  upload.parentNode.replaceChild(clone, upload); }, false); 三、File对象   用户所选择的文件都存储在了一个FileList对象上,其中每个文件都对应了一个File对象
  File对象负责处理那些以文件形式存在的二进制数据,也就是操作本地文件。
  File对象是Blob【下面会提到】的特殊类型,即大块的二进制数据,File对象的尺寸及类型等属性都继承自Blob。
  1)File对象可以通过3种方式获取:
  1. 元素上选择文件后返回的FileList对象中的成员
  2. 拖放操作【Drag或Drop】生成的 DataTransfer对象内files属性中的成员
  3. HTMLCanvasElement上执行mozGetAsFile()方法后的返回结果
document.getElementById('upload').files[0]//选取第一个文件对象   2)File对象有9个属性,这里就只介绍3个:
  1. name:当前File对象所引用文件的文件名,不包括路径,只读。
  2. size:文件大小,单位为字节,只读的64位整数.
  3. type:MIME类型,只读字符串,如果类型未知,则返回null。有些移动端的浏览器明明选择了图片,返回的却是null,非常坑。
  还有3个非标准的方法:getAsBinary()、getAsDataURL()和getAsText(in DOMString encoding)。
  这3个方法现在已经过时,现在用FileReader对象中的方法来取代。
四、FileReader   web应用程序可以异步的读取存储在用户计算机上的文件(或者原始数据缓冲)内容,可以使用File对象或者Blob对象来指定所要处理的文件或数据。
  1) readAsArrayBuffer():在返回的result属性中将包含一个ArrayBuffer对象【缓冲数组,是一种用于呈现通用、固定长度的二进制数据的类型】以表示所读取文件的内容
  Blob可以“append”,ArrayBuffer数据。ArrayBuffer存在的意义就是作为数据源提前写入在内存中,就是提前钉死在某个区域,长度也固定。
  2) readAsBinaryString():result属性中将包含所读取文件的原始二进制数据
  3) readAsDataURL():result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容
  4) readAsText():result属性中将包含一个字符串以表示所读取的文件内容
  下面的代码是获取data:URL,可以将返回的result内容赋值给img的src,用于预览等操作。
var reader = new FileReader(); reader.readAsDataURL(file); reader.onload = function(e) {  var img = new Image();  img.src = this.result;  console.log(this.result); }; console.log(this.result)内容如下:   
五、URL对象   URL对象是硬盘上指向文件的URL。上面的例子中获取图片的引用,通过读取data URI,data URI是个一大串的字符。
  图片原本就在硬盘上,还要转换成另一个格式再用,有点绕了,完全可以直接引用文件的URL,下面是两个方法:
  1) URL.createObjectURL():接收一个文件的引用(File或Blob对象)返回一个URL对象
  2) URL.revokeObjectURL():销毁创建的URL
var url = URL.createObjectURL(file); var img = new Image(); img.src = url; img.onload = function(e) {  window.URL.revokeObjectURL(this.src); //销毁 } console.log(url);   console.log(url)内容如下:
  
  在移动端需要做个兼容性判断:
window.URL = window.URL "| window.webkitURL; 六、Blob对象   Blob(binary large object)对象代表了一段二进制数据,就是一个包含只读原始数据的类文件对象。
  File接口基于Blob,继承了Blob的功能,并且扩展支持了用户计算机上的本地文件。
  1)创建Blob对象的4种方法:
  1. 调用Blob构造函数
  2. 使用一个已有Blob对象上的slice()方法切出另一个Blob对象
  3. 调用canvas对象上的toBlob方法
  4. 过气的方法,通过BlobBuilder接口创建,但兼容性不好,并且现有的BlobBuilder实现都是带前缀的
  2)利用Blob对象,生成可下载文件

var blob = new Blob(["pwstrick"]);//数组中添加DOMString对象
var a = document.createElement("a");
a.href = URL.createObjectURL(blob);//创建URL对象
a.download = "test.txt";//HTML5新属性
a.textContent = "test";            
document.getElementsByTagName('body')[0].appendChild(a);

  生成一个“a”标签,并且点击这个链接,可以下载一个txt文本,内容是“pwstrick”。
  3)通过slice方法,将二进制数据按照字节分块,返回一个新的Blob对象

upload.addEventListener('change', function() {
  var upload = document.getElementById('upload'); //每次要动态获取
  var file = upload.files[0];
  var start = 0;
  var chunk = 1024 * 10; //10KB
  var end = start + chunk;
  var size = file.size;
  while (start < size) {
    segment(file, start, end);
    start = end;
    end = start + chunk;
    if (end > size) {
      end = size;
    }
  }
}, false);

function segment(file, start, end) {
  var reader = new FileReader();
  reader.onload = function(evt) {
    console.log(['Read bytes: ', start, ' - ', end].join(''));
  };
  var blob = file.slice(start, end);
  reader.readAsBinaryString(blob);
}

七、formData   XMLHttpRequest Level 2添加了一个新的接口FormData。
  利用FormData对象,可以使用键值对来模拟一个完整的表单,然后使用XMLHttpRequest发送这个"表单"。
  使用FormData的最大优点就是我们可以异步上传一个二进制文件。
 

var formData = new FormData();
  formData.append("name", "value");//普通键值对
  formData.append("blob", blob); //传递一个blob对象
  formData.append("file", file); //传递一个file对象
  var oReq = new XMLHttpRequest();
  oReq.open("POST", "http://xx.com");
  oReq.send(formData);

  参考资料:
  https://developer.mozilla.org/zh-CN/docs/Using_files_from_web_applications    在web应用中使用文件
  http://javascript.ruanyifeng.com/htmlapi/file.html    文件和二进制数据的操作
  http://www.iunbug.com/archives/2012/06/04/208.html   [译]JavaScript文件操作基础
  http://www.iunbug.com/archives/2012/06/05/254.html   [译]JavaScript文件操作URL对象
  http://www.html5rocks.com/zh/tutorials/file/dndfiles/     通过 File API 使用 JavaScript 读取文件