<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>
要在画布上绘制图形,首先要取得绘图上下文。使用 getContext()方法可以获取对绘图上下文的
引用。对于平面图形,需要给这个方法传入参数"2d",表示要获取 2D 上下文对象,当然
使用<canvas>元素时,最好先测试一下 getContext()方法是否存在
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let context = drawing.getContext("2d");
// 其他代码
}
toDataURL()
:导出<canvas>元素上的图像.这个方法接收一个参数:要生成图像
的 MIME 类型(与用来创建图形的上下文无关)
{
// 取得图像的数据 URI
let imgURI = drawing.toDataURL("image/png");
// 显示图片
let image = document.createElement("img");
image.src = imgURI;
document.body.appendChild(image);
}
填充和描边
fillStyle()
:填充
strokeStyle()
:描边
context.fillStyle = '#0000ff';
context.fillRect(10, 10, 50, 50);
context.strokeStyle = '#ff0000';
context.strokeRect(10, 10, 50, 50);
// fillStyle || strokeStyle 需要结合Rect函数才能展现出效果,当然你也可以直接调用stroke();
这里把 strokeStyle 设置为"red"(CSS 颜色名称),把 fillStyle 设置为"#0000ff"(蓝色)。
绘制矩形
绘制矩形相关的方法有 3 个:
fillRect()
、strokeRect()
和 clearRect()
。这些方法都接收 4 个参数:矩形 x 坐标
、矩形 y 坐标
、
矩形宽度
和矩形高度
。这几个参数的单位都是像素
。
{
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 绘制半透明蓝色矩形
context.fillStyle = "rgba(0,0,255,0.5)";
context.fillRect(30, 30, 50, 50);
}
{
//绘制矩形轮廓
// 绘制红色轮廓的矩形
context.strokeStyle = "#ff0000";
context.strokeRect(10, 10, 50, 50);
// 绘制半透明蓝色轮廓的矩形
context.strokeStyle = "rgba(0,0,255,0.5)";
context.strokeRect(30, 30, 50, 50);
}
注意 描边宽度由
lineWidth
属性控制,它可以是任意整数值。类似地,lineCap
属性控制线条端点的形状["butt"(平头)、"round"(出圆头)或"square"(出方头)],而lineJoin
属性控制线条交点的形状["round"(圆转)、"bevel"(取平)或"miter"(出尖)]。
clearRect()
:方法可以擦除画布中某个区域
{
// 绘制红色矩形
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);
}
绘制路径
要想绘制更加复杂推行,必须首先调用 beginPath()
方法以表示要开始绘制新路径。然后,再调用下列方法来绘制路径。
- arc(x, y, radius, startAngle, endAngle, counterclockwise):以坐标(x, y)为圆心,以 radius 为半径绘制一条弧线,起始角度为 startAngle,结束角度为 endAngle(都是弧度)。最后一个参数 counterclockwise 表示是否逆时针计算起始角度和结束角度(默认为顺时针)。
// 绘制外圆
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
- arcTo(x1, y1, x2, y2, radius):以给定半径 radius,经由(x1, y1)绘制一条从上一点
到(x2, y2)的弧线。
ctx.beginPath();
ctx.moveTo(20,20); //创建一个起点
ctx.lineTo(100,20); // 创建一个水平线
ctx.arcTo(150,20,150,70,50); // 创建一个弧
ctx.lineTo(150,120); // 继续垂直线
ctx.stroke(); //绘画
- bezierCurveTo(c1x, c1y, c2x, c2y, x, y):以(c1x, c1y)和(c2x, c2y)为控制点,
绘制一条从上一点到(x, y)的弧线(三次贝塞尔曲线)。
ctx.moveTo(20,20);
ctx.bezierCurveTo(20,100,200,100,200,20);
- lineTo(x, y):绘制一条从上一点到(x, y)的直线。
- moveTo(x, y):不绘制线条,只把绘制光标移动到(x, y)。
- quadraticCurveTo(cx, cy, x, y):以(cx, cy)为控制点,绘制一条从上一点到(x, y)
的弧线(二次贝塞尔曲线)。
ctx.moveTo(20,20);
ctx.quadraticCurveTo(20,100,200,20);
- rect(x, y, width, height):以给定宽度和高度在坐标点(x, y)绘制一个矩形。这个方法
与 strokeRect()和 fillRect()的区别在于,它创建的是一条路径,而不是独立的图形。
ctx.rect(20,20,150,100);
- closePath()法绘制一条返回起点的线
ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(20,100);
ctx.lineTo(70,100);
ctx.closePath();
ctx.stroke();
举例:
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.stroke();
当然我们还可以动态的去绘制图形isPointInPath()
ctx.rect(20,20,150,100);
if (ctx.isPointInPath(20,50))
{
ctx.stroke();
};
绘制文本
fillText()
strokeText()
这两个方法都接收 4 个参数:
要绘制的字符串
、x 坐标
、y 坐标
和可选的最大像素宽度
,这两个方法最终绘制的结果都取决于font
、textAlign
、textBaseLine
context.font = "bold 14px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("12", 100, 20);
textAlign = "center"
、textAlign = "start"
、textAlign = "end"
三者比较
textBaseLine 方法同理
measureText()方法
:文本绘制到特定区域,用于辅助确定文本大小。使用 font、textAlign 和 textBaseline 属性当前的值计算绘制指定文本后的大小,目前只有一个属性 width
,举例说明:
let 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);
补充:strokeText()方法也是绘制文本,只不过是没有填充色,是空心的。
变换
于改变绘制上下文的变换矩阵有一下居中方法:
-
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()。
举例说明:
在前面绘制表盘的例子中,把坐标原点移动到表盘中心。
// 移动原点到表盘中心
context.translate(100, 100);
// 旋转表针
context.rotate(1);
// 绘制分针
context.moveTo(0, 0);
context.lineTo(0, -85);
// 绘制时针
context.moveTo(0, 0);
context.lineTo(-65, 0);
context.stroke();
另外save()
和restore()
方法会对上下文进行保存和恢复,restore()
会根据所调用save()
进行逐步恢复。注意,save()
方法只保存应用到绘图上下文的设置和变换,不保存绘图上下文的内容。
绘制图像
drawImage()
:把现有图像绘制到画布上,接收 9个的参数。分别为:HTML 的img元素(还可以是另一个<canvas>元素)、绘制目标的 x 和 y 坐标以及绘制的宽和高(单位:像素)、目标区域 x 坐标、目标区域 y 坐标、目标区域宽度和目标区域高度
let image = document.images[0];
//这一部分从(0, 10)开始,50 像素宽、50 像素高。而绘制到画布上时,会从(0, 100)开始,变成 40 像素宽、60 像素高。
context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60);
阴影
-
shadowColor
:CSS 颜色值,表示要绘制的阴影颜色,默认为黑色。 -
shadowOffsetX
:阴影相对于形状或路径的 x 坐标的偏移量,默认为 0。 -
shadowOffsetY
:阴影相对于形状或路径的 y 坐标的偏移量,默认为 0。 -
shadowBlur
:像素,表示阴影的模糊量。默认值为 0,表示不模糊。
//在绘制的图形加入一下代码
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);
渐变
createLinearGradient()
:四个参数 起点 x 坐标、起点 y 坐标、终点 x 坐标和终点 y 坐标
addColorStop()
:两个参数色标位置和 CSS 颜色字符串。色标位置通过 0~1 范围内的值表示,0 是第一种颜色,1 是最后一种颜色
举例:
//这个 gradient 对象现在表示的就是在画布上从(30, 30)到(70, 70)绘制一个渐变。渐变的起点颜色为白色,终点颜色为黑色
let gradient = context.createLinearGradient(30, 30, 70, 70);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
// 绘制渐变矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);
注意:填充的矩形起始坐标必须等于gradient对象才可以达到完美的渐变效果;
createRadialGradient()
:径向渐变(或放射性渐变),方法接收 6 个参
数,分别对应两个圆形圆心的坐标和半径。前 3 个参数指定起点圆形中心的 x、y 坐标和半径,后 3 个参数指定终点圆形中心的 x、y 坐标和半径
let 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()
:第一个参数HTML中的img元素、第二个参数的值与 CSS 的
background-repeat 属性是一样的,包括"repeat"、"repeat-x"、"repeat-y"和"no-repeat"。
let image = document.images[0],
pattern = context.createPattern(image, "repeat");
// 绘制矩形
context.fillStyle = pattern;
context.fillRect(10, 10, 150, 150);
注意:跟渐变一样,图案的起点实际上是画布的原点(0, 0)
图像数据
getImageData()
:获取原始图像数据,4个参数:要取得数据中第一个像素的左上角坐标和要取得的像素宽度及高度
举例:要从(10, 5)开始取得 50 像素宽、50 像素高的区域对应的数据
let imageData = context.getImageData(10,5,50,50);
打印:imageData
其中的data又包含四个信息的数组:[r,g,b,a]
let data = imageData.data,
red = data[0],
green = data[1],
blue = data[2],
alpha = data[3];
合成
globalAlpha
属性是一个范围在 0~1 的值(包括 0 和 1),用于指定所有绘制内容的透明度,默认值是0
举例
// 绘制红色矩形
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;
globalCompositionOperation
属性表示新绘制的形状如何与上下文中已有的形状融合
source-over
:默认值,新图形绘制在原有图形上面。
source-in
:新图形只绘制出与原有图形重叠的部分,画布上其余部分全部透明。
source-out
:新图形只绘制出不与原有图形重叠的部分,画布上其余部分全部透明。
source-atop
:新图形只绘制出与原有图形重叠的部分,原有图形不受影响。
destination-over
:新图形绘制在原有图形下面,重叠部分只有原图形透明像素下的部分可见。
destination-in
:新图形绘制在原有图形下面,画布上只剩下二者重叠的部分,其余部分完全透明。
destination-out
:新图形与原有图形重叠的部分完全透明,原图形其余部分不受影响。
destination-atop
:新图形绘制在原有图形下面,原有图形与新图形不重叠的部分完全透明。
lighter
:新图形与原有图形重叠部分的像素值相加,使该部分变亮。
copy
:新图形将擦除并完全取代原有图形。
xor
:新图形与原有图形重叠部分的像素执行“异或”计算。
顾名思义,自己打开编辑器试一试吧,比较有意思的是lighter
// 绘制红色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 设置合成方式
context.globalCompositeOperation = "destination-over";
// 绘制蓝色矩形
context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(30, 30, 50, 50);
WebGL基础
WebGL 是画布的 3D 上下文
WebGL 上下文
应用前提,为了确保在创建webgl上下文时某些浏览器就会抛出错误,应该加入try-catch。这也是最完美的做法
let drawing = document.getElementById("drawing"),
gl;
// 确保浏览器支持<canvas>
if (drawing.getContext) {
try {
gl = drawing.getContext("webgl");
} catch (ex) {
// 什么也不做
}
if (gl) {
// 使用 WebGL
} else {
alert("WebGL context could not be created.");
}
}
其中getContext()
中可添加的乱七八糟的属性它来了
- alpha:布尔值,表示是否为上下文创建透明通道缓冲区,默认为 true。
- depth:布尔值,表示是否使用 16 位深缓冲区,默认为 true。
- stencil:布尔值,表示是否使用 8 位模板缓冲区,默认为 false。
- antialias:布尔值,表示是否使用默认机制执行抗锯齿操作,默认为 true。
- premultipliedAlpha:布尔值,表示绘图缓冲区是否预乘透明度值,默认为 true。
- preserveDrawingBuffer:布尔值,表示绘图完成后是否保留绘图缓冲区,默认为 false。
人家说了这种属性一定要充分了解之后再修改,很浪费性能的哦!
-
准备绘图
通常,所有绘图操作之前都需要先清除绘制区域。
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
-
视口与坐标
viewport()
该方法四个参数分别为相对于<canvas>元素的 x、y 坐标及宽度和高度
gl.viewport(0, 0, drawing.width,drawing.height);
// 视口是<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);
注意:改视口的坐标与页面中的坐标不一样
-
缓冲区
顶点信息需要保存在定型数组中。要使用这些信息,必须先把它们转换为 WebGL 缓冲区。创建缓冲区要调用gl.createBuffer()
方法,并使用gl.bindBuffer()
方法将缓冲区绑定到WebGL 上下文。
let buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0.5, 1]), gl.STATIC_DRAW);
调用 gl.bindBuffer()将 buffer 设置为上下文的当前缓冲区。然后,所有缓冲区操作都在buffer 上直接执行。因此,调用
gl.bufferData()
虽然没有包含对 buffer 的直接引用,但仍然是在它上面执行的。上面最后一行代码使用一个 Float32Array(通常把所有顶点信息保存在Float32Array 中)初始化了 buffer.如果想输出缓冲区内容,那么可以调用 drawElements()方法并传入 gl.ELEMENT_ARRAY_BUFFER。
补充:
gl.bufferData()方法的最后一个参数
- gl.STATIC_DRAW:数据加载一次,可以在多次绘制中使用。
- gl.STREAM_DRAW:数据加载一次,只能在几次绘制中使用。
- gl.DYNAMIC_DRAW:数据可以重复修改,在多次绘制中使用。
如果不再需要缓冲区,那么最好调用 gl.deleteBuffer()方法释放其占用的内存:
gl.deleteBuffer(buffer);
-
错误
gl.getError()
:这个方法返回一个常量,表示发生的错误类型
- gl.NO_ERROR:上一次操作没有发生错误(0 值)。
- gl.INVALID_ENUM:上一次操作没有传入 WebGL 预定义的常量。
- gl.INVALID_VALUE:上一次操作需要无符号数值,但是传入了负数。
- gl.INVALID_OPERATION:上一次操作在当前状态下无法完成。
- gl.OUT_OF_MEMORY:上一次操作因内存不足而无法完成。
- gl.CONTEXT_LOST_WEBGL:上一次操作因外部事件(如设备掉电)而丢失了 WebGL 上下文。
let errorCode = gl.getError();
while (errorCode) {
console.log("Error occurred: " + errorCode);
errorCode = gl.getError();
}
-
着色器
WebGL 中有两种着色器:顶点着色器和片段(或像素)着色器,他是用C 或JavaScript 完全不同的语言 GLSL(OpenGL Shading Language)写的,So
- 编写着色器 可以略过... ...但是需要记住的是 每个着色器都有一个
main()
方法给着色器传递数据的方式有两种:attribute
和uniform
,并且他们都是在main方法外边定义的
-
创建着色器程序
为了读取script标签内部的代码可以设置浏览器不能识别的type
<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>
<script>
//读取代码
let vertexGlsl = document.getElementById("vertexShader").text,
fragmentGlsl = document.getElementById("fragmentShader").text;
</script>
createShader():用于用于创建一个 WebGLShader
着色器对象,参数为gl.VERTEX_SHADER
或 gl.FRAGMENT_SHADER
两者中的一个。
调用gl.shaderSource()
方法把 GLSL 代码应用到着色器,再调用 gl.compileShader()
编译着色器
<script>
//向上连接
let vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexGlsl);
gl.compileShader(vertexShader);
let fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentGlsl);
gl.compileShader(fragmentShader);
</script>
再接着会调用createProgram()方法用于创建和初始化一个 WebGLProgram
对象。
//向上连接
let program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
第一行代码创建了一个程序,然后 attachShader()
用于添加着色器。调用 gl.linkProgram()
将两个着色器链接到了变量 program 中
就可以通过 gl.useProgram()方法让WebGL 上下文使用这个程序了:
gl.useProgram(program);
-
给着色器传值
定义的着色器必须传入一个值,对于 uniform 变量,可以调用gl.getUniformLocation()
方法,对于uniform
变量,可以调用 gl.getUniformLocation()方法
let uColor = gl.getUniformLocation(program, "uColor");
gl.uniform4fv(uColor, [0, 0, 0, 1]);
这个例子从 program 中找到 uniform 变量 uColor,然后返回了它的内存位置。第二行代码调用
gl.uniform4fv()方法给 uColor 传入了值。
需要注意的是getUniformLocation()方法的参数,program为创建的着色器程序
同理给顶点着色器传值(对应attribute
变量的着色器),调用gl.getAttribLocation()方法
let aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");
gl.enableVertexAttribArray(aVertexPosition);
gl.vertexAttribPointer(aVertexPosition, itemSize, gl.FLOAT, false, 0, 0);
这里,首先取得 aVertexPosition 的内存位置,然后使用 gl.enableVertexAttribArray()
来启用。最后一行代码创建了一个指向调用 gl.bindBuffer()指定的缓冲区的指针,并把它保存在aVertexPosition 中,从而可以在后面由顶点着色器使用。
-
绘图
WebGL只能绘制三种形状:点、线和三角形
WebGL 绘图要使用drawArrays()
和drawElements()
方法,前者使用数组缓冲区,后者则操作元素数组缓冲区。
drawArrays()和 drawElements()的第一个参数都表示要绘制形状的常量。
-
gl.POINTS
:将每个顶点当成一个点来绘制。 -
gl.LINES
:将数组作为一系列顶点,在这些顶点间绘制直线。每个顶点既是起点也是终点,因此数组中的顶点必须是偶数个才能开始绘制。 -
gl.LINE_LOOP
:将数组作为一系列顶点,在这些顶点间绘制直线。从第一个顶点到第二个顶点绘制一条直线,再从第二个顶点到第三个顶点绘制一条直线,以此类推,直到绘制到最后一个顶点。此时再从最后一个顶点到第一个顶点绘制一条直线。这样就可以绘制出形状的轮廓。 -
gl.LINE_STRIP
:类似于 gl.LINE_LOOP,区别在于不会从最后一个顶点到第一个顶点绘制直线。 -
gl.TRIANGLES
:将数组作为一系列顶点,在这些顶点间绘制三角形。如不特殊指定,每个三角形都分开绘制,不共享顶点。 -
gl.TRIANGLES_STRIP
:类似于 gl.TRIANGLES,区别在于前 3 个顶点之后的顶点会作为第三个顶点与其前面的两个顶点构成三角形。例如,如果数组中包含顶点 A、B、C、D,那么第一个三角形使用 ABC,第二个三角形使用 BCD。 -
gl.TRIANGLES_FAN
:类似于 gl.TRIANGLES,区别在于前 3 个顶点之后的顶点会作为第三个顶点与其前面的顶点和第一个顶点构成三角形。例如,如果数组中包含顶点 A、B、C、D,那么第一个三角形使用 ABC,第二个三角形使用 ACD。
第二个参数是数组缓冲区的起点索引
,第三个参数是数组缓冲区包含的顶点集合的数量
举例子:
// 假设已经使用本节前面的着色器清除了视口
//将上面的代码需要补充
// 定义 3 个顶点的 x 坐标和 y 坐标
let vertices = new Float32Array([ 0, 1, 1, -1, -1, -1 ]),
buffer = gl.createBuffer(),
vertexSetSize = 2,
vertexSetCount = vertices.length/vertexSetSize,
uColor,
aVertexPosition;
// 将数据放入缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 给片段着色器传入颜色
uColor = gl.getUniformLocation(program, "uColor");
gl.uniform4fv(uColor, [ 0, 0, 0, 1 ]);
// 把顶点信息传给着色器
aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");
gl.enableVertexAttribArray(aVertexPosition);
gl.vertexAttribPointer(aVertexPosition, vertexSetSize, gl.FLOAT, false, 0, 0);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, vertexSetCount);
以上实例需要进一步去了解GLSL语法 官网,哈哈当前他会给你不一样的惊喜~
您也可以通过修改drawArrays的第一个参数去完成不同的三角形
-
纹理
gl.createTexture()
:方法创建新的纹理,使用的前提是必须等图片加载完毕load
let image = new Image(),
texture;
image.src = "smile.gif";
image.onload = function() {
texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
// 除当前纹理
gl.bindTexture(gl.TEXTURE_2D, null);
}
当看到这么多新的API我也是一脸懵逼,感叹gl的伟大~
bindTexture():将给定的 WebGLTexture
绑定到目标(绑定点)。
pixelStorei():)设置了像素存储格式
-
读取像素
readPixels()
:上下文中读取像素数据,的参数包括 x 和 y 坐标、宽度、高度、图像格式、类型和定型数组。
需要注意的是
- 如果这个类型是 gl.UNSIGNED_BYTE,则定型数组必须是 Uint8Array;
- 如果这个类型是 gl.UNSIGNED_SHORT_5_6_5、gl.UNSIGNED_SHORT_4_4_4_4 或 gl.UNSIGNED_SHORT_5_5_5_1,则定型数组必须是 Uint16Array。
let pixels = new Uint8Array(25*25);
gl.readPixels(0, 0, 25, 25, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
最后
附加一个实例
<!-- 画布区域 -->
<div id="bg">
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
</div>
<!-- 画布区域结束 -->
(function($) {
var canvas = $('#bg').children('canvas'),
background = canvas[0],
foreground1 = canvas[1],
foreground2 = canvas[2],
config = {
circle: {
amount: 18,
layer: 3,
color: [157, 97, 207],
alpha: 0.3
},
line: {
amount: 12,
layer: 3,
color: [255, 255, 255],
alpha: 0.3
},
speed: 0.5,
angle: 20
};
if (background.getContext) {
var bctx = background.getContext('2d'),
fctx1 = foreground1.getContext('2d'),
fctx2 = foreground2.getContext('2d'),
M = window.Math, // Cached Math
degree = config.angle / 360 * M.PI * 2,
circles = [],
lines = [],
wWidth, wHeight, timer;
requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
function(callback, element) { setTimeout(callback, 1000 / 60); };
cancelAnimationFrame = window.cancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.msCancelAnimationFrame ||
window.oCancelAnimationFrame ||
clearTimeout;
var setCanvasHeight = function() {
wWidth = $(window).width();
wHeight = $(window).height(),
canvas.each(function() {
this.width = wWidth;
this.height = wHeight;
});
};
var drawCircle = function(x, y, radius, color, alpha) {
var gradient = fctx1.createRadialGradient(x, y, radius, x, y, 0);
gradient.addColorStop(0, 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + alpha + ')');
gradient.addColorStop(1, 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + (alpha - 0.1) + ')');
fctx1.beginPath();
fctx1.arc(x, y, radius, 0, M.PI * 2, true);
fctx1.fillStyle = gradient;
fctx1.fill();
};
var drawLine = function(x, y, width, color, alpha) {
var endX = x + M.sin(degree) * width,
endY = y - M.cos(degree) * width,
gradient = fctx2.createLinearGradient(x, y, endX, endY);
gradient.addColorStop(0, 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + alpha + ')');
gradient.addColorStop(1, 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + (alpha - 0.1) + ')');
fctx2.beginPath();
fctx2.moveTo(x, y);
fctx2.lineTo(endX, endY);
fctx2.lineWidth = 3;
fctx2.lineCap = 'round';
fctx2.strokeStyle = gradient;
fctx2.stroke();
};
var drawBack = function() {
};
var animate = function() {
var sin = M.sin(degree),
cos = M.cos(degree);
if (config.circle.amount > 0 && config.circle.layer > 0) {
fctx1.clearRect(0, 0, wWidth, wHeight);
for (var i = 0, len = circles.length; i < len; i++) {
var item = circles[i],
x = item.x,
y = item.y,
radius = item.radius,
speed = item.speed;
if (x > wWidth + radius) {
x = -radius;
} else if (x < -radius) {
x = wWidth + radius
} else {
x += sin * speed;
}
if (y > wHeight + radius) {
y = -radius;
} else if (y < -radius) {
y = wHeight + radius;
} else {
y -= cos * speed;
}
item.x = x;
item.y = y;
drawCircle(x, y, radius, item.color, item.alpha);
}
}
if (config.line.amount > 0 && config.line.layer > 0) {
fctx2.clearRect(0, 0, wWidth, wHeight);
for (var j = 0, len = lines.length; j < len; j++) {
var item = lines[j],
x = item.x,
y = item.y,
width = item.width,
speed = item.speed;
if (x > wWidth + width * sin) {
x = -width * sin;
} else if (x < -width * sin) {
x = wWidth + width * sin;
} else {
x += sin * speed;
}
if (y > wHeight + width * cos) {
y = -width * cos;
} else if (y < -width * cos) {
y = wHeight + width * cos;
} else {
y -= cos * speed;
}
item.x = x;
item.y = y;
drawLine(x, y, width, item.color, item.alpha);
}
}
timer = requestAnimationFrame(animate);
};
var createItem = function() {
circles = [];
lines = [];
if (config.circle.amount > 0 && config.circle.layer > 0) {
for (var i = 0; i < config.circle.amount / config.circle.layer; i++) {
for (var j = 0; j < config.circle.layer; j++) {
circles.push({
x: M.random() * wWidth,
y: M.random() * wHeight,
radius: M.random() * (20 + j * 5) + (20 + j * 5),
color: config.circle.color,
alpha: M.random() * 0.2 + (config.circle.alpha - j * 0.1),
speed: config.speed * (1 + j * 0.5)
});
}
}
}
if (config.line.amount > 0 && config.line.layer > 0) {
for (var m = 0; m < config.line.amount / config.line.layer; m++) {
for (var n = 0; n < config.line.layer; n++) {
lines.push({
x: M.random() * wWidth,
y: M.random() * wHeight,
width: M.random() * (20 + n * 5) + (20 + n * 5),
color: config.line.color,
alpha: M.random() * 0.2 + (config.line.alpha - n * 0.1),
speed: config.speed * (1 + n * 0.5)
});
}
}
}
cancelAnimationFrame(timer);
timer = requestAnimationFrame(animate);
drawBack();
};
$(document).ready(function() {
setCanvasHeight();
createItem();
});
$(window).resize(function() {
setCanvasHeight();
createItem();
});
}
})(jQuery);