Canvas 使用指南

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

推荐阅读更多精彩内容