要使用<canvas>元素,必须先设置其width和height属性,指定能够绘图的区域大小。出如今开始和结束标签中的内容是后备信息,若是浏览器不支持<canvas>元素,就会显示这些信息。以下例子:web
<canvas id="drawing" width="200" height="200"> A drawing of something.</canvas>
与其它元素同样,<canvas>元素对应的DOM元素对象也有width和height属性,能够随意修改。并且,也能够经过CSS为该元素添加样式,若是不添加任何样式或者不绘制任何图形,在页面中是看不到该元素的。canvas
要在这块画布(canvas)上绘图,须要取得绘图上下文。以下代码:数组
var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); //更多代码 }
使用toDataURL()方法,能够导出在<canvas>元素上绘制的图像。这个方法接收一个参数,即图像的MIME类型格式,并且适合用于建立图像的任何上下文。好比,要取得画布中的一副PNG格式的图像,以下代码:浏览器
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); }
使用2D绘图上下文提供的方法,能够绘制简单的2D图形,好比矩形、弧线和路径。2D上下文的坐标开始于<canvas>元素的左上角,原点坐标是(0,0)。全部坐标值都基于这个原点计算。x值越大越靠右,y值越大越靠下。app
2D上下文的两种基本绘图操做是填充和描边。填充,就是用指定的样式(颜色、渐变或图像)填充图形;描边,就是只在图形的边缘画线。大多数2D上下文操做都会细分为填充和描边两个操做,而操做的结果取决于两个属性:fillStyle和strokeStyle。ide
这两个属性的值能够是字符串、渐变对象或模式对象。并且他们的默认值都是“#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"; }
矩形是惟一一种能够直接在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); }
strokeRect()方法在画布上绘制的矩形会使用指定的颜色描边。描边颜色经过strokeStyle属性指定。好比:webgl
var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); //绘制红色描边矩形 context.strokeStyle = "#ff0000"; context.strokeRect(10,10,50,50); //绘制半透明的蓝色描边矩形 context.strokeStyle = "rgba(0,0,255,0.5)"; context.strokeRect(30,30,50,50); }
以上代码绘制了两个重叠的矩形。不过,这两个矩形都只有框线,内部并无填充颜色。
最后,clearRect()方法用于清除画布上的矩形区域。本质上,这个方法能够把绘制上下文中的某一矩形区域变透明。经过绘制形状而后再清除指定区域,就能够生成有意思的效果,例如把某个形状切掉一块。以下例子:
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); //在两个矩形重叠的地方清除一个小矩形 context.clearRect(40,40,10,10); }
2D绘制上下文支持不少在画布上绘制路径的方法。经过路径能够创造出复杂的形状和线条。要绘制路径,首先必须调用beginPath()方法,表示要开始绘制新路径。而后,在经过下列方法来实际的绘制路径。
建立了路径后,接下来有几种可能的选择。若是想绘制一条想链接到路径起点的线条,能够调用closePath()。若是路径已经完成,你想用fillStyle填充它,能够调用fill()方法。另外,还能够调用stroke()方法对路径描边,描边使用的是strokeStyle。最后还能够调用clip(),这个方法能够在路径上建立一个剪切区域。
以下例子,绘制一个不带数字的时钟表盘。
var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); //开始路径 context.beginPath(); //绘制外圆 context.arc(100,100,99,0,2*Math.PI,false); //绘制内圆 context.moveTo(194,100); context.arc(100,100,94,0,2*Math.PI,false); //绘制分针 context.moveTo(100,100); context.lineTo(100,15); //绘制时针 context.moveTo(100,100); context.lineTo(35,100); //绘制描边 context.strokeStyle = "#ff0000"; context.stroke(); }
因为路径的使用很频繁,因此就有了一个名为isPointInPath()的方法。这个方法接收x和y坐标做为参数,用于在路径被关闭以前肯定画布上的某一点是否位于路径上,例如:
if(context.isPointInPath(100,100)){ console.log("Point(100,100) is in the path") }
绘制文本主要有两个方法:fillText()和strokeText()。这两个方法均可以接收4个参数:要绘制的文本字符串、x坐标、y坐标和可选的最大像素宽度。并且,这两个方法都如下列3个属性为基础。
这几个属性都有默认值,所以不必每次使用它们都从新设置一遍值。fillText()方法使用fillStyle属性绘制文本,而strokeText()方法使用strokeStyle属性为文本描边。相对来讲,仍是使用fillText()的时候更多,由于该方法模仿了在网页中正常显示文本。例以下面代码在前面建立的表盘上方绘制了数字12:
context.font = "bold 14px Arial"; context.textAlign = "center"; context.textBaseline = "middle"; context.fillText("12",100,20);
上面坐标(100,20)表示的是文本水平和垂直中点的坐标。
//正常 context.font = "bold 14px Arial"; context.textAlign = "center"; context.textBaseline = "middle"; context.fillText("12",100,20); //起点对齐 context.textAlign = "start"; context.fillText("12",100,40); //终点对齐 context.textAlign = "end"; context.fillText("12",100,60);
因为绘制文本比较复杂,特别是须要把文本控制在某一区域中的时候,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,100); context.fillText("Font size is" + fontSize + "px", 10,150);
经过上下文的变换,能够把处理后的图像绘制到画布上。2D绘制上下文支持各类基本的绘制变换。建立绘制上下文时,会以默认值初始化变换矩阵,在默认的变换矩阵下,全部处理都按描述直接绘制。为绘制上下文应用变换,会致使不一样的变换矩阵应用处理,从而产生不一样的结果。
能够经过以下方法来修改变换矩阵。
m1_1 m1_2 dx
m2_1 m2_2 dy
0 0 1
变换有可能很简单,也有可能很复杂,这都要视状况而定。好比,就那前面的绘制表针来讲,若是把原点变换到变盘的中心,而后在绘制表针就容易多了,以下例子:
var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); //开始路径 context.beginPath(); //绘制外圆 context.arc(100,100,99,0,2*Math.PI,false); //绘制内圆 context.moveTo(194,100); context.arc(100,100,94,0,2*Math.PI,false); //变换原点 context.translate(100,100); //绘制分针 context.moveTo(0,0); context.lineTo(0,-85); //绘制时针 context.moveTo(0,0); context.lineTo(-65,0); //绘制描边 context.strokeStyle = "#ff0000"; context.stroke(); }
把原点变换到时钟表盘的中心点(100,100)后,在同一方向上绘制线条就变成了简单的数学问题了。全部数学计算都基于(0,0),而不是(100,100)。还能够更进一步,像下面这样使用rotate()方法旋转时针的表针。
var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); //开始路径 context.beginPath(); //绘制外圆 context.arc(100,100,99,0,2*Math.PI,false); //绘制内圆 context.moveTo(194,100); context.arc(100,100,94,0,2*Math.PI,false); //变换原点 context.translate(100,100); //旋转表针 context.rotate(1); //绘制分针 context.moveTo(0,0); context.lineTo(0,-85); //绘制时针 context.moveTo(0,0); context.lineTo(-65,0); //绘制描边 context.strokeStyle = "#ff0000"; context.stroke(); }
虽然没有什么办法把上下文中的一切都重置回默认值,但有两个方法能够跟踪上下文的状态变化。若是你知道未来还要返回某组属性与变换的组合,能够调用save()方法。调用这个方法后,当时的全部设置都会进入一个栈结构,得以妥善保管。而后能够对上下文进行其它修改。等想要回到以前保存的设置时,能够调用restore()方法,在保存设置的栈结构中向前返回一级,恢复以前的状态。连续调用save()能够把更多设置保存到栈结构中,以后在连续调用restore()则能够一级一级返回,以下例子:
var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); 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)开始绘制红色矩形 }
须要注意的是,save()方法保存的只是对绘图上下文的设置和变换。不会保存绘图上下文的内容。
使用drawImage()方法能够把图像绘制到画布上。调用这个方法时,可使用三种不一样的参数组合。最简单的调用方式是传入一个HTML<img>元素,以及绘制该图像的起点的x和y坐标,以下:
window.onload = function(){ var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); var img = document.images[0]; context.fillStyle = "#000000"; context.fillRect(0,0,200,200); context.drawImage(img,10,10); } }
若是你想改变绘制后的图像大小,能够多传两个参数,分别表示目标宽度和目标高度。以下:
window.onload = function(){ var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); var img = document.images[0]; context.fillStyle = "#000000"; context.fillRect(0,0,200,200); context.drawImage(img,10,10,100,200); } }
执行后图像的大小变成了100*200。
除了上述两种方式,还能够选择把图像中的某个区域绘制到上下文中。drawImage()方法的这种调用方式总共须要传入9个参数:要绘制的图像、源图像的x坐标、源图像的y坐标、源图像的宽度、源图像的高度、目标图像的x坐标、目标图像的y坐标、目标图像的宽度、目标图像的高度。以下:
context.drawImage(img,0,10,50,50,0,100,40,60);
这行代码只把原始图像的一部分绘制到画布上。原始图像的这一部分起点为(0,10),宽和高都是50像素。最终绘制到上下文中的图像的起点为(0,100),而大小变成了40*60像素。
2D上下文会根据如下几个属性的值,自动为形状或路径绘制出阴影。
这些属性均可以经过context对象来修改。
var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); //设置阴影 context.shadowOffsetX = 5; context.shadowOffsetY = 5; context.shadowBlur = 4; context.shadowColor = "rgba(0,0,0,0.5)"; //绘制红色矩形 context.fillStyle = "#ff0000"; context.fillRect(10,10,50,50); //绘制蓝色矩形 context.fillStyle ="rgba(0,0,255,1)"; context.fillRect(30,30,50,50); }
渐变由CanvasGradient实例表示,很容易经过2D上下文来建立和修改。要建立一个新的线性渐变,能够调用createLinearGradient()方法。这个方法接收4个参数:起点的x坐标、起点的y坐标、终点的x坐标、终点的y坐标。调用这个方法后,它就会建立一个指定大小的渐变,并返回CanvasGradient对象的实例。
建立了渐变对象后,下一步就是使用addColorStop()方法来指定色标。这个方法接收2个参数:色标位置和CSS颜色值。色标位置是一个0(开始的颜色)到1(结束的颜色)之间的数字。以下:
var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); var gradient = context.createLinearGradient(30,30,70,70); gradient.addColorStop(0,"white"); gradient.addColorStop(1,"black"); //绘制红色矩形 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()中使用相同的值,以下:
function createRectLinearGradient(context,x,y,width,height){ return context.createLinearGradient(x,y,x+width,y+height); } var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); var gradient = createRectLinearGradient(context,30,30,50,50); gradient.addColorStop(0,"white"); gradient.addColorStop(1,"black"); //绘制红色矩形 context.fillStyle = "#ff0000"; context.fillRect(10,10,50,50); //绘制渐变矩形 context.fillStyle =gradient; context.fillRect(30,30,50,50); }
要建立径向渐变(或放射渐变),可使用createRadialGradient()方法。这个方法接收6个参数,对应这两个圆的圆心和半径。前三个参数指定的是起点圆的原心(x和y)以及半径,后三个参数指定的是终点圆的原心(x和y)以及半径。
若是想从某个形状的中心点开始建立一个向外扩散的径向渐变效果,就要将两个圆定义为同心圆。好比,拿前面建立的矩形来讲,径向渐变的两个圆的圆心都应该在(55,55),由于矩形的区域是从(30,30)到(80,80),看下面代码:
var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); var gradient = context.createRadialGradient(55,55,10,55,55,30); gradient.addColorStop(0,"white"); gradient.addColorStop(1,"black"); //绘制红色矩形 context.fillStyle = "#ff0000"; context.fillRect(10,10,50,50); //绘制渐变矩形 context.fillStyle =gradient; context.fillRect(30,30,50,50); }
效果以下所示:
模式其实就是重复的图像,能够用来填充或描边图形。要建立一新模式,能够调用createPattern()方法并传入两个参数:一个THML<img>元素和一个 表示如何重复图像的字符串。其中,第二个参数的值与CSS的background-repeat的属性值相同,包括“repeat”、“repeat-x”、“repeat-y”和“no-repeat”。以下例子:
window.onload = function(){ var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); var image = document.images[0], pattern = context.createPattern(image,"repeat"); //绘制矩形 context.fillStyle =pattern; context.fillRect(10,10,150,150); } }
效果如图:
createPattern()方法的第一个参数也能够是一个<video>元素,或者另外一个<canvas>元素。
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)。可以直接访问到原始图像数据,就可以以各类方式来操做这些数据。例如,经过修改图像数据,能够像下面这样建立一个简单的灰阶过滤器。
window.onload = function(){ var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"), image = document.images[0], imageData,data, i,len,average, red,green,blue,alpha; //绘制原始图像 context.drawImage(image,0,0); //取得图像数据 imageData = context.getImageData(0,0,image.width,image.height); data = imageData.data; for( i = 0, len = data.length; i < len; i+=4){ red = data[i]; green = data[i+1]; blue = data[i+2]; alpha = data[i+3]; //求得rgb平均值 average = Math.floor((red + green + blue) / 3); //设置颜色值,透明度不变 data[i] = average; data[i+1] = average; data[i+2] = average; } //回写图像数据并显示结果 imageData.data = data; context.putImageData(imageData,0,0); } }
如上代码,在把data数组回写到imageData对象后,调用putImageData()方法把图像数据绘制到画布上。最终获得了图像的黑白版。
效果以下:
还有两个会应用到2D上下文中全部绘制操做的属性:globalAlpha和globalCompositionOperation。其中globalAlpha是一个介于0到1之间的值(包括0和1),用于指定全部绘制的透明度。默认值为0。若是全部后续操做都要基于相同的透明度,就能够先把globalAlpha设置为适当的值,而后绘制,最后再把它设置为默认值0。以下例子:
var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); //绘制红色矩形 context.fillStyle = "#ff0000"; context.fillRect(10,10,50,50); //修改全局透明度 context.globalAlpha = 0.5; //绘制蓝色矩形 context.fillStyle = "rgba(0,0,255,1)"; context.fillRect(30,30,50,50); //重置全局透明度 context.globalAlpha = 0; }
在上面例子中,咱们把蓝色矩形绘制到了红色矩形上面,由于在绘制蓝色矩形前,globalAlpha设置为了0.5,因此蓝色矩形会呈现半透明效果,透过它能够看到下面的红色矩形。
第二个属性globalCompositionOperation表示后绘制的图形怎样与先绘制的图形结合。这个属性的值是字符串,可能的值以下:
var drawing = document.getElementById('drawing'); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var context = drawing.getContext("2d"); //绘制红色矩形 context.fillStyle = "#ff0000"; context.fillRect(10,10,50,50); //设置合成操做 context.globalCompositeOperation = "xor"; //绘制蓝色矩形 context.fillStyle = "rgba(0,0,255,1)"; context.fillRect(30,30,50,50); }
webGL是针对Canvas的3D上下文。与其它web技术不一样,webGL并非W3C指定的标准。而是由Khronos Group制定的。Khronos Group也设计了其余图像处理API,好比openGL ES2.0。浏览器使用的webGL就是基于openGL ES2.0制定的。
webGL涉及的复杂计算须要提早知道数值的精度,而标准的JavaScript数值没法知足须要。为此,webGL引入了一个概念,叫类型化数组(typed arrays)。类型化数组也是数组,只不过其元素被设置为特定类型的值。
类型化数组的核心就是一个名为ArrayBuffer的类型。每一个ArrayBuffer对象表示的只是内存中指定的字节数,但不会指定这些字节用于保存什么类型的数据。经过ArrayBuffer所能作的,就是为了未来使用而分配必定数量的字节。例如,下面这行代码会在内存中分配20B。
var buffer = new ArrayBuffer(20);
建立了ArrayBuffer对象后,可以经过该对象得到的信息只有它包含的字节数,方法是访问其byteLength属性:
var bytes = buffer.byteLength;
虽然ArrayBuffer对象自己没有什么可说的,但对webGL而言,使用它是极其重要的。并且,在涉及视图的时候,你才会发现它原来仍是颇有意思的。
使用ArrayBuffer(数组缓冲器类型) 的一种特别的方式就是用它来建立数组缓冲器视图。其中,最多见的视图是DataView,经过它能够选择ArrayBuffer的一小段字节。为此,能够在建立DataView实例的时候传入一个ArrayBuffer、一个可选的字节偏移量(从该字节开始选择)和一个可选的要选择的字节数。例如:
var buffer = new ArrayBuffer(20); //基于整个缓冲器建立一个新视图 var view = new DataView(buffer); //建立一个开始于字节9的新视图 var view = new DataView(buffer,9); //建立一个从字节9开始到字节18的新视图 var view = new DataView(buffer,9,10);
实例化后,DataView对象会把字节偏移量以及字节长度信息分别保存在byteOffset和byteLength属性中。
console.log(view.byteOffset); //9 console.log(view.byteLength); //10
读取和写入DataView的时候,要根据实际操做的数据类型,选择相应的getter和setter方法。下表列出了DataView支持的数据类型以及相应的读写方法。
数据类型 | getter | setter |
有符号8位整数 | getInt8(byteOffset) | setInt8(byteOffset,value) |
无符号8位整数 | getUint8(byteOffset) | setUint8(byteOffset,value) |
有符号16位整数 | getInt16(byteOffset,littleEndian) | setInt16(byteOffset,value,littleEndian) |
无符号16位整数 | getUint16(byteOffset,littleEndian) | setUint16(byteOffset,value,littleEndian) |
有符号32位整数 | getInt32(byteOffset,littleEndian) | setInt32(byteOffset,value,littleEndian) |
无符号32位整数 | getUint32(byteOffset,littleEndian) | setUint32(byteOffset,value,littleEndian) |
32位浮点数 | getFloat32(byteOffset,littleEndian) | setFloat32(byteOffset,value,littleEndian) |
64位浮点数 | getFloat64(byteOffset,littleEndian) | setFloat64(byteOffset,value,littleEndian) |
littleEndian:可选。若是为 false 或未定义,则应写入 big-endian 值;不然应写入 little-endian 值。详细了解《MSDN:DataView对象》
全部这些方法的第一个参数都是一个字节偏移量,表示要从哪一个字节开始读取或者写入。不要忘了,要保存有些数据类型的数据,可能须要不止1B。好比,无符号8位整数要用1B,而32为浮点数则要用4B。使用DataView,就须要你本身来管理这些细节,即要明确本身的数据须要多少字节,并选择正确的读写方法。例如:
var buffer = new ArrayBuffer(20), view = new DataView(buffer), value; view.setUint16(0,25); view.setUint16(2,50); //不能从字节1开始,由于16位整数要用2B value = view.getUint16(0);
以上代码把两个无符号16位整数保存到了数组缓冲器中。由于每一个16位整数要用2B,因此保存第一个数的字节偏移量为0,而保存第二个数的字节偏离量为2。
能够经过几种不一样的方式来访问同一字节。例如:
var buffer = new ArrayBuffer(20), view = new DataView(buffer), value; view.setUint16(0,25); value = view.getInt8(0); console.log(value); //0
在这个例子中,数值25以16位无符号整数的形式被写入,字节偏移量为0。而后,再以8位有符号整数的方式读取该数据,获得的结果为0。这是由于25的二进制形式的前8位(第一个字节)全是0,如图所示:
可见,虽然DataView能让咱们在字节级别上读写数组缓冲器中的数据,但咱们必须本身记住要将数据保存到哪里,须要占用多少字节。这样一来,就会带来不少工做量,所以,类型化视图也就应运而生。
类型化视图通常也被称为类型化数组,由于他们除了元素必须是某种特定的数据类型外,与常规的数组无异。类型化视图也分为几种,并且它们都继承了DataView。
每种视图类型都以不一样的方式表示数据,而同一数据视选择的类型不一样有可能占用一或多字节。例如,20B的ArrayBuffer能够保存20个Int8Array或Uint8Array,或者10个Int16Array或Uint16Array,或者5个Int32Array、Uint32Array或Float32Array,或者2个Float64Array。
因为这些视图都继承自DataView,于是可使用相同的构造函数参数来实例化。第一个参数是要使用ArrayBuffer对象,第二个参数是做为起点的字节偏移量(默认为0),第三个参数是要包含的字节数。三个参数只有第一个是必须的。以下例子:
var buffer = new ArrayBuffer(200); //建立一个新数组,使用整个缓冲器 var int8s = new Int8Array(buffer); //只使用从字节10开始的缓冲器 var int16s = new Int16Array(buffer,10); //只使用从字节10到字节19的的缓冲器 var uint16s = new Uint16Array(buffer,10,10);
可以指定缓冲器中可用的字节段,意味着能在同一个缓冲器中保存不一样类型的数值。好比,下面的代码就是在缓冲器的开头保存8位整数,而在其它字节中保存16位整数。
var buffer = new ArrayBuffer(32); //使用缓冲器的一部分保存8位整数另外一部分保存在16位整数 var int8s = new Int8Array(buffer,0,11); var uint16s = new Uint16Array(buffer,12,10);
每一个视图构造函数都有一个名为BYTES_PER_ELEMENT的属性,表示类型化数组的每一个元素须要多少字节。所以,Uint8Array.BYTES_PER_ELEMENT就是1,而Float32Array.BYTES_PER_ELEMENT则为4。能够利用这个属性来辅助初始化。
var buffer = new ArrayBuffer(32); //须要11个元素空间 var int8s = new Int8Array(buffer,0,11*Int8Array.BYTES_PER_ELEMENT); //须要5个元素空间 var uint16s = new Uint16Array(buffer,int8s.byteOffset + int8s.byteLength + 1,5*Uint16Array.BYTES_PER_ELEMENT);
类型化视图的目的在于简化对二进制数据的操做。除了前面看到的优势以外,建立类型化视图还能够不用首先建立ArrayBuffer对象。只要传入但愿数组保存的元素数,相应的构造函数就能够自动建立一个包含足够字节数的ArrayBuffer对象,例如:
//建立一个数组保存10个8位整数(10字节) var int8s = new Int8Array(10); //建立一个数组保存10个16位整数(20字节) var int16s = new Int16Array(10);
另外,也能够把常规数组转换为类型化视图,只要把常规数组传入到类型化视图的构造函数便可:
//建立一个数组保存5个8位整数(10字节) var int8s = new Int8Array([10,20,30,40,50]);
这是用默认值来初始化类型化视图的最佳方式,也是webGL项目中最经常使用的方式。
使用类型化视图时,能够经过length属性肯定数组中有多少元素,以下:
//建立一个数组保存5个8位整数(10字节) var int8s = new Int8Array([10,20,30,40,50]); for(var i = 0,len = int8s.length; i < len; i++){ console.log("value at position" + i + " is " + int8s[i]); }
固然也可使用方括号语法为类型化视图的元素赋值。若是为相应的元素指定字节数放不下相应的值,则实际保存的值是最大可能值的模。例如,无符号16位整数所能表示的最大数值是65535,若是你想保存65536,那实际保存的值是0;若是你想保存65537,那实际保存的值是1,依次类推。
var uint16s = new Uint16Array(10); uint16s[0] = 65537; console.log(uint16s[0]); //1
类型化视图还有一个方法subarray(),使用这个方法能够基于底层数组缓冲器的子集建立一个新视图。这个方法接收两个参数:开始元素的索引和可选的结束元素的索引。返回的类型与与调用该方法的视图类型相同。例如:
var uint16s = new Uint16Array(10), sub = uint16s.subarray(2,5); console.log(sub); //[0, 0, 0]
在以上代码中,sub也是Uint16Array的一个实例,并且底层与uint16s都基于同一个ArrayBuffer。
目前,在支持的浏览器中,webGL的名字叫“experimental-webgl”,这是由于webGL的规范仍然未制定完成。制定完成后,这个名字就会变成简单的“webgl”。若是浏览器不支持webGL,取得上下文时会返回null。在使用webGL时,务必先检测一下返回值:
var drawing = document.getElementById("drawing"); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var gl = drawing.getContext("experimental-webgl"); if(gl){ //使用webGL } }
经过给getContext()传递第二个参数,能够为webGL上下文设置一些选项。这个参数自己是一个对象,能够包含下列属性。
传递这个选项对象的方式以下:
var drawing = document.getElementById("drawing"); //肯定浏览器支持<canvas>元素 if(drawing.getContext){ var gl = drawing.getContext("experimental-webgl",{alpha:false}); if(gl){ //使用webGL } }
大多数上下文选项只在高级技巧中使用。不少时候,各个选项的默认值就能知足咱们的需求。
若是getContext()没法建立webGL上下文,最好把调用封装在try-catch块中,以下:
var drawing = document.getElementById("drawing"), gl; //肯定浏览器支持<canvas>元素 if(drawing.getContext){ try{ gl = drawing.getContext("experimental-webgl"); }catch(ex){ //什么也不作 } if(gl){ //使用webGL }else{ alert("webGL context could not be created"); } }
若是你属性openGL,那确定会对各类操做中使用很是多的常量印象深入。这些常量在openGL中都带前缀GL_。在webGL中,保存在上下文对象中的这些常量都没有GL_前缀。好比,GL_COLOR_BUFFER_BIT常量在webGL上下文就是gl.COLOR_BUFFER_BIT。webGL以这种方式支持大多数openGL常量(有一部分常量是不支持的)。
openGL(以及webGL)中的不少方法都视图经过名字传达有关数据类型的信息。若是某种方法能够接收不一样类型以及不一样数量的参数,看方法名的后缀就能够知道。方法名的后缀会包含参数个数(1到4)和接收的数据类型(f表示浮点数,i表示整数)。例如,gl.uniform4f()意味着要接收4个浮点数,而gl.uniform3i()则表示要接收3个整数。
也有不少方法接收数组参数而非一个个单独的参数。这样的方法其名字中会包含字母v(即vector,矢量)。所以,gl.uniform3iv()能够接收一个包含3个值的整数数组。请你们记住以上命名约定,这样对理解后面关于webGL的讨论颇有帮助。
在实际操做webGL上下文以前,通常都要使用某种实色清除<canvas>,为绘图作好准备。为此,首先必须使用clearColor()方法来指定要使用的颜色值,该方法接收4个参数:红、绿、蓝和透明度。每一个参数必须是一个0到1之间的数值,表示每种份量在最终颜色中的强度,以下代码:
gl.clearColor(0,0,0,1); //black gl.clear(gl_COLOR_BUFFER_BIT);
以上代码把清理颜色缓冲区的值设置为黑色,而后调用clear()方法,这个方法与openGL中的glClear()等价。传入的参数gl_COLOR_BUFFER_BIT告诉webGL使用以前定义的颜色来填充相应区域。通常来讲,都要先清理缓冲区,而后再执行其余绘图操做。
开始绘图以前,一般要先定义webGL的视口(viewport)。默认状况下视图可使用整个<canvas>区域。要改变视口大小,能够调用viewport()方法并传入4个参数:(视口相对于<canvas>元素的)x坐标、y坐标、宽度和高度。例如,下面的调用就使用了<canvas>元素:
gl.viewport(0,0,drawing.width,drawing.height);
视口坐标与咱们熟悉的网页坐标不同。视口坐标的原点(0,0)在<canvas>元素的左下角,x轴和y轴的正方向分别是向右和向上,能够定义为(width-1,height-1),以下图所示:
知道怎么定义视口大小,就能够只在<canvas>元素的部分区域中绘图。以下例子:
//视口是<canvas>左下角的四分之一区域 gl.viewport(0,0,drawing.width/2,drawing.height/2); //视口是<canvas>左上角的四分之一区域 gl.viewport(0,drawing.height/2,drawing.width/2,drawing.height/2); //视口是<canvas>右下角的四分之一区域 gl.viewport(drawing.width/2,0,drawing.width/2,drawing.height/2);
另外,视图内部的坐标系与定义视口的坐标系也不同。在视口内部,坐标原点(0,0)是视口的中心点,所以,视口左下角坐标为(-1,-1),而右上角坐标为(1,1)。以下图所示:
若是在视口内部绘图时使用视口外部的坐标,结果可能被视口剪切。好比,要绘制的形状有一个顶点在(1,2),那么该形状在视口右侧的部分会被剪切掉。
建立缓冲区:gl.createBuffer(),使用gl.bindBuffer()绑定到webGL上下文,gl.deleteBuffer()释放内存。
webGL操做通常不会抛出错误,手工调用gl.getError()方法。
webGL有两种着色器:顶点着色器和片断(或像素)着色器。