1.RequestAnimationFrame
早期定时动画
setTimeout和setInterval不能保证时间精度,第二个参数只能保证何时将代码添加到浏览器的任务队列
requestAnimationFrame(cb)的cb在浏览器重绘屏幕前调用
function updateProgress(){
const div = document.getElementById('div');
div.style.width = (parseInt(div.style.width, 10) + 5) + '%';
if (div.style.width !== '100%'){
requestAnimationFrame(updateProgress);
}
}
requestAnimationFrame(updateProgress);
requestAnimationFrame()的函数实际上可以接受一个参数,该参数为DOMHighResTimeStamp的实例,表示下次重绘的时间点。
cancelAnimationFrame(id); // 对应clearTimeout clearInterval
requestAnimationFrame()可以用作节流
let enabled = true;
function expensiveOperation(){
console.log('scroll', Date.now());
}
window.addEventListener('scroll', () => {
if (enabled) {
enabled = false;
window.requestAnimationFrame(expensiveOperation);
setTimeout(() => enabled = true, 50)
}
}
2.基本的画布功能
<canvas>元素必须要指定width和height两个属性,标签之间的内容是在浏览器不支持的情况下展示的内容
getContext();// 获取绘制上下文,参数为‘2d’或者‘webgl’
toDataURL(); // 导出canvas元素上的图像,接受一个MIME类型参数
3、2D绘图上下文
原点(0,0)在左上角,width和height分别为x轴和y轴的最大值
填充和描边
fillStyle和strokeStyle两个属性,属性值可以是字符串、渐变对象和图案对象
const myCanvas = document.getElementById('myCanvas');
const ctx = myCanvas.getContext('2d');
ctx.strokeStyle = 'red';
ctx.fillStyle = '#0000ff';
绘制矩形
fillRect(x,y, width, height);
strokeRect(x, y, width, height);
clearRect(x, y, width, height);
绘制路径
必须beginPath()开始绘制新路径,closePath()绘制一条返回起点的路径。而后可以指定fillStyle属性后,使用fill()填充区域,或者指定strokeStyle属性后,使用stroke()填充路径,或者使用clip()创建一个新的剪切区域。
方法 | 说明 |
---|---|
arc(x, y, startAngle, endAngle, counterclockwise) | 以(x,y)为圆心,从起始角度到结束角度绘制弧线,最后一个参数为是否顺时针,默认为true |
arcTo(x1, y1, x2, y2, radius) | 给定半径,经过(x1, y1)从上一个点绘制到(x2,y2)的弧线 |
bezierCurveTo(c1x, c1y, c2x, c2y, x, y) | 以(c1x, c1y)(c2x, c2y)为控制点,绘制从上一点到(x,y)的三次贝塞尔曲线 |
lineTo(x,y) | 上一点到(x,y)的直线 |
moveTo(x,y) | 移动光标到(x,y) |
quadraticCurveTo(cx, cy) | 以(cx, cy)为控制点,绘制从上一点到(x,y)的二次贝塞尔曲线 |
rect(x, y, width, height) | 绘制一个矩形路径,而非图形 |
isPointInPath(x, y) | 判断(x, y)是否在当前路径 |
绘制文本
fillText(contentStr, x, y, maxWidth);
strokeText(contentStr, x, y, maxWidth);
// 相关设置属性
ctx.font; // 以css样式指定字体样式、大小、字体簇
ctx.textAlign; // 文本对齐方式 start/end/left/right/center
ctx.textBaseLine; // 文本基线, top/hanging/middle/alphabetic/ideographic/bottom
辅助确定文本大小的方法measureText(str),返回一个TextMetrics对象
变换
方法 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
rotate(angle) | 围绕原点将图像旋转angle | |||||||||
scale(scaleX, scaleY) | 在x轴和y轴上分别缩放相应倍数 | |||||||||
translate(x, y) | 将坐标原点移动到(x, y) | |||||||||
transform(m1_1, m1_2, m2_1, m2_2, dx, dy) | 通过矩阵乘法直接修改上下文矩阵
| |||||||||
setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy) | 先将上下文变换矩阵重置,然后以传入的参数调用transform |
save()可以将所有设置放入一个暂存栈,需要恢复时调用restore()
绘制图像
drawImage()可以接受三组不同的参数
//1.传入一个HTML的<img>元素
let img = document.images[0];
ctx.drawImage(img, x, y);
ctx.drawImage(img, x, y, width, height); // width和height默认为<img>的宽高
// 2.将图像的部分绘制到指定区域
ctx.drawImage(img, srcX, srcY, srcWidth, srcHeight, targetX, targetY, targetWidth, targetHeight);
// 3.第一个参数可以是另外一个canvas元素
// 操作的结果可以通过toDataURL() 获取
阴影
属性值设置
属性 | 说明 |
---|---|
shadowColor | CSS颜色值,默认黑色 |
shadowOffsetX | x坐标偏移量 |
shadowOffsetY | y坐标偏移量 |
shadowBlur | 阴影模糊量 |
渐变
线性渐变通过CanvasGradient的实例表示,createLinearGradient()创建一个CanvasGradient实例,接受起点和终点的坐标作为参数;
addColorStop()方法为gradient对象添加色标,接受两个参数(位置0-1,颜色)
const gradient = ctx.createLinearGradient(30,30,70,70);
gradient.addColorStop(0, 'white');
gradient.addColorStop(1, 'black');
// 绘制红色矩形
ctx.fillStyle = '#FF0000';
ctx.fillRect(10,10,50,50);
// 绘制渐变矩形
ctx.fillStyle = gradient;
ctx.fillRect(30,30,50,50);
径向渐变通过createRadialGradient()创建,接受六个参数,分别为起点和终点的圆心坐标、半径、
ctx.createRadialGradient(startX, startY, startR, endX, endY, endR);
图案
用于填充和描画重复图案,调用createPattern(),接受两个参数,第一个参数为<img>或者<video> 和<canvas>,第二个参数为重复模式(repeat-x, repeat-y, repeat, no-repeat)
图像数据
getImageData(startX, startY, width, height)返回一个ImageData对象,每个ImageData包含三个属性width, height, data,其中data中每四个值分别表示第i个像素点的红、绿、蓝和透明度值,取值范围为0-255
合成
属性值 | 说明 | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
globalAlpha | 全局透明度,0-1, 用于指定绘制内容的透明度 | ||||||||||||||||||||||
globalCompositionOperation | 新绘制的形状如何与已有的上下文形状融合
|
4.WebGL
webgl上下文
getContext(‘webgl’);
webgl基础
可以在调用getContext()时传入配置项,指定创建的上下文的一些选项
配置项 | 说明 | 默认值 |
---|---|---|
alpha | 是否为上下文创建透明通道缓冲区 | true |
depth | 是否使用16位深缓冲区 | true |
stencil | 是否使用8位模板缓冲区 | false |
antialias | 是否使用默认机制执行抗锯齿操作 | true |
premultipliedAlpha | 绘图缓冲区是否预乘透明度 | true |
preserveDrawingBuffer | 绘图完成后是否保留绘图缓冲区 | false |
let drawing = document.getElementById("drawing");
// 确保浏览器支持<canvas>
if (drawing.getContext) {
let gl = drawing.getContext("webgl", { alpha: false });
if (gl) {
// 使用 WebGL
}
}
1.常量
OpenGL中以GL_开头的常量大部分都被webGL支持,但不需要这个前缀
gl.COLOR_BUFFER_BIT // GL_COLOR_BUFFER_BIT
2.方法命名
参数数量(1-4)+数据类型(‘f’, ‘i’)如, gl.uniform4f()
3.准备绘图
绘图之前,需要使用clearColor(r, g, b, a) 使用一种颜色清除<canvas>元素, rgba参数值都在0-1;
也可以使用gl.clear(gl.COLOR_BUFFER_BIT)使用之前定义的颜色填充画图
4.视口与坐标
默认情况下,视口为整个canvas区域,也可以使用viewport()方法改变视口
gl.viewport(x, y, width, height)
定义视口的坐标系统是canvas像素坐标系统,而视口中,中心点是原点(0,0),左下角是(-1,-1),右上角是(1,1)。
5.缓冲区
const buffer = gl.createBuffer(); // 创建缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); // 将缓冲区绑定到上下文中,此处是将buffer与ARRAY_BUFFER绑定,将buffer设置为当前上下文的缓冲区
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0.5,1]), gl.STATIC_DRAW); // 将数据写入缓冲区第三个参数可以有3个选择
/**
gl.STATIC_DRAW 数据加载一次,可以在多次绘制中使用
gl.STREAM_DRAW 数据加载一次,只能在几次绘制中使用
gl.DYNAMIC_DRAW 数据可以重复修改,在多次绘制中使用
**/
// 不再需要缓冲区
gl.deleteBuffer(buffer);
6.错误
webGL操作中通常不会抛出错误,gl.getError()方法会返回一个常量,表示发生的错误类型
gl.NO_ERROR | 没有发生错误 |
gl.INVALID_ENUM | 上一次操作没有传入webgl预定的常量 |
gl.INVALID_VALUE | 上一次操作需要无符号数值,但是传入了负数 |
gl.INVALID_OPERATION | 上一次操作在当前状态下无法完成 |
gl.OUT_OF_MEMORY | 上次操作因内存不足无法完成 |
gl.CONTEXT_LOST_WEBGL | 上次操作因外部事件而丢失了上下文 |
7.着色器
定点着色器将3D顶点绘制为2D的点;
片元着色器将计算每个像素的颜色值;
使用GLSL编程
编写着色器
每个着色器有一个main()方法,绘制期间反复执行,
给着色器传递数据的方式有两种attribute
和uniform
attribute将定点传入定点着色器,uniform将任意常量值传入任意着色器
创建着色器程序
gl.createShader(shaderType); // shaderType: gl.VERTEX_SHADER, gl.FRAGMENT_SHADER 创建着色器
gl.shaderSource(shader, shaderSource);// 将glsl代码应用于着色器
gl.compileShader(shader); // 编译着色器
const program = gl.createProgram(); // 创建着色器程序
gl.attachShader(program, shader); // 将着色器添加到着色器程序,需要调用两次分别传入定点着色器和片元着色器
gl.linkProgram(program); // 将两个着色器链接到变量program
gl.useProgram(program); // 让webgl上下文使用该程序
给着色器传值
// 1.找到接受值的变量位置
let uColorLocation = gl.getUniformLocation(program, 'uColor'); // 针对uniform变量uColor
let aVertextPosLocation = gl.getAttribLocation(program, 'aVertextPosition'); // 针对attribute变量aVertexPosition
// 2.传值
// 针对uniform变量
gl.uniform4fv(uColorLocation, [0,0,0,1]);
// 针对attribte变量
gl.enableVertexAttribArray(aVertexPosLocation);
gl.vertexAttribPointer(aVertexPosLocation, itemSize, gl.FLOAT, false, 0, 0) ; // 创建一个指向bindBuffer()指定的缓冲区指针,并保存在aVertexPosLocation中,
8.绘图
webGL只能绘制3中基本图形:点、线、三角形
绘图方法有两种:
drawArrays()
使用数组缓冲区
drawElements()
操作元素数组缓冲区
drawArrays()
接受三个参数,第一个参数表示形状,第二个参数是数组缓冲区的起点索引,第三个参数是数组缓冲区顶点集合的数量
形状参数列表:
参数 | 意义 |
---|---|
gl.POINTS | 将每个顶点当做一个点 |
gl.LINES | 将数组作为一系列点,每两个点依次绘制直线,顶点数必须是偶数 |
gl.LINE_LOOP | 将数组作为一系列顶点,依次依次绘制直线连接成封闭形状 |
gl.LINE_STRIP | 类似于gl.LINE_LOOP,区别在于最后不会连接终点和起点 |
gl.TRIANGLES | 将数组作为一系列顶点,在这些顶点间绘制三角形,每个三角形都不会共享顶点 |
gl.TRIANGLES_STRIP | 类似于gl.TRIANGLES,区别在于共享点,如果有ABCD四个点,则会绘制两个三角形ABC和BCD |
gl.TRIANGLES_FAN | 如果有ABCD四个点,则会绘制ABC, ACD三角形 |
/****** 绘制一个三角形 ******/
const canvas = document.querySelector('#canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
const vSource = `
attribute vec4 a_position;
void main(){
gl_Position = a_position;
}
`;
const fSource = `
precision highp float;
uniform vec4 uColor;
void main(){
gl_FragColor = uColor;
}
`;
const program = gl.createProgram();
const vShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vShader, vSource);
gl.compileShader(vShader);
const fShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fShader, fSource);
gl.compileShader(fShader);
gl.attachShader(program, vShader);
gl.attachShader(program, fShader);
gl.linkProgram(program);
gl.useProgram(program);
const uColorLocation = gl.getUniformLocation(program, 'uColor');
gl.uniform4f(uColorLocation, 1, 0, 0, 0.5);
const positionData = [0.5, 0, 0, 0, 0.5, 0, -0.5, -0.5, 0.5];
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positionData), gl.STATIC_DRAW);
const positionLocation = gl.getAttribLocation(program, 'a_position');
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLocation);
gl.clearColor(0,1,1,1);
gl.clear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.TRIANGLES, 0, 3);
纹理
纹理的来源可以是图片、<video>或者<canvas>
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);
}
读取像素
// 读取(0,0)到(25,25)区域的像素,并写入pixels数组,rgba四值,0-255
let pixels = new Uint8Array(25*25);
gl.readPixels(0, 0, 25, 25, gl.RGBA, gl.UNSIGNED_BYTE, pixels);