前端必刷系列之红宝书——第 18 章

"红宝书" 通常指的是《JavaScript 高级程序设计》,这是一本由 Nicholas C. Zakas(尼古拉斯·扎卡斯)编写的 JavaScript 书籍,是一本广受欢迎的经典之作。这本书是一部翔实的工具书,满满的都是 JavaScript 知识和实用技术。

不管你有没有刷过红宝书,如果现在还没掌握好,那就一起来刷红宝书吧,go!go!go!

系列文章:

第一部分:基本知识(重点、反复阅读)

  1. 前端必刷系列之红宝书------第 1、2 章
  2. 前端必刷系列之红宝书------第 3 章
  3. 前端必刷系列之红宝书------第 4、5 章
  4. 前端必刷系列之红宝书------第 6 章

第二部分:进阶内容(重点、反复阅读)

  1. 前端必刷系列之红宝书------第 7 章
  2. 前端必刷系列之红宝书------第 8 章
  3. 前端必刷系列之红宝书------第 9 章
  4. 前端必刷系列之红宝书------第 10 章
  5. 前端必刷系列之红宝书------第 11 章

第三部分:BOM 和 DOM (着重学习)

  1. 前端必刷系列之红宝书------第 12、13 章
  2. 前端必刷系列之红宝书------第 14 章
  3. 前端必刷系列之红宝书------第 18 章

第 18 章 动画与 Canvas 图形

使用 requestAnimationFrame

一般计算机显示器的屏幕刷新率都是 60Hz,基本上意味着每秒需要重绘 60 次。大多数浏览器会限制重绘频率,使其不超出屏幕的刷新率,这是因为超过刷新率,用户也感知不到。

因此,实现平滑动画最佳的重绘间隔为 1000 毫秒/60,大约 17 毫秒。以这个速度重绘可以实现最平滑的动画,因为这已经是浏览器的极限了。如果同时运行多个动画,可能需要加以限流,以免 17 毫秒的重绘间隔过快,导致动画过早运行完。

requestAnimationFrame() 用以通知浏览器某些 JS 代码要执行动画了。这样浏览器就可以在运行某些代码后进行适当的优化。目前所有浏览器都支持这个方法。

js 复制代码
let requestID = null
let enabled = true;

function expensiveOperation() { 
    console.log('Invoked at', Date.now()); 
}

// 节流
window.addEventListener('scroll', () => { 
    if (enabled) { 
        enabled = false; 
        requestID = window.requestAnimationFrame(expensiveOperation); 
        window.setTimeout(() => enabled = true, 50); 
    } 
}); 

// 返回一个请求 ID,可以用于来取消重绘任务
window.cancelAnimationFrame(requestID); 

基本的画布功能

js 复制代码
let drawing = document.getElementById("drawing"); 

// 确保浏览器支持<canvas> 
if (drawing.getContext) {
    let context = drawing.getContext("2d");
    
    // 取得图像的数据 URI 
    let imgURI = drawing.toDataURL("image/png"); 
    // 显示图片
    let image = document.createElement("img"); 
    image.src = imgURI; 
    document.body.appendChild(image); 
}

2D 绘图上下文

2D 绘图上下文提供了绘制 2D 图形的方法,包括矩形、弧形和路径。

2D 上下文的坐标原点(0, 0)<canvas> 元素的左上角。所有坐标值都相对于该点计算,因此 x 坐标向右增长,y 坐标向下增长

默认情况下,width 和 height 表示两个方向上像素的最大值。

填充和描边

填充 fillStyle以指定样式(颜色、渐变或图像)自动填充形状,而描边 strokeStyle只为图形边界着色。

js 复制代码
let drawing = document.getElementById("drawing"); 
// 确保浏览器支持<canvas> 
if (drawing.getContext) { 
    let context = drawing.getContext("2d"); 
    context.strokeStyle = "red"; 
    context.fillStyle = "#0000ff"; 
} 

绘制矩形

矩形是唯一一个可以直接在 2D 绘图上下文中绘制的形状。

与绘制矩形相关的方法有 3 个:fillRect()、strokeRect()和 clearRect()。这些方法都接收 4 个参数:矩形 x 坐标、矩形 y 坐标、矩形宽度和矩形高度。这几个参数的单位都是像素。

js 复制代码
let drawing = document.getElementById("drawing"); 
// 确保浏览器支持<canvas> 
if (drawing.getContext) { 
    let 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); 
    
    
    // 绘制红色轮廓的矩形
    context.strokeStyle = "#ff0000"; 
    context.strokeRect(10, 10, 50, 50); 
    // 绘制半透明蓝色轮廓的矩形
    context.strokeStyle = "rgba(0,0,255,0.5)"; 
    context.strokeRect(30, 30, 50, 50); 
} 

绘制路径

js 复制代码
// 开始绘制新路径
beginPath()

// 以(x, y)为圆心,以 radius 为半径绘制一条弧线,
// 起始角度 startAngle,结束角度为 endAngle(都是弧度)。
// counterclockwise 表示是否逆时针计算起始角度和结束角度(默认为顺时针)。
arc(x, y, radius, startAngle, endAngle, counterclockwise)

// 以给定半径 radius,经由(x1, y1)绘制一条从上一点到(x2, y2)的弧线。
arcTo(x1, y1, x2, y2, radius)

// 以(c1x, c1y)和(c2x, c2y)为控制点,绘制一条从上一点到(x, y)的弧线(三次贝塞尔曲线)。
bezierCurveTo(c1x, c1y, c2x, c2y, x, y)

// 绘制一条从上一点到(x, y)的直线。
lineTo(x, y)

// 不绘制线条,只把绘制光标移动到(x, y)
moveTo(x, y)

// 以(cx, cy)为控制点,绘制一条从上一点到(x, y)的弧线(二次贝塞尔曲线)。
quadraticCurveTo(cx, cy, x, y)

// 以给定宽度和高度在坐标点(x, y)绘制一个矩形。
// 这个方法与 strokeRect()和 fillRect()的区别在于,
// 它创建的是一条路径,而不是独立的图形。
rect(x, y, width, height)

绘制文本

  • font:以 CSS 语法指定的字体样式、大小、字体族等,比如"10px Arial"。
  • textAlign:指定文本的对齐方式,可能的值包括"start"、"end"、"left"、"right"和 "center"。推荐使用"start"和"end",不使用"left"和"right",因为前者无论在从左到右书写的语言还是从右到左书写的语言中含义都更明确。
  • textBaseLine :指定文本的基线,可能的值包括"top"、"hanging"、"middle"、"alphabetic"、"ideographic"和"bottom"

变换

js 复制代码
// 围绕原点把图像旋转 angle 弧度
rotate(angle)

// 通过在 x 轴乘以 scaleX、在 y 轴乘以 scaleY 来缩放图像。
// scaleX 和 scaleY 的默认值都是 1.0。
scale(scaleX, scaleY)

// 把原点移动到(x, y)。执行这个操作后,坐标(0, 0)就会变成(x, y)。
translate(x, y)

// 像下面这样通过矩阵乘法直接修改矩阵。
// m1_1 m1_2 dx 
// m2_1 m2_2 dy 
// 0 0 1 
transform(m1_1, m1_2, m2_1, m2_2, dx, dy) 

// 把矩阵重置为默认值,再以传入的参数调用 transform()。
setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy):
js 复制代码
let drawing = document.getElementById("drawing"); 
// 确保浏览器支持<canvas> 
if (drawing.getContext) { 
    let 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); 
    
    // 1号方法
    // 绘制分针
    // context.moveTo(100, 100); 
    // context.lineTo(100, 15); 
    // 绘制时针
    // context.moveTo(100, 100); 
    // context.lineTo(35, 100);  
    
    // 2号方法
    // 移动原点到表盘中心
    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();
    
    context.font = "bold 14px Arial"; 
    context.textAlign = "center"; 
    context.textBaseline = "middle"; 
    context.fillText("12", 100, 20); 
} 

绘制图像

js 复制代码
// 要绘制的图像、源图像 x 坐标、源图像 y 坐标、源图像宽度、源图像高度
// 目标区域 x 坐标、目标区域 y 坐标、目标区域宽度和目标区域高度
context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60); 

阴影

  • shadowColor:CSS 颜色值,表示要绘制的阴影颜色,默认为黑色。
  • shadowOffsetX:阴影相对于形状或路径的 x 坐标的偏移量,默认为 0。
  • shadowOffsetY:阴影相对于形状或路径的 y 坐标的偏移量,默认为 0。
  • shadowBlur:像素,表示阴影的模糊量。默认值为 0,表示不模糊。

渐变

js 复制代码
// 创建线性渐变对象 (x0, y0, x1, y1)
let gradient = context.createLinearGradient(30, 30, 70, 70); 
gradient.addColorStop(0, "white"); 
gradient.addColorStop(1, "black"); 



// 径向渐变 (x0, y0, r0, x1, y1, r1)
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); 

图案

js 复制代码
// 获取 Canvas 元素
var canvas = document.getElementById('imagePatternCanvas');
var ctx = canvas.getContext('2d');

// 创建图像对象
var img = new Image();
img.src = 'path/to/your/image.jpg';

// 图像加载完成后创建图像图案
img.onload = function() {
    // 'repeat', 'repeat-x', 'repeat-y', 'no-repeat'
    var pattern = ctx.createPattern(img, 'repeat'); 

    // 使用图案填充矩形
    ctx.fillStyle = pattern;
    ctx.fillRect(0, 0, 400, 200);
};

图像数据

js 复制代码
let drawing = document.getElementById("drawing"); 
// 确保浏览器支持<canvas> 
if (drawing.getContext) { 
    let 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 并应用到画布上显示出来
    imageData.data = data; 
    context.putImageData(imageData, 0, 0); 
} 

合成

在 Canvas 中,你可以使用合成操作来控制绘制的图形之间的混合方式。合成操作允许你通过不同的合成模式来控制图形的显示效果。

js 复制代码
// 获取 Canvas 元素
var canvas = document.getElementById('globalCompositeModeCanvas');
var ctx = canvas.getContext('2d');

// 绘制一个红色矩形
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 100);

// 设置全局合成模式
ctx.globalCompositeOperation = 'destination-over';

// 绘制一个蓝色矩形,根据合成模式绘制在前一个矩形的后面
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 100, 100);

上述代码,首先绘制了一个红色的矩形,然后设置了全局合成模式为 'destination-over',接着绘制了一个蓝色的矩形,根据合成模式,蓝色矩形被绘制在前一个红色矩形的后面。

js 复制代码
// 获取 Canvas 元素
var canvas = document.getElementById('compositeOperationsCanvas');
var ctx = canvas.getContext('2d');

// 绘制一个红色矩形
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 100);

// 设置透明度
ctx.globalAlpha = 0.5;

// 绘制一个蓝色矩形,根据透明度产生混合效果
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 100, 100);

上述代码,使用 globalAlpha 设置了透明度,然后绘制了一个蓝色的矩形,产生了透明度混合的效果。

WebGL 上下文

WebGL 是画布的 3D 上下文。

WebGL 是以 OpenGL ES 2.0 为基础的。要使用 WebGL 最好熟悉 OpenGL ES 2.0,因为很多概念可以照搬过来。

learnopengl-cn.github.io/ learnwebgl.brown37.net/

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WebGL Simple Example</title>
</head>

<body>
  <canvas id="myCanvas" width="400" height="400" style="border: 1px solid #000;"></canvas>

  <script>
    document.addEventListener("DOMContentLoaded", function () {
      // 获取 Canvas 元素
      var canvas = document.getElementById('myCanvas');
      // 获取 webgl
      var gl = canvas.getContext('webgl')

      if (!gl) {
        console.log('Unable to initialize WebGL. Your browser may not support it.');
        return;
      }

      const vertexShaderSrc = `
        attribute vec4 a_Position;
        void main() {
          gl_Position = a_Position;
        }
        `;

      const fragmentShaderSrc = `
        void main() {
          gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
        `;

      // 渲染器生成处理
      // 创建顶点渲染器
      const vertexShader = gl.createShader(gl.VERTEX_SHADER);
      gl.shaderSource(vertexShader, vertexShaderSrc);
      gl.compileShader(vertexShader);

      // 创建片元渲染器
      const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
      gl.shaderSource(fragmentShader, fragmentShaderSrc);
      gl.compileShader(fragmentShader);

      // 创建程序对象
      const program = gl.createProgram();
      gl.attachShader(program, vertexShader);
      gl.attachShader(program, fragmentShader);
      gl.linkProgram(program);
      gl.useProgram(program);
      gl.program = program;

      // 顶点数据
      // prettier-ignore
      const vertices = new Float32Array([
        0, 0.5, // 第一个点
        -0.5, -0.5, // 第二个点
        0.5, -0.5, // 第三个点
      ]);

      // 创建缓存对象
      const vertexBuffer = gl.createBuffer();
      // 绑定缓存对象到上下文
      gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
      // 向缓存区写入数据
      gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

      // 获取 a_Position 变量地址
      const a_Position = gl.getAttribLocation(gl.program, "a_Position");
      // 将缓冲区对象分配给 a_Position 变量
      gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

      // 允许访问缓存区
      gl.enableVertexAttribArray(a_Position);

      // 绘制
      // 清空画布,并指定颜色
      gl.clearColor(0, 0, 0, 1);
      gl.clear(gl.COLOR_BUFFER_BIT);

      // 绘制三角形
      gl.drawArrays(gl.TRIANGLES, 0, 3);
    });
  </script>
</body>
</html>

效果图:

未完待续...

参考资料

《JavaScript 高级程序设计》(第 4 版)

相关推荐
让开,我要吃人了2 小时前
HarmonyOS开发实战(5.0)实现二楼上划进入首页效果详解
前端·华为·程序员·移动开发·harmonyos·鸿蒙·鸿蒙系统
everyStudy3 小时前
前端五种排序
前端·算法·排序算法
甜兒.4 小时前
鸿蒙小技巧
前端·华为·typescript·harmonyos
Jiaberrr8 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy8 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白8 小时前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、8 小时前
Web Worker 简单使用
前端
web_learning_3218 小时前
信息收集常用指令
前端·搜索引擎
tabzzz8 小时前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
200不是二百9 小时前
Vuex详解
前端·javascript·vue.js