一、canvs基础
1、绘图环境
canvas 的能力是通过 context 对象表现出来的,context一般称为绘图环境。
const context = canvas.getContext(DOMString)
DOMString 为 "2d" 时,context 是CanvasRenderingContext2D
对象;(本文重点)
DOMString 为 "webgl" 时,context 是WebGLRenderingContext
对象;
DOMString 为 "webgl2" 时,context 是WebGL2RenderingContext
对象;
DOMString 为 "bitmaprenderer" 时,context 是ImageBitmap
对象;
2、canvas 尺寸问题
- 默认大小 300*150
- canvas 尺寸
注意 canva 元素实际上有两套尺寸:元素大小E与绘图表面大小S。
当使用 <canvas width='600' height='300' id='canvas'> 这种方式设置大小时,是同时设置了E和S为600*300;
当使用CSS设定canvas大小时,仅仅是设置了E,不会影响S(仍是300*150);
当S和E的大小不一致时,浏览器就会将S进行缩放,使之与E大小相同,这也会导致绘制在S上的内容跟着被缩放。这往往不是期待的结果,所以应该避免用CSS设置canvas大小 - 当CSS设置的宽高小于canvas直接设置的宽高,则绘制结果会被缩小;反之放大
3、canvas 自身的API
- 两属性
width: 绘图表面宽度,height: 绘图表面高度 - 三方法
getContext
toDataURL(type,quality),canvas 转 data URL, 可赋值给 img 的 src
toBlob(callback,type),canvas 转文件对象,可以以图片文件格式保存
4、CanvasRenderingContext2D 属性
CanvasRenderingContext2D 对象实例 instance 共有16个属性,只要设置了这些属性,就会影响 instance 调用绘制方法时的表现,常用的有:
- fillStyle & strokeStyle
设置填充和描边 - font & textAlign & textBaseline
设置字体及水平和垂直对其方式 - lineCap & lineWidth & lineJoin & miterLimit
设置线段端点、线段屏幕像素宽度、线段交汇等绘制方式 - shadowBlur & shadowColor & shadowOffsetX & shadowOffsetY
设置阴影参数 - globalAlpha & globalCompsiteOperation
设置全局透明度,图像合成方式
注意:可使用 instance 的 save() 和 restore() 方法来临时修改 instance 的属性
5、事件处理
- 鼠标事件中,需要注意坐标转换
onmousedonwn,onmousemove,onmouseup,onmouseout etc
function windowToCanvas(canvas, x, y) {
var bbox = canvas.getBoundingClientRect();
console.log("canvas", canvas.width, canvas.height); // 绘图表面大小S
console.log("bbox", bbox.width, bbox.height); // 元素大小E,受CSS设置影响
return {
x: (x - bbox.left)* (canvas.width / bbox.width), // 如果被缩放,则坐标也跟着缩放
y: (y - bbox.top)* (canvas.height / bbox.height)
};
}
canvas.onmousemove = function(e) {
var loc = windowToCanvas(canvas, e.clientX, e.clientY);
// ...
};
- 键盘事件
由于 canvas 是不可获焦元素,所以无法直接在 canvas 上监听键盘事件。不过可以在 documen或window上监听,通常需要处理 keydown,keypress,keyup 三种事件,一般在游戏处理中会遇到。
6、离屏 canvas
1、一般用来保存数据,不展示在浏览器页面上,创建的两种方式:a、css 方式设置为 display:none;b、JS创建 document.createElement('canvas');
2、与HTML结合使用:可以采用 CSS 定位的方式,将 HTML 元素置于 canvas 元素之上,比如:在 canva 上叠加一个 div panel 作为某个开关控制界面;选景橡皮筋;时钟等
3、也可使用两个 canvas,一个用来显示,另一个用来做数据准备和处理,这种方式通常效率高,但比较耗费内存
7、Canvas 绘图基本套路
1、准备一个绘制背景的函数,用于每次擦除上一次绘制的结果
2、绘制辅助线
3、监听事件,做坐标转换 windowToCanvas
4、绘制内容的保存于恢复
function saveDrawingSurface() {
drawingSurfaceImageData = context.getImageData(0, 0,
canvas.width,
canvas.height);
}
function restoreDrawingSurface() {
context.putImageData(drawingSurfaceImageData, 0, 0);
}
5、三事件
onmousedown:保存初始 canvas 绘制状态
onmousemove: 更新位置信息,并不断调用初始 canvas 绘制状态来擦除上一个绘制
onmouseup: 调用初始 canvas 绘制状态来擦除上一个绘制,并根据onmousemove保存的信息做最终绘制,将结果绘制在 canvas 上
二、canvas 图形文本绘制
1、矩形绘制
- clearReact(x,y,width,height)
- strokeReact(x,y,width,height)
相关属性:strokeStyle,lineJoin,lineWidth - fillReact(x,y,width,height)
相关属性:fillStyle - rect(x,y,width,height)
逆时针绘制
2、渐变色与图案和阴影
fillStyle 和 strokeStyle 可以是任意有效的css颜色值或者渐变色以及图像Pattern
- 线性渐变
gradient = context.createLinearGradient(0, 0, 0, canvas.height / 2);
gradient.addColorStop(0, "blue");
gradient.addColorStop(0.25, "white");
gradient.addColorStop(0.5, "purple");
gradient.addColorStop(0.75, "red");
gradient.addColorStop(1, "yellow");
context.fillStyle = gradient;
- 径向渐变
gradient = context.createRadialGradient(
canvas.width / 2,
canvas.height,
10,
canvas.width / 2,
0,
100
);
gradient.addColorStop(0, "blue");
gradient.addColorStop(0.25, "white");
gradient.addColorStop(0.5, "purple");
gradient.addColorStop(0.75, "red");
gradient.addColorStop(1, "yellow");
context.fillStyle = gradient;
- 图像 Pattern
var image = new Image();
var pattern = context.createPattern(image, repeatString);
context.fillStyle = pattern;
- 阴影
1、绘制阴影的条件:a、shadowColor 不是全透明;b、shadowBlur,shadowOffsetX,shadowOffsetY 有一个不为0
2、shadowOffsetX,shadowOffsetY 为负值时,可绘制内嵌阴影
3、路径
canvas 某一时刻只能有一条路径存在,这条路径可以包含多条子路径。用 beginPath 来开始一条新路径或清除上一次子路径
- 子路径
当画完一个图形时,如果不调用 beginPath 就开始画另一个图形,那么相当于当前路径里拥有两条子路径(这种情况会导致第一个图形再被绘制一遍);如果调用了 beginPath,则当前路径里只有一条子路径。 - 路径方向
绘制图形时,以逆时针顺时针方向作为路径方向,如 rect 永远是逆时针,arc 最后一个参数为true则顺时针 - 非零环绕规则
如果当前路径是循环的或者子路径有交叉,则在调用 fill 方法填充时,会进行非零环绕判断,这个跟路径方向有关:从填充区任意一点向外画一条线,这条线会与路径相交,顺时针加1,逆时针减一,最后值如果不为0,则 fill 会生效。
4、线段
- 1px 线段
调用 lineTo,moveTo 方法时坐标要加个0.5,否则绘制1px会变成2px。因为当在两个像素中间开始绘制1px时,绘制系统会各占用两个像素中的0.5px,而0.5px不会单独绘制,被占据0.5px的那个1px像素会被完整绘制,从而变成了2px - 线段
1、lineCap
默认butt,无任何效果;round 增加一个半圆;square 增加一个半正方形
2、lineJoin
控制两条线段接合处,默认bevel,直线连接;miter 变为矩形;round 圆弧连接 - moveTo(x,y)
- lineTo(x,y)
5、圆弧
- arc
arc(radius,x,y,start,end,ccw) - arcTo
arcTo(x1,y1,x2,y2,radius),适合画圆角那种弧
6、贝塞尔曲线
- 二阶贝塞尔
quadraticCurveTo(cx,cy,x,y) 一个控制点和一个锚点,另一个锚点是当前路径中最后一个点 - 三阶贝塞尔
bezierCurveTo(c1x,c1y,c2x,c2y,x,y) 二个控制点和一个锚点,另一个锚点是当前路径中最后一个点
7、文本
textAlign: left,center,right
textBaseline: top,middle,bottom
1、三属性 font,textAlign,textBaseline
2、三方法 strokeText, fillText, measureText
strokeText(text,x,y,maxWidth) 指定文本超过maxWidth会被缩放
measureText(text).width 返回指定文本宽度
3、水平垂直居中
context.textAlign = 'center'
context.textBaseline = 'middle'
function drawText(text){
context.fillStyle = 'blue'
context.fillText(text,canvas.width/2,canvas.height/2)
}
8、坐标变换
注意每次变换前,用 save 和 restore 来保存原来绘制上下文
- 平移
context.translate(x,y) - 旋转
context.rotate(angle) - 缩放
context.scale(x,y) - transform & setTransform
结合了以上三种变换
x1=ax+cy+e
y1=bx+dy+f
9、剪辑区域
1、由路径定义的一片区域,如一个三角形,矩形,圆形,然后调用 clip 即可得到剪辑区域
2、默认和 canvas 大小一致
3、设置剪辑区域后,浏览器将只对该区域进行绘制
4、调用clip会把剪辑区域设为当前剪辑区域与当前路径定义的区域的交集,故clip 的调用经常在 save 和 restore 之间,这是为了防止剪辑区域越来越小
三、canvas 图像与视频绘制
主要是 drawImage,getImageData,putImageData,createImageData 四个 API
- drawImage,受全局设置影响(globalAlpha,globalCompositeOperation)
将一幅图片,一个canvas内容,视频某一帧绘制到当前 canvas 中
1、drawImage(imageOrCanvas,dx,dy) 不缩放,从 (dx,dy) 处开始绘制
2、drawImage(imageOrCanvas,dx,dy,dw,dh) 缩放,从 (dx,dy) 处开始,绘制大小(dw,dh)
3、drawImage(imageOrCanvas,sx,sy,sw,sh,dx,dy,dw,dh),指定部分绘制到指定位置 - getImageData(left,top,width,height)
获取图像像素数据,返回值{width,height,data},注意是设备像素,不是CSS像素 - putImageData(imageData,x,y,dx,dy,dw,dh)
以给定的图像数据重新绘制到(x,y)处,大小为(dx,dy,dw,dh),注意: x,y 是 CSS 像素,dx,dy,dw,dh 是设备像素。此外 putImageData 不受全局设置的影响 - createImageData()
创建一个空 ImageData 对象