CANVAS
Canvas API 可以用来动态生成和展示图形、图表、图像以及动画。最近用到了canvas,整理一下canvas的知识点。
Canvas特点
Canvas 本质上是一个位图画布,其上绘制的图形是不可缩放的,不能像SVG图像那样可以被放大缩小。此外,用canvas绘制出来的对象不属于页面DOM结构或者任何命名空间。SVG图像却可以在不同的分辨率下流畅地缩放,并且支持单击检测(能检测到鼠标点击了图像上的哪个点)。但是Cavas有两个优势: 首先,不需要将所有绘制图像中的每个图元当做对象存储,所以执行性能非常好;其次,在其他编程语言现有的优秀二维绘图API的基础上实现的Canvas API 比较简单。但是如果你的图像显示需要显著的交互行为,那么可以考虑使用SVG代替Canvas API。SVG也能用于绘制,而且它整合了浏览器的DOM。
Canvas基本用法及注意点
在使用canvas 时要先设置 width 和height属相,默认是宽为300,高为150。然后取得绘图上下文,即取得绘图上下文对象的引用。并且要进行检测getContext()是否存在。
1234var canvas = document.getElementById("canvas");if(canvas.getContext){}最好在canvas 标签上直接设置width和height,若是使用css设置宽高属性,在某些情况下会导致图像扭曲。
在获取context之后,即可以进行绘制矩形、绘制路径、绘制文本、变换、绘制图像、阴影、渐变等功能。在绘制这些图形时用到最多的是context的fillStyle 和 strokeStyle属性。 这两个属性的值可以是字符串、渐变对象或模式对象。
渐变对象 context.createLinearGradient()、模式对象 context.createPattern() 可以赋值给 stroke、fill 这两个属性。
只有当对路径应用context 的stroke() 或者fill() 方法时,结果才能显现出来,否则在只有在显示图像、显示文本或者绘制、填充和清除矩形框的时候,canvas才会马上更新。
若是先stroke后fill , 填充会覆盖一部分描边路径。若设置路径宽度是4px,这个宽度是沿路径线居中对齐的,而填充是把路径轮廓内部所有像素全部填充,所以会覆盖描边路径的一半。若是想看到完整的路径,则可以先填充再描边。
不论开始绘制何种图形,第一个需要调用的就是context的beginPath(),用来通知canvas将要绘制一个新的图形了。对于canvas来说,beginPath()函数最大的用处是canvas需要据此来计算图形的内部和外部的范围,以便完成后续的stroke和fill。
在canvas 时插入图片时,必须等到图片完全加载后才能对其操作,浏览器通常会在页面脚本执行的同时异步加载图片。如果视图在图片未完全加载之前就将其呈现在canvas上,那么canvas将不会显示任何图片。
12345var img = new Image();img.src ="x.png";img.onload = function(){}使用canvas上的toDataURL()方法,可以导出在canvas元素上绘制的图像,参数表示图像的MIME类型格式。如果绘制到画布上的图像源自不同的域,toDataURL()会抛出错误。
关于可重用代码: 一般绘制都应从原点(坐标系的0,0点)开始,应用变换(缩放、平移、旋转),然后不断修改代码直到达到希望的效果。所以经常会用到 translate()、save()、 restore() 函数。
使用图像数据
可以通过context的getImageData()取得原始图像数据,返回的是ImageData 的实例。每个ImageData对象都有三个属性:width、height 和data。其中data属性是一个数组,保存着图像中每一个像素的数据。在data数组中,每一个像素用4个元素来保存,分别表示红、绿、蓝和透明度值。因此,第一个像素的数据就保存在数组的第0到第3个元素中,我们可以在直接访问到这些元素图像数据,并且能以各种方式来修改这些数据。修改之后使用context.putImageData()将图像数据绘制到画布上。
由于安全问题,在getImageData() 调用时,如果canvas中的图像来自其他域,就会抛出安全异常。因为在没有canvas API之前,无法使用编程的方式获取下载图片的像素信息,来自其他网站的私有图片可以显示在本地,但无法被读取或者复制。
- 两个会应用到context中所有绘制操作的属性:globalAlpha 和 globalCompositionOperation 。
有趣的demo
模拟热图的实现:
1234567891011121314151617181920212223242526272829303132333435canvas.onmousemove = function(e) {x = e.clientX - e.target.offsetLeft;y = e.clientY - e.target.offsetTop;addToPoint(x, y)}function getColor(intensity) {var colors = ["#072933", "#2E4045", "#8C593B", "#B2814E", "#FAC268", "#FAD237"];return colors[Math.floor(intensity / 2)];}function drawPoint(x, y, radius) {context.fillStyle = getColor(radius);radius = Math.sqrt(radius) * 6;context.beginPath();context.arc(x, y, radius, 0, Math.PI * 2, true)context.closePath();context.fill();}function addToPoint(x, y) {x = Math.floor(x / SCALE);y = Math.floor(y / SCALE);if (!points[[x, y]]) {points[[x, y]] = 1;} else if (points[[x, y]] == 10) {return} else {points[[x, y]]++;}drawPoint(x * SCALE, y * SCALE, points[[x, y]]);}用points对应的值去获取对应的颜色和绘制相应大小的图形。
模拟阴天闪电
123456if (Math.random() > .01) {context.globalAlpha = 0.65; // 不透明度 越大越不透明context.fillStyle = '#000000';context.fillRect(0, 0, 400, 600);context.globalAlpha = 1.0;}大部分情况下天气较暗,用来模拟阴天,配合requestAnimationFrame 实现闪电
模拟下雨
1234567context.fillStyle = context.createPattern(rain, 'repeat');var now = Date.now();context.save();context.translate(-256 + (0.1 * now) % 256, -256 + (0.5 * now) % 256);context.fillRect(0, 0, 400 + 256, 600 + 256);context.restore();用一张下雨的图片铺满,然后根据时间 来设置原点位置,并且使得画的矩形比下雨所需的矩形要大,以保证覆盖。配合requestAnimationFrame 实现下雨
模拟下雨与下雪
12345678910111213context.fillStyle = context.createPattern(rain, 'repeat');var now = Date.now();context.save();context.translate(-256 + (0.1 * now) % 256, -256 + (0.5 * now) % 256);context.fillRect(0, 0, 400 + 256, 600 + 256);context.restore();context.save();context.translate(-256 + (0.08 * now) % 256, -256 + (0.2 * now) % 256);context.fillRect(0, 0, 400 + 256, 600 + 256);context.restore();
下雨的同时下雪与只下雨的区别是下雪的速率比较小,下雨的速率比较大,所以设置两个不同的原点,同时绘制两个不同的图片,相互叠加,这样就能有雨雪的效果。
上面这几个例子都要配合requestAnimationFrame来实现动画。
引用文献
- 《HTML5高级程序设计》 第二章
- 《javascript 高级程序设计》