#JavaScript学习#第十五章:使用Canvas绘图

492 阅读18分钟

Tips: 内容为知识梳理


目录

  1. 基本用法

  2. 2D上下文

  3. WebGL


1. 基本用法

<canvas>元素可以指定一个绘图区域,并设置其height和width属性

var drawing = document.getElementById("drawing"); 
if (drawing.getContext){ 
var context = drawing.getContext("2d"); //更多代码 
} 

这样就可以取得绘图上下文

var drawing = document.getElementById("drawing");  
//确定浏览器支持<canvas>元素 
if (drawing.getContext){ 
//取得图像的数据 URI     
var imgURI = drawing.toDataURL("image/png"); 
//显示图像     
var image = document.createElement("img");     
image.src = imgURI;     
document.body.appendChild(image); 
}

用toDataURL方法取得图像URL

2. 2D上下文

2.1 填充和描边

2D 上下文的两种基本绘图操作是填充和描边。填充,就是用指定的样式(颜色、渐变或图像)填 充图形;描边,就是只在图形的边缘画线。大多数 2D上下文操作都会细分为填充和描边两个操作,而操作的结果取决于两个属性:fillStyle 和 strokeStyle。 这两个属性的值可以是字符串、渐变对象或模式对象,而且它们的默认值都是"#000000"。如果为 它们指定表示颜色的字符串值,可以使用 CSS 中指定颜色值的任何格式,包括颜色名、十六进制码、 rgb、rgba、hsl 或 hsla。

var drawing = document.getElementById("drawing");  

//确定浏览器支持<canvas>元素 
if (drawing.getContext){ 

var context = drawing.getContext("2d");   
  context.strokeStyle = "red";    
   context.fillStyle = "#0000ff"; 
   }

所有涉及描边和填充的操作都将使用这两个样式,直至重新设置这两个值。

2.2绘制矩形

矩形是唯一一种可以直接在 2D 上下文中绘制的形状。与矩形有关的方法包括 fillRect()、 strokeRect()和 clearRect()。这三个方法都能接收 4个参数:矩形的 x坐标、矩形的 y坐标、矩形 宽度和矩形高度。这些参数的单位都是像素。 首先,fillRect()方法在画布上绘制的矩形会填充指定的颜色。填充的颜色通过 fillStyle 属 性指定,比如:

var drawing = document.getElementById("drawing");  

//确定浏览器支持<canvas>元素 
if (drawing.getContext){ 
var context = drawing.getContext("2d"); 
//绘制红色矩形     
context.fillStyle = "#ff0000";     
context.fillRect(10, 10, 50, 50); 

//绘制半透明的蓝色矩形     
context.fillStyle = "rgba(0,0,255,0.5)";     
context.fillRect(30, 30, 50, 50); 
} 

用strokeStyle和strokeRect方法绘制边框 用clearRect清除矩形区域

2.3 绘制路径

要绘制路径,首先必须调用 beginPath()方法,表示要开始 绘制新路径。然后,再通过调用下列方法来实际地绘制路径。

  • arc(x, y, radius, startAngle, endAngle, counterclockwise):以(x,y)为圆心绘 制一条弧线,弧线半径为 radius,起始和结束角度(用弧度表示)分别为 startAngle 和 endAngle。后一个参数表示 startAngle 和 endAngle 是否按逆时针方向计算,值为 false 表示按顺时针方向计算。
  • arcTo(x1, y1, x2, y2, radius):从上一点开始绘制一条弧线,到(x2,y2)为止,并且以 给定的半径 radius 穿过(x1,y1)。
  • bezierCurveTo(c1x, c1y, c2x, c2y, x, y):从上一点开始绘制一条曲线,到(x,y)为 止,并且以(c1x,c1y)和(c2x,c2y)为控制点。
  • lineTo(x, y):从上一点开始绘制一条直线,到(x,y)为止。
  • moveTo(x, y):将绘图游标移动到(x,y),不画线。
  • quadraticCurveTo(cx, cy, x, y):从上一点开始绘制一条二次曲线,到(x,y)为止,并 且以(cx,cy)作为控制点。
  • rect(x, y, width, height):从点(x,y)开始绘制一个矩形,宽度和高度分别由 width 和 height 指定。这个方法绘制的是矩形路径,而不是 strokeRect()和 fillRect()所绘制的独 立的形状。

创建了路径后,接下来有几种可能的选择。如果想绘制一条连接到路径起点的线条,可以调用 closePath()。如果路径已经完成,你想用 fillStyle 填充它,可以调用 fill()方法。另外,还可 以调用 stroke()方法对路径描边,描边使用的是 strokeStyle。后还可以调用 clip(),这个方法 可以在路径上创建一个剪切区域。

2.4 绘制文本

绘制文本主要有两个 方法:fillText()和 strokeText()。这两个方法都可以接收 4 个参数:要绘制的文本字符串、x 坐 标、y坐标和可选的大像素宽度。而且,这两个方法都以下列 3个属性为基础。

  • font:表示文本样式、大小及字体,用 CSS中指定字体的格式来指定,例如"10px Arial"。

  • textAlign:表示文本对齐方式。可能的值有"start"、"end"、"left"、"right"和"center"。 建议使用"start"和"end",不要使用"left"和"right",因为前两者的意思更稳妥,能同时 适合从左到右和从右到左显示(阅读)的语言。

  • textBaseline:表示文本的基线。可能的值有"top"、"hanging"、"middle"、"alphabetic"、 "ideographic"和"bottom"。

    context.font = "bold 14px Arial";
     context.textAlign = "center"; 
     context.textBaseline = "middle"; 
     context.fillText("12", 100, 20); 
    

这些代码绘制了数字12

2D 上下文提供了辅助确定 文本大小的方法 measureText()。这个方法接收一个参数,即要绘制的文本;返回一个 TextMetrics 对象。返回的对象目前只有一个 width 属性,但将来还会增加更多度量属性。 measureText()方法利用 font、textAlign 和 textBaseline 的当前值计算指定文本的大小。 比如,假设你想在一个 140像素宽的矩形区域中绘制文本 Hello world!,下面的代码从 100像素的字体 大小开始递减,终会找到合适的字体大小。

var fontSize = 100; context.font = fontSize + "px Arial";  

while(context.measureText("Hello world!").width > 140){    
 fontSize--;     
context.font = fontSize + "px Arial"; 
} 
context.fillText("Hello world!", 10, 10); 
context.fillText("Font size is " + fontSize + "px", 10, 50); 

2.5 变换

可以通过如下方法来修改变换矩阵。

  • rotate(angle):围绕原点旋转图像 angle 弧度。
  • scale(scaleX, scaleY):缩放图像,在 x方向乘以 scaleX,在 y方向乘以 scaleY。scaleX 和 scaleY 的默认值都是 1.0。
  • translate(x, y):将坐标原点移动到(x,y)。执行这个变换之后,坐标(0,0)会变成之前由(x,y) 表示的点。
  • transform(m1_1, m1_2, m2_1, m2_2, dx, dy):直接修改变换矩阵,方式是乘以如下 矩阵。 m1_1 m1_2 dx m2_1 m2_2 dy 0 0 1
  • setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy):将变换矩阵重置为默认状态,然后 再调用 transform()。

无论是刚才执行的变换,还是 fillStyle、strokeStyle 等属性,都会在当前上下文中一直有效, 除非再对上下文进行什么修改。虽然没有什么办法把上下文中的一切都重置回默认值,但有两个方法可 以跟踪上下文的状态变化。如果你知道将来还要返回某组属性与变换的组合,可以调用 save()方法。 调用这个方法后,当时的所有设置都会进入一个栈结构,得以妥善保管。然后可以对上下文进行其他修 改。等想要回到之前保存的设置时,可以调用 restore()方法,在保存设置的栈结构中向前返回一级, 恢复之前的状态。连续调用 save()可以把更多设置保存到栈结构中,之后再连续调用 restore()则可 以一级一级返回。

context.fillStyle = "#ff0000"; context.save(); 

context.fillStyle = "#00ff00"; context.translate(100, 100); context.save(); 

context.fillStyle = "#0000ff"; context.fillRect(0, 0, 100, 200); //从点(100,100)开始绘制蓝色矩形 

context.restore(); context.fillRect(10, 10, 100, 200); //从点(110,110)开始绘制绿色矩形 

context.restore();
context.fillRect(0, 0, 100, 200); //从点(0,0)开始绘制红色矩形 

2.6 绘制图像

D绘图上下文内置了对图像的支持。如果你想把一幅图像绘制到画布上,可以使用 drawImage() 方法。根据期望的终结果不同,调用这个方法时,可以使用三种不同的参数组合。简单的调用方式 是传入一个 HTML 元素,以及绘制该图像的起点的 x和 y坐标。例如:

var image = document.images[0]; context.drawImage(image, 10, 10);

2DDrawImageExample01.htm 这两行代码取得了文档中的第一幅图像,然后将它绘制到上下文中,起点为(10,10)。绘制到画布上 的图像大小与原始大小一样。如果你想改变绘制后图像的大小,可以再多传入两个参数,分别表示目标 宽度和目标高度。通过这种方式来缩放图像并不影响上下文的变换矩阵。例如:

context.drawImage(image, 50, 10, 20, 30);

2DDrawImageExample01.htm 执行代码后,绘制出来的图像大小会变成 20×30像素。 除了上述两种方式,还可以选择把图像中的某个区域绘制到上下文中。drawImage()方法的这种调 用方式总共需要传入 9个参数:要绘制的图像、源图像的 x坐标、源图像的 y坐标、源图像的宽度、源 图像的高度、目标图像的 x 坐标、目标图像的 y 坐标、目标图像的宽度、目标图像的高度。这样调用 drawImage()方法可以获得多的控制。例如:

context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60);

2DDrawImageExample01.htm 这行代码只会把原始图像的一部分绘制到画布上。原始图像的这一部分的起点为(0,10),宽和高都 是 50像素。终绘制到上下文中的图像的起点是(0,100),而大小变成了 40×60像素。

2.7 阴影

2D上下文会根据以下几个属性的值,自动为形状或路径绘制出阴影。

  • shadowColor:用 CSS颜色格式表示的阴影颜色,默认为黑色。
  • shadowOffsetX:形状或路径 x轴方向的阴影偏移量,默认为 0。
  • shadowOffsetY:形状或路径 y轴方向的阴影偏移量,默认为 0。
  • shadowBlur:模糊的像素数,默认 0,即不模糊。

2.8 渐变

渐变由 CanvasGradient 实例表示,很容易通过 2D上下文来创建和修改。要创建一个新的线性渐 变,可以调用 createLinearGradient()方法。这个方法接收 4个参数:起点的 x坐标、起点的 y坐 标、终点的 x 坐标、终点的 y 坐标。调用这个方法后,它就会创建一个指定大小的渐变,并返回 CanvasGradient 对象的实例。 创建了渐变对象后,下一步就是使用 addColorStop()方法来指定色标。这个方法接收两个参数: 色标位置和 CSS颜色值。色标位置是一个 0(开始的颜色)到 1(结束的颜色)之间的数字。

var gradient = context.createLinearGradient(30, 30, 70, 70); 
gradient.addColorStop(0, "white"); 
gradient.addColorStop(1, "black"); 

然后就可以把 fillStyle 或 strokeStyle 设置为这个对象,从而使用渐变来绘制 形状或描边:

//绘制红色矩形 
context.fillStyle = "#ff0000"; 
context.fillRect(10, 10, 50, 50); 
//绘制渐变矩形 
context.fillStyle = gradient; 
context.fillRect(30, 30, 50, 50);

确保渐变 与形状对齐非常重要,有时候可以考虑使用函数来确保坐标合适

function createRectLinearGradient(context, x, y, width, height){      
return context.createLinearGradient(x, y, x+width, y+height); } 

这个函数基于起点的x和y坐标以及宽度和高度值来创建渐变对象,从而让我们可以在fillRect() 中使用相同的值

var gradient = createRectLinearGradient(context, 30, 30, 50, 50);  

gradient.addColorStop(0, "white"); 
gradient.addColorStop(1, "black"); 

//绘制渐变矩形 
context.fi llStyle = gradient; 
context.fillRect(30, 30, 50, 50);

要创建径向渐变(或放射渐变),可以使用 createRadialGradient()方法。这个方法接收 6个参 数,对应着两个圆的圆心和半径。前三个参数指定的是起点圆的原心(x 和 y)及半径,后三个参数指 定的是终点圆的原心(x 和 y)及半径

2.9 模式

模式其实就是重复的图像,可以用来填充或描边图形。要创建一个新模式,可以调用 createPattern()方法并传入两个参数:一个 HTML 元素和一个表示如何重复图像的字符串。 其中,第二个参数的值与 CSS的 background-repeat 属性值相同,包括"repeat"、"repeat-x"、 "repeat-y"和"no-repeat"。

var image = document.images[0],     
 pattern = context.createPattern(image, "repeat"); 

//绘制矩形 
context.fillStyle = pattern; 
context.fillRect(10, 10, 150, 150); 

2.10 使用图像数据

2D上下文的一个明显的长处就是,可以通过 getImageData()取得原始图像数据。这个方法接收 4个参数:要取得其数据的画面区域的 x和 y坐标以及该区域的像素宽度和高度。例如,要取得左上角 坐标为(10,5)、大小为 50×50像素的区域的图像数据,可以使用以下代码:

var imageData = context.getImageData(10, 5, 50, 50); 这里返回的对象是 ImageData 的实例。每个 ImageData 对象都有三个属性:width、height 和 data。其中 data 属性是一个数组,保存着图像中每一个像素的数据。在 data 数组中,每一个像素用4个元素来保存,分别表示红、绿、蓝和透明度值。因此,第一个像素的数据就保存在数组的第 0到第 3个元素中,例如:

var data = imageData.data,    
 red = data[0],   
   green = data[1],    
    blue = data[2],    
     alpha = data[3];

数组中每个元素的值都介于 0到 255之间(包括 0和 255)。能够直接访问到原始图像数据,就能够 以各种方式来操作这些数据。

2.11 合成

还有两个会应用到 2D 上下文中所有绘制操作的属性:globalAlpha 和 globalComposition- Operation。其中,globalAlpha 是一个介于 0和 1之间的值(包括 0和 1),用于指定所有绘制的透 明度。默认值为 0。如果所有后续操作都要基于相同的透明度,就可以先把 globalAlpha 设置为适当 值,然后绘制,后再把它设置回默认值 0 第二个属性 globalCompositionOperation 表示后绘制的图形怎样与先绘制的图形结合。这个 属性的值是字符串,可能的值如下。

  • source-over(默认值):后绘制的图形位于先绘制的图形上方。
  • source-in:后绘制的图形与先绘制的图形重叠的部分可见,两者其他部分完全透明。
  • source-out:后绘制的图形与先绘制的图形不重叠的部分可见,先绘制的图形完全透明。
  • source-atop:后绘制的图形与先绘制的图形重叠的部分可见,先绘制图形不受影响。
  • destination-over:后绘制的图形位于先绘制的图形下方,只有之前透明像素下的部分才可见。
  • destination-in:后绘制的图形位于先绘制的图形下方,两者不重叠的部分完全透明。
  • destination-out:后绘制的图形擦除与先绘制的图形重叠的部分。
  • destination-atop:后绘制的图形位于先绘制的图形下方,在两者不重叠的地方,先绘制的 图形会变透明。
  • lighter:后绘制的图形与先绘制的图形重叠部分的值相加,使该部分变亮。
  • copy:后绘制的图形完全替代与之重叠的先绘制图形。
  • xor:后绘制的图形与先绘制的图形重叠的部分执行“异或”操作。

3.WebGL

WebGL是针对Canvas的3D上下文

3.1 类型化数组

类型化数组是WebGL项目中执行各种操作的重要基础,短篇幅难以说清,详情请见红宝书15章。

3.2 WebGL上下文

var drawing = document.getElementById("drawing"); 

//确定浏览器支持<canvas>元素 if (drawing.getContext){ 

var gl = drawing.getContext("experimental-webgl");     
if (gl){         //使用 WebGL     
} 
}

取得上下文的操作如上 通过给 getContext()传递第二个参数,可以为 WebGL上下文设置一些选项。 这个参数本身是一 个对象,可以包含下列属性。

  • alpha:值为 true,表示为上下文创建一个 Alpha通道缓冲区;默认值为 true。 + depth:值为 true,表示可以使用 16位深缓冲区;默认值为 true。

  • stencil:值为 true,表示可以使用 8位模板缓冲区;默认值为 false。

  • antialias:值为 true,表示将使用默认机制执行抗锯齿操作;默认值为 true。

  • premultipliedAlpha:值为 true,表示绘图缓冲区有预乘 Alpha值;默认值为 true。

  • preserveDrawingBuffer:值为 true,表示在绘图完成后保留绘图缓冲区;默认值为 false。

     var gl = drawing.getContext("experimental-webgl", { alpha: false}); 
    

如上所示

  1. 常量 如果你熟悉 OpenGL,那肯定会对各种操作中使用非常多的常量印象深刻。这些常量在 OpenGL中 都带前 GL_。在 WebGL 中,保存在上下文对象中的这些常量都没有 GL_前。比如说, GL_COLOR_BUFFER_BIT 常量在 WebGL上下文中就是 gl.COLOR_BUFFER_BIT。WebGL以这种方式支 持大多数 OpenGL常量
  2. 方法命名 OpenGL(以及 WebGL)中的很多方法都试图通过名字传达有关数据类型的信息。如果某方法可以 接收不同类型及不同数量的参数,看方法名的后就可以知道。方法名的后会包含参数个数(1到 4) 和接收的数据类型(f 表示浮点数,i 表示整数)。例如,gl.uniform4f()意味着要接收 4个浮点数, 而 gl.uniform3i()则表示要接收 3个整数。
  3. 准备绘图 在实际操作 WebGL上下文之前,一般都要使用某种实色清除<canvas>,为绘图做好准备。为此, 首先必须使用 clearColor()方法来指定要使用的颜色值,该方法接收 4个参数:红、绿、蓝和透明度。 每个参数必须是一个 0到 1之间的数值,表示每种分量在终颜色中的强度
  4. 视口与坐标
  • 开始绘图之前,通常要先定义 WebGL的视口(viewport)。默认情况下,视口可以使用整个<canvas> 区域。要改变视口大小,可以调用 viewport()方法并传入 4个参数:(视口相对于<canvas>元素的) x坐标、y坐标、宽度和高度
  • 视口坐标与我们通常熟悉的网页坐标不一样。视口坐标的原点(0,0)在<canvas>元素的左下角
  • 另外,视口内部的坐标系与定义视口的坐标系也不一样。在视口内部,坐标原点(0,0)是视口的中心 点,因此视口左下角坐标为(1,1),而右上角坐标为(1,1)
  1. 缓冲区 顶点信息保存在 JavaScript的类型化数组中,使用之前必须转换到 WebGL的缓冲区。要创建缓冲区,可以调用 gl.createBuffer(),然后使用 gl.bindBuffer()绑定到 WebGL 上下文。这两步做完之 后,就可以用数据来填充缓冲区了。

       var buffer = gl.createBuffer(); 
       gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 
       gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0.5, 1]), gl.STATIC_DRAW); 
    
  2. 错误 JavaScript 与 WebGL 之间的一个大的区别在于,WebGL 操作一般不会抛出错误。为了知道是否 有错误发生,必须在调用某个可能出错的方法后,手工调用 gl.getError()方法

  3. 着色器 着色器(shader)是 OpenGL中的另一个概念。WebGL中有两种着色器:顶点着色器和片段(或像 素)着色器。顶点着色器用于将 3D 顶点转换为需要渲染的 2D 点。片段着色器用于准确计算要绘制的每个像素的颜色。

  4. 编写着色器 GLSL是一种类 C语言,专门用于编写 OpenGL着色器。详细请见红宝书

  5. 编写着色器程序 浏览器不能理解 GLSL程序,因此必须准备好字符串形式的 GLSL程序,以便编译并链接到着色器 程序。为便于使用,通常是把着色器包含在页面的<script>标签内,并为该标签指定一个自定义的 type 属性。由于无法识别 type 属性值,浏览器不会解析<script>标签中的内容,但这不影响你读写其中 的代码。

     <script type="x-webgl/x-vertex-shader" id="vertexShader"> 
     attribute vec2 aVertexPosition; 
     void main() {         
      gl_Position = vec4(aVertexPosition, 0.0, 1.0); 
      } 
      </script> 
      <script type="x-webgl/x-fragment-shader" id="fragmentShader"> 
      uniform vec4 uColor; 
     void main() {         
     gl_FragColor = uColor; 
     } 
     </script> 
    

然后,可以通过 text 属性提取出<script>元素的内容:

	var vertexGlsl = 
	 document.getElementById("vertexShader").text,      
	fragmentGlsl = document.getElementById("fragmentShader").text

取得了 GLSL字符串之后,接下来就是创建着色器对象。要创建着色器对象,可以调用 gl.create- Shader()方法并传入要创建的着色器类型(gl.VERTEX_SHADER 或 gl.FRAGMENT_SHADER)。编译着色 器使用的是 gl.compileShader()。
  1. 为着色器传入值 前面定义的着色器都必须接收一个值才能工作。为了给着色器传入这个值,必须先找到要接收这个 值的变量。对于 Uniform变量,可以使用 gl.getUniformLocation(),这个方法返回一个对象,表示 Uniform变量在内存中的位置。然后可以基于变量的位置来赋值
  2. 调试着色器和程序 与 WebGL 中的其他操作一样,着色器操作也可能会失败,而且也是静默失败。如果你想知道着色 器或程序执行中是否发生了错误,必须亲自询问 WebGL上下文。
  3. 绘图 WebGL 只能绘制三种形状:点、线和三角。其他所有形状都是由这三种基本形状合成之后,再绘 制到三维空间中的。执行绘图操作要调用 gl.drawArrays()或 gl.drawElements()方法,前者用于 数组缓冲区,后者用于元素数组缓冲区。
  4. 纹理 WebGL的纹理可以使用 DOM中的图像。要创建一个新纹理,可以调用 gl.createTexture(), 然后再将一幅图像绑定到该纹理。如果图像尚未加载到内存中,可能需要创建一个 Image 对象的实例, 以便动态加载图像。图像加载完成之前,纹理不会初始化,因此,必须在 load 事件触发后才能设置纹 理
  5. 读取像素 与 2D 上下文 类似,通过 WebGL 上下文也能读取像素值。读取像素值的方法 readPixels()与 OpenGL 中的同名方法只有一点不同,即后一个参数必须是类型化数组。像素信息是从帧缓冲区读取的,然后保存在类型化数组中。readPixels()方法的参数有:x、y、宽度、高度、图像格式、数据类 型和类型化数组。