活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

JavaScript输出矩形从入门到精通掌握多种绘制方法与技巧提升前端开发技能

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-19 01:20:35 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

在Web前端开发中,图形绘制是一项基础且重要的技能。矩形作为最基础的几何图形之一,在各种Web应用中都有广泛的应用,从简单的界面元素到复杂的数据可视化。掌握JavaScript输出矩形的多种方法,不仅能提升你的前端开发技能,还能为你在创建用户界面、数据可视化、游戏开发等方面提供强大的工具。本文将带你从基础到高级,全面了解使用JavaScript绘制矩形的各种方法和技巧。

1. 基础方法:使用HTML和CSS创建矩形

1.1 使用HTML元素创建矩形

最简单的创建矩形的方法是使用HTML元素,如div,并通过CSS设置其样式:
  1. <div class="rectangle"></div>
复制代码
  1. .rectangle {
  2.   width: 200px;
  3.   height: 100px;
  4.   background-color: #3498db;
  5. }
复制代码

这种方法简单直观,适合创建静态的矩形元素,但缺乏动态性和灵活性。

1.2 使用JavaScript动态创建矩形元素

我们可以使用JavaScript动态创建矩形元素,并添加到DOM中:
  1. // 创建矩形元素
  2. function createRectangle(width, height, color) {
  3.   const rectangle = document.createElement('div');
  4.   rectangle.style.width = width + 'px';
  5.   rectangle.style.height = height + 'px';
  6.   rectangle.style.backgroundColor = color;
  7.   
  8.   // 添加到页面中
  9.   document.body.appendChild(rectangle);
  10.   
  11.   return rectangle;
  12. }
  13. // 使用示例
  14. const myRectangle = createRectangle(300, 150, '#e74c3c');
复制代码

这种方法允许我们根据需要动态创建矩形,但仍然受限于CSS样式的能力。

2. Canvas API绘制矩形

Canvas API是HTML5提供的一个强大的绘图接口,它允许我们使用JavaScript在画布上绘制图形,包括矩形。

2.1 Canvas基础设置

首先,我们需要在HTML中创建一个canvas元素:
  1. <canvas id="myCanvas" width="500" height="300"></canvas>
复制代码

然后,在JavaScript中获取canvas上下文:
  1. const canvas = document.getElementById('myCanvas');
  2. const ctx = canvas.getContext('2d');
复制代码

2.2 绘制填充矩形

使用fillRect()方法绘制填充矩形:
  1. function drawFilledRectangle(x, y, width, height, color) {
  2.   ctx.fillStyle = color;
  3.   ctx.fillRect(x, y, width, height);
  4. }
  5. // 使用示例
  6. drawFilledRectangle(50, 50, 200, 100, '#3498db');
复制代码

2.3 绘制描边矩形

使用strokeRect()方法绘制只有边框的矩形:
  1. function drawStrokedRectangle(x, y, width, height, color, lineWidth) {
  2.   ctx.strokeStyle = color;
  3.   ctx.lineWidth = lineWidth || 1;
  4.   ctx.strokeRect(x, y, width, height);
  5. }
  6. // 使用示例
  7. drawStrokedRectangle(100, 100, 200, 100, '#e74c3c', 3);
复制代码

2.4 绘制填充和描边矩形

结合fillRect()和strokeRect()方法,我们可以创建既有填充又有描边的矩形:
  1. function drawRectangleWithStroke(x, y, width, height, fillColor, strokeColor, lineWidth) {
  2.   // 绘制填充矩形
  3.   ctx.fillStyle = fillColor;
  4.   ctx.fillRect(x, y, width, height);
  5.   
  6.   // 绘制描边矩形
  7.   ctx.strokeStyle = strokeColor;
  8.   ctx.lineWidth = lineWidth || 1;
  9.   ctx.strokeRect(x, y, width, height);
  10. }
  11. // 使用示例
  12. drawRectangleWithStroke(150, 150, 200, 100, '#2ecc71', '#27ae60', 3);
复制代码

2.5 使用路径绘制矩形

我们也可以使用路径来绘制矩形,这提供了更多的灵活性:
  1. function drawRectangleWithPath(x, y, width, height, fillColor, strokeColor, lineWidth) {
  2.   ctx.beginPath();
  3.   ctx.rect(x, y, width, height);
  4.   
  5.   if (fillColor) {
  6.     ctx.fillStyle = fillColor;
  7.     ctx.fill();
  8.   }
  9.   
  10.   if (strokeColor) {
  11.     ctx.strokeStyle = strokeColor;
  12.     ctx.lineWidth = lineWidth || 1;
  13.     ctx.stroke();
  14.   }
  15. }
  16. // 使用示例
  17. drawRectangleWithPath(200, 200, 200, 100, '#f39c12', '#d68910', 3);
复制代码

2.6 清除矩形区域

使用clearRect()方法可以清除指定矩形区域的内容:
  1. function clearRectangleArea(x, y, width, height) {
  2.   ctx.clearRect(x, y, width, height);
  3. }
  4. // 使用示例
  5. // 在画布上绘制一个矩形
  6. drawFilledRectangle(50, 50, 200, 100, '#3498db');
  7. // 清除矩形的一部分
  8. clearRectangleArea(100, 75, 100, 50);
复制代码

3. SVG绘制矩形

SVG(Scalable Vector Graphics)是一种基于XML的矢量图像格式,它也可以用来绘制矩形。与Canvas不同,SVG创建的图形是DOM元素,可以直接通过CSS样式化或通过JavaScript操作。

3.1 基本SVG矩形

在HTML中创建SVG矩形:
  1. <svg width="500" height="300">
  2.   <rect x="50" y="50" width="200" height="100" fill="#3498db" />
  3. </svg>
复制代码

3.2 使用JavaScript动态创建SVG矩形

我们可以使用JavaScript动态创建SVG矩形元素:
  1. function createSVGRectangle(container, x, y, width, height, fill, stroke, strokeWidth) {
  2.   // 创建SVG元素
  3.   const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  4.   svg.setAttribute('width', '500');
  5.   svg.setAttribute('height', '300');
  6.   
  7.   // 创建矩形元素
  8.   const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  9.   rect.setAttribute('x', x);
  10.   rect.setAttribute('y', y);
  11.   rect.setAttribute('width', width);
  12.   rect.setAttribute('height', height);
  13.   
  14.   if (fill) rect.setAttribute('fill', fill);
  15.   if (stroke) rect.setAttribute('stroke', stroke);
  16.   if (strokeWidth) rect.setAttribute('stroke-width', strokeWidth);
  17.   
  18.   // 将矩形添加到SVG中
  19.   svg.appendChild(rect);
  20.   
  21.   // 将SVG添加到容器中
  22.   container.appendChild(svg);
  23.   
  24.   return rect;
  25. }
  26. // 使用示例
  27. const container = document.getElementById('svg-container');
  28. createSVGRectangle(container, 50, 50, 200, 100, '#e74c3c', '#c0392b', 3);
复制代码

3.3 SVG矩形的圆角

SVG矩形支持圆角,通过rx和ry属性设置:
  1. function createRoundedSVGRectangle(container, x, y, width, height, rx, ry, fill, stroke, strokeWidth) {
  2.   const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  3.   svg.setAttribute('width', '500');
  4.   svg.setAttribute('height', '300');
  5.   
  6.   const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  7.   rect.setAttribute('x', x);
  8.   rect.setAttribute('y', y);
  9.   rect.setAttribute('width', width);
  10.   rect.setAttribute('height', height);
  11.   rect.setAttribute('rx', rx);
  12.   rect.setAttribute('ry', ry);
  13.   
  14.   if (fill) rect.setAttribute('fill', fill);
  15.   if (stroke) rect.setAttribute('stroke', stroke);
  16.   if (strokeWidth) rect.setAttribute('stroke-width', strokeWidth);
  17.   
  18.   svg.appendChild(rect);
  19.   container.appendChild(svg);
  20.   
  21.   return rect;
  22. }
  23. // 使用示例
  24. createRoundedSVGRectangle(container, 100, 100, 200, 100, 10, 10, '#2ecc71', '#27ae60', 3);
复制代码

4. 使用JavaScript库绘制矩形

除了原生API,我们还可以使用各种JavaScript库来简化矩形绘制过程。

4.1 使用jQuery创建矩形

jQuery可以简化DOM操作,使创建和操作矩形更加容易:
  1. <div id="rectangle-container"></div>
复制代码
  1. function createJQueryRectangle(container, width, height, color) {
  2.   const $rectangle = $('<div></div>')
  3.     .css({
  4.       'width': width + 'px',
  5.       'height': height + 'px',
  6.       'background-color': color
  7.     })
  8.     .appendTo(container);
  9.   
  10.   return $rectangle;
  11. }
  12. // 使用示例
  13. const $container = $('#rectangle-container');
  14. const $myRectangle = createJQueryRectangle($container, 300, 150, '#9b59b6');
复制代码

4.2 使用D3.js创建矩形

D3.js是一个强大的数据可视化库,它提供了创建和操作SVG元素的方法:
  1. <div id="d3-container"></div>
复制代码
  1. function createD3Rectangle(container, x, y, width, height, fill, stroke, strokeWidth) {
  2.   // 创建SVG容器
  3.   const svg = d3.select(container)
  4.     .append('svg')
  5.     .attr('width', 500)
  6.     .attr('height', 300);
  7.   
  8.   // 创建矩形
  9.   const rectangle = svg.append('rect')
  10.     .attr('x', x)
  11.     .attr('y', y)
  12.     .attr('width', width)
  13.     .attr('height', height)
  14.     .attr('fill', fill);
  15.   
  16.   if (stroke) {
  17.     rectangle.attr('stroke', stroke);
  18.   }
  19.   
  20.   if (strokeWidth) {
  21.     rectangle.attr('stroke-width', strokeWidth);
  22.   }
  23.   
  24.   return rectangle;
  25. }
  26. // 使用示例
  27. const d3Container = document.getElementById('d3-container');
  28. createD3Rectangle(d3Container, 50, 50, 200, 100, '#1abc9c', '#16a085', 3);
复制代码

4.3 使用Paper.js创建矩形

Paper.js是一个强大的矢量图形脚本框架,它运行在Canvas之上:
  1. <canvas id="paper-canvas" width="500" height="300"></canvas>
复制代码
  1. // 初始化Paper.js
  2. paper.setup('paper-canvas');
  3. function createPaperRectangle(x, y, width, height, fillColor, strokeColor, strokeWidth) {
  4.   const rectangle = new paper.Rectangle(new paper.Point(x, y), new paper.Size(width, height));
  5.   const path = new paper.Path.Rectangle(rectangle);
  6.   
  7.   if (fillColor) {
  8.     path.fillColor = fillColor;
  9.   }
  10.   
  11.   if (strokeColor) {
  12.     path.strokeColor = strokeColor;
  13.   }
  14.   
  15.   if (strokeWidth) {
  16.     path.strokeWidth = strokeWidth;
  17.   }
  18.   
  19.   paper.view.draw();
  20.   
  21.   return path;
  22. }
  23. // 使用示例
  24. createPaperRectangle(100, 100, 200, 100, '#f1c40f', '#f39c12', 3);
复制代码

5. 高级技巧:动画矩形和交互式矩形

5.1 使用CSS动画创建动画矩形

我们可以使用CSS动画为矩形添加简单的动画效果:
  1. <div class="animated-rectangle"></div>
复制代码
  1. .animated-rectangle {
  2.   width: 100px;
  3.   height: 100px;
  4.   background-color: #3498db;
  5.   animation: moveAndChange 3s infinite alternate;
  6. }
  7. @keyframes moveAndChange {
  8.   0% {
  9.     transform: translateX(0);
  10.     background-color: #3498db;
  11.   }
  12.   100% {
  13.     transform: translateX(400px);
  14.     background-color: #e74c3c;
  15.   }
  16. }
复制代码

5.2 使用JavaScript控制矩形动画

使用JavaScript可以更灵活地控制矩形动画:
  1. <canvas id="animation-canvas" width="500" height="300"></canvas>
复制代码
  1. const canvas = document.getElementById('animation-canvas');
  2. const ctx = canvas.getContext('2d');
  3. let x = 50;
  4. let y = 50;
  5. let width = 100;
  6. let height = 100;
  7. let dx = 2; // x方向速度
  8. let dy = 1; // y方向速度
  9. let color = '#3498db';
  10. function animateRectangle() {
  11.   // 清除画布
  12.   ctx.clearRect(0, 0, canvas.width, canvas.height);
  13.   
  14.   // 绘制矩形
  15.   ctx.fillStyle = color;
  16.   ctx.fillRect(x, y, width, height);
  17.   
  18.   // 更新位置
  19.   x += dx;
  20.   y += dy;
  21.   
  22.   // 边界检测
  23.   if (x + width > canvas.width || x < 0) {
  24.     dx = -dx;
  25.     color = getRandomColor();
  26.   }
  27.   
  28.   if (y + height > canvas.height || y < 0) {
  29.     dy = -dy;
  30.     color = getRandomColor();
  31.   }
  32.   
  33.   // 继续动画
  34.   requestAnimationFrame(animateRectangle);
  35. }
  36. function getRandomColor() {
  37.   const letters = '0123456789ABCDEF';
  38.   let color = '#';
  39.   for (let i = 0; i < 6; i++) {
  40.     color += letters[Math.floor(Math.random() * 16)];
  41.   }
  42.   return color;
  43. }
  44. // 开始动画
  45. animateRectangle();
复制代码

5.3 创建交互式矩形

我们可以创建响应用户交互的矩形:
  1. <canvas id="interactive-canvas" width="500" height="300"></canvas>
复制代码
  1. const canvas = document.getElementById('interactive-canvas');
  2. const ctx = canvas.getContext('2d');
  3. // 矩形对象
  4. const rectangle = {
  5.   x: 200,
  6.   y: 150,
  7.   width: 100,
  8.   height: 100,
  9.   color: '#3498db',
  10.   isDragging: false,
  11.   offsetX: 0,
  12.   offsetY: 0
  13. };
  14. // 绘制矩形
  15. function drawRectangle() {
  16.   ctx.clearRect(0, 0, canvas.width, canvas.height);
  17.   ctx.fillStyle = rectangle.color;
  18.   ctx.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
  19. }
  20. // 检查点是否在矩形内
  21. function isPointInRectangle(x, y) {
  22.   return x >= rectangle.x &&
  23.          x <= rectangle.x + rectangle.width &&
  24.          y >= rectangle.y &&
  25.          y <= rectangle.y + rectangle.height;
  26. }
  27. // 鼠标按下事件
  28. canvas.addEventListener('mousedown', (e) => {
  29.   const rect = canvas.getBoundingClientRect();
  30.   const x = e.clientX - rect.left;
  31.   const y = e.clientY - rect.top;
  32.   
  33.   if (isPointInRectangle(x, y)) {
  34.     rectangle.isDragging = true;
  35.     rectangle.offsetX = x - rectangle.x;
  36.     rectangle.offsetY = y - rectangle.y;
  37.     canvas.style.cursor = 'grabbing';
  38.   }
  39. });
  40. // 鼠标移动事件
  41. canvas.addEventListener('mousemove', (e) => {
  42.   const rect = canvas.getBoundingClientRect();
  43.   const x = e.clientX - rect.left;
  44.   const y = e.clientY - rect.top;
  45.   
  46.   if (rectangle.isDragging) {
  47.     rectangle.x = x - rectangle.offsetX;
  48.     rectangle.y = y - rectangle.offsetY;
  49.     drawRectangle();
  50.   } else if (isPointInRectangle(x, y)) {
  51.     canvas.style.cursor = 'grab';
  52.   } else {
  53.     canvas.style.cursor = 'default';
  54.   }
  55. });
  56. // 鼠标释放事件
  57. canvas.addEventListener('mouseup', () => {
  58.   rectangle.isDragging = false;
  59.   canvas.style.cursor = 'default';
  60. });
  61. // 初始绘制
  62. drawRectangle();
复制代码

5.4 使用WebGL创建3D矩形

WebGL允许我们在浏览器中创建3D图形。下面是一个使用WebGL创建3D旋转矩形的示例:
  1. <canvas id="webgl-canvas" width="500" height="300"></canvas>
复制代码
  1. const canvas = document.getElementById('webgl-canvas');
  2. const gl = canvas.getContext('webgl');
  3. // 顶点着色器源码
  4. const vsSource = `
  5.   attribute vec4 aVertexPosition;
  6.   attribute vec4 aVertexColor;
  7.   
  8.   uniform mat4 uModelViewMatrix;
  9.   uniform mat4 uProjectionMatrix;
  10.   
  11.   varying lowp vec4 vColor;
  12.   
  13.   void main(void) {
  14.     gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
  15.     vColor = aVertexColor;
  16.   }
  17. `;
  18. // 片段着色器源码
  19. const fsSource = `
  20.   varying lowp vec4 vColor;
  21.   
  22.   void main(void) {
  23.     gl_FragColor = vColor;
  24.   }
  25. `;
  26. // 创建着色器
  27. function loadShader(gl, type, source) {
  28.   const shader = gl.createShader(type);
  29.   gl.shaderSource(shader, source);
  30.   gl.compileShader(shader);
  31.   
  32.   if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
  33.     console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
  34.     gl.deleteShader(shader);
  35.     return null;
  36.   }
  37.   
  38.   return shader;
  39. }
  40. // 初始化着色器程序
  41. function initShaderProgram(gl, vsSource, fsSource) {
  42.   const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
  43.   const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
  44.   
  45.   const shaderProgram = gl.createProgram();
  46.   gl.attachShader(shaderProgram, vertexShader);
  47.   gl.attachShader(shaderProgram, fragmentShader);
  48.   gl.linkProgram(shaderProgram);
  49.   
  50.   if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
  51.     console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
  52.     return null;
  53.   }
  54.   
  55.   return shaderProgram;
  56. }
  57. const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
  58. const programInfo = {
  59.   program: shaderProgram,
  60.   attribLocations: {
  61.     vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
  62.     vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'),
  63.   },
  64.   uniformLocations: {
  65.     projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
  66.     modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
  67.   },
  68. };
  69. // 创建矩形缓冲区
  70. function initBuffers(gl) {
  71.   // 矩形顶点位置
  72.   const positions = [
  73.     // 前面
  74.     -1.0, -1.0,  1.0,
  75.      1.0, -1.0,  1.0,
  76.      1.0,  1.0,  1.0,
  77.     -1.0,  1.0,  1.0,
  78.    
  79.     // 后面
  80.     -1.0, -1.0, -1.0,
  81.     -1.0,  1.0, -1.0,
  82.      1.0,  1.0, -1.0,
  83.      1.0, -1.0, -1.0,
  84.    
  85.     // 顶面
  86.     -1.0,  1.0, -1.0,
  87.     -1.0,  1.0,  1.0,
  88.      1.0,  1.0,  1.0,
  89.      1.0,  1.0, -1.0,
  90.    
  91.     // 底面
  92.     -1.0, -1.0, -1.0,
  93.      1.0, -1.0, -1.0,
  94.      1.0, -1.0,  1.0,
  95.     -1.0, -1.0,  1.0,
  96.    
  97.     // 右面
  98.      1.0, -1.0, -1.0,
  99.      1.0,  1.0, -1.0,
  100.      1.0,  1.0,  1.0,
  101.      1.0, -1.0,  1.0,
  102.    
  103.     // 左面
  104.     -1.0, -1.0, -1.0,
  105.     -1.0, -1.0,  1.0,
  106.     -1.0,  1.0,  1.0,
  107.     -1.0,  1.0, -1.0,
  108.   ];
  109.   
  110.   const positionBuffer = gl.createBuffer();
  111.   gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  112.   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
  113.   
  114.   // 矩形面颜色
  115.   const faceColors = [
  116.     [1.0, 0.0, 0.0, 1.0],    // 前面: 红色
  117.     [0.0, 1.0, 0.0, 1.0],    // 后面: 绿色
  118.     [0.0, 0.0, 1.0, 1.0],    // 顶面: 蓝色
  119.     [1.0, 1.0, 0.0, 1.0],    // 底面: 黄色
  120.     [1.0, 0.0, 1.0, 1.0],    // 右面: 紫色
  121.     [0.0, 1.0, 1.0, 1.0],    // 左面: 青色
  122.   ];
  123.   
  124.   let colors = [];
  125.   for (let j = 0; j < faceColors.length; ++j) {
  126.     const c = faceColors[j];
  127.     colors = colors.concat(c, c, c, c);
  128.   }
  129.   
  130.   const colorBuffer = gl.createBuffer();
  131.   gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
  132.   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
  133.   
  134.   // 矩形索引
  135.   const indices = [
  136.     0,  1,  2,      0,  2,  3,    // 前面
  137.     4,  5,  6,      4,  6,  7,    // 后面
  138.     8,  9,  10,     8,  10, 11,   // 顶面
  139.     12, 13, 14,     12, 14, 15,   // 底面
  140.     16, 17, 18,     16, 18, 19,   // 右面
  141.     20, 21, 22,     20, 22, 23,   // 左面
  142.   ];
  143.   
  144.   const indexBuffer = gl.createBuffer();
  145.   gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  146.   gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
  147.   
  148.   return {
  149.     position: positionBuffer,
  150.     color: colorBuffer,
  151.     indices: indexBuffer,
  152.   };
  153. }
  154. const buffers = initBuffers(gl);
  155. // 绘制场景
  156. function drawScene(gl, programInfo, buffers, deltaTime) {
  157.   gl.clearColor(0.0, 0.0, 0.0, 1.0);
  158.   gl.clearDepth(1.0);
  159.   gl.enable(gl.DEPTH_TEST);
  160.   gl.depthFunc(gl.LEQUAL);
  161.   
  162.   gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  163.   
  164.   // 创建透视矩阵
  165.   const fieldOfView = 45 * Math.PI / 180;
  166.   const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  167.   const zNear = 0.1;
  168.   const zFar = 100.0;
  169.   const projectionMatrix = mat4.create();
  170.   
  171.   mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
  172.   
  173.   // 创建模型视图矩阵
  174.   const modelViewMatrix = mat4.create();
  175.   
  176.   mat4.translate(modelViewMatrix, modelViewMatrix, [-0.0, 0.0, -6.0]);
  177.   mat4.rotate(modelViewMatrix, modelViewMatrix, cubeRotation, [0, 0, 1]);
  178.   mat4.rotate(modelViewMatrix, modelViewMatrix, cubeRotation * .7, [0, 1, 0]);
  179.   
  180.   // 设置位置属性
  181.   {
  182.     const numComponents = 3;
  183.     const type = gl.FLOAT;
  184.     const normalize = false;
  185.     const stride = 0;
  186.     const offset = 0;
  187.     gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
  188.     gl.vertexAttribPointer(
  189.         programInfo.attribLocations.vertexPosition,
  190.         numComponents,
  191.         type,
  192.         normalize,
  193.         stride,
  194.         offset);
  195.     gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
  196.   }
  197.   
  198.   // 设置颜色属性
  199.   {
  200.     const numComponents = 4;
  201.     const type = gl.FLOAT;
  202.     const normalize = false;
  203.     const stride = 0;
  204.     const offset = 0;
  205.     gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color);
  206.     gl.vertexAttribPointer(
  207.         programInfo.attribLocations.vertexColor,
  208.         numComponents,
  209.         type,
  210.         normalize,
  211.         stride,
  212.         offset);
  213.     gl.enableVertexAttribArray(programInfo.attribLocations.vertexColor);
  214.   }
  215.   
  216.   gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices);
  217.   
  218.   gl.useProgram(programInfo.program);
  219.   
  220.   gl.uniformMatrix4fv(
  221.       programInfo.uniformLocations.projectionMatrix,
  222.       false,
  223.       projectionMatrix);
  224.   gl.uniformMatrix4fv(
  225.       programInfo.uniformLocations.modelViewMatrix,
  226.       false,
  227.       modelViewMatrix);
  228.   
  229.   {
  230.     const vertexCount = 36;
  231.     const type = gl.UNSIGNED_SHORT;
  232.     const offset = 0;
  233.     gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
  234.   }
  235.   
  236.   cubeRotation += deltaTime;
  237. }
  238. let cubeRotation = 0.0;
  239. let then = 0;
  240. // 渲染循环
  241. function render(now) {
  242.   now *= 0.001;
  243.   const deltaTime = now - then;
  244.   then = now;
  245.   
  246.   drawScene(gl, programInfo, buffers, deltaTime);
  247.   
  248.   requestAnimationFrame(render);
  249. }
  250. requestAnimationFrame(render);
复制代码

注意:上面的WebGL示例使用了gl-matrix库来进行矩阵运算。在实际使用中,你需要先引入这个库。

6. 性能优化和最佳实践

6.1 Canvas性能优化

当使用Canvas绘制大量矩形时,性能可能会成为一个问题。以下是一些优化技巧:

只重绘发生变化的部分,而不是整个画布:
  1. // 不好的做法:每次清除整个画布
  2. function animate() {
  3.   ctx.clearRect(0, 0, canvas.width, canvas.height);
  4.   drawAllRectangles();
  5.   requestAnimationFrame(animate);
  6. }
  7. // 好的做法:只清除需要重绘的部分
  8. function animate() {
  9.   // 只清除上一帧的矩形位置
  10.   ctx.clearRect(prevX, prevY, width, height);
  11.   
  12.   // 绘制新位置的矩形
  13.   drawRectangle(x, y, width, height);
  14.   
  15.   // 更新上一帧的位置
  16.   prevX = x;
  17.   prevY = y;
  18.   
  19.   requestAnimationFrame(animate);
  20. }
复制代码

对于复杂的静态内容,可以使用离屏Canvas进行预渲染:
  1. // 创建离屏Canvas
  2. const offscreenCanvas = document.createElement('canvas');
  3. const offscreenCtx = offscreenCanvas.getContext('2d');
  4. offscreenCanvas.width = 500;
  5. offscreenCanvas.height = 300;
  6. // 在离屏Canvas上绘制静态内容
  7. function drawStaticContent() {
  8.   offscreenCtx.fillStyle = '#ecf0f1';
  9.   offscreenCtx.fillRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
  10.   
  11.   // 绘制多个静态矩形
  12.   for (let i = 0; i < 50; i++) {
  13.     const x = Math.random() * offscreenCanvas.width;
  14.     const y = Math.random() * offscreenCanvas.height;
  15.     const width = 20 + Math.random() * 30;
  16.     const height = 20 + Math.random() * 30;
  17.     const color = `hsl(${Math.random() * 360}, 70%, 60%)`;
  18.    
  19.     offscreenCtx.fillStyle = color;
  20.     offscreenCtx.fillRect(x, y, width, height);
  21.   }
  22. }
  23. // 在主Canvas上绘制离屏Canvas的内容
  24. function drawScene() {
  25.   ctx.clearRect(0, 0, canvas.width, canvas.height);
  26.   
  27.   // 绘制离屏Canvas的内容
  28.   ctx.drawImage(offscreenCanvas, 0, 0);
  29.   
  30.   // 绘制动态内容
  31.   drawDynamicRectangle();
  32. }
  33. // 初始化
  34. drawStaticContent();
复制代码

尽量减少绘制调用的次数,批量处理相似的绘制操作:
  1. // 不好的做法:多次调用绘制方法
  2. function drawMultipleRectanglesBad(rectangles) {
  3.   rectangles.forEach(rect => {
  4.     ctx.fillStyle = rect.color;
  5.     ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
  6.   });
  7. }
  8. // 好的做法:批量处理相同颜色的矩形
  9. function drawMultipleRectanglesGood(rectangles) {
  10.   // 按颜色分组
  11.   const rectanglesByColor = {};
  12.   rectangles.forEach(rect => {
  13.     if (!rectanglesByColor[rect.color]) {
  14.       rectanglesByColor[rect.color] = [];
  15.     }
  16.     rectanglesByColor[rect.color].push(rect);
  17.   });
  18.   
  19.   // 批量绘制
  20.   Object.keys(rectanglesByColor).forEach(color => {
  21.     ctx.fillStyle = color;
  22.     rectanglesByColor[color].forEach(rect => {
  23.       ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
  24.     });
  25.   });
  26. }
复制代码

6.2 SVG性能优化

当需要显示大量矩形时,尽量减少SVG元素的数量:
  1. // 不好的做法:为每个矩形创建一个SVG元素
  2. function createManyRectanglesBad(count) {
  3.   const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  4.   svg.setAttribute('width', '500');
  5.   svg.setAttribute('height', '300');
  6.   
  7.   for (let i = 0; i < count; i++) {
  8.     const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  9.     rect.setAttribute('x', Math.random() * 450);
  10.     rect.setAttribute('y', Math.random() * 250);
  11.     rect.setAttribute('width', 20 + Math.random() * 30);
  12.     rect.setAttribute('height', 20 + Math.random() * 30);
  13.     rect.setAttribute('fill', `hsl(${Math.random() * 360}, 70%, 60%)`);
  14.     svg.appendChild(rect);
  15.   }
  16.   
  17.   return svg;
  18. }
  19. // 好的做法:使用单个path元素绘制多个矩形
  20. function createManyRectanglesGood(count) {
  21.   const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  22.   svg.setAttribute('width', '500');
  23.   svg.setAttribute('height', '300');
  24.   
  25.   const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  26.   let pathData = '';
  27.   
  28.   for (let i = 0; i < count; i++) {
  29.     const x = Math.random() * 450;
  30.     const y = Math.random() * 250;
  31.     const width = 20 + Math.random() * 30;
  32.     const height = 20 + Math.random() * 30;
  33.    
  34.     pathData += `M ${x} ${y} L ${x + width} ${y} L ${x + width} ${y + height} L ${x} ${y + height} Z `;
  35.   }
  36.   
  37.   path.setAttribute('d', pathData);
  38.   path.setAttribute('fill', 'none');
  39.   path.setAttribute('stroke', '#333');
  40.   path.setAttribute('stroke-width', '1');
  41.   
  42.   svg.appendChild(path);
  43.   return svg;
  44. }
复制代码

对于简单的动画,使用CSS动画比JavaScript动画性能更好:
  1. <svg width="500" height="300">
  2.   <rect class="animated-rect" x="50" y="50" width="100" height="100" fill="#3498db" />
  3. </svg>
复制代码
  1. .animated-rect {
  2.   animation: move 3s infinite alternate;
  3.   transform-origin: center;
  4. }
  5. @keyframes move {
  6.   0% {
  7.     transform: translateX(0) rotate(0deg);
  8.   }
  9.   100% {
  10.     transform: translateX(300px) rotate(180deg);
  11.   }
  12. }
复制代码

6.3 通用最佳实践

对于动画,使用requestAnimationFrame而不是setTimeout或setInterval:
  1. // 不好的做法
  2. function animateBad() {
  3.   updateRectanglePosition();
  4.   drawRectangle();
  5.   setTimeout(animateBad, 16); // 约60fps
  6. }
  7. // 好的做法
  8. function animateGood() {
  9.   updateRectanglePosition();
  10.   drawRectangle();
  11.   requestAnimationFrame(animateGood);
  12. }
  13. // 开始动画
  14. animateGood();
复制代码

在动画循环中,避免重复计算不变的值:
  1. // 不好的做法:每次循环都计算
  2. function animateBad() {
  3.   const centerX = canvas.width / 2;
  4.   const centerY = canvas.height / 2;
  5.   const radius = 100;
  6.   
  7.   const angle = Date.now() * 0.001;
  8.   const x = centerX + Math.cos(angle) * radius;
  9.   const y = centerY + Math.sin(angle) * radius;
  10.   
  11.   drawRectangle(x, y, 50, 50);
  12.   requestAnimationFrame(animateBad);
  13. }
  14. // 好的做法:预先计算不变的值
  15. function animateGood() {
  16.   // 这些值在循环外计算一次
  17.   const centerX = canvas.width / 2;
  18.   const centerY = canvas.height / 2;
  19.   const radius = 100;
  20.   
  21.   return function animate() {
  22.     const angle = Date.now() * 0.001;
  23.     const x = centerX + Math.cos(angle) * radius;
  24.     const y = centerY + Math.sin(angle) * radius;
  25.    
  26.     drawRectangle(x, y, 50, 50);
  27.     requestAnimationFrame(animate);
  28.   };
  29. }
  30. // 开始动画
  31. const animate = animateGood();
  32. animate();
复制代码

对于频繁创建和销毁的对象,使用对象池模式可以提高性能:
  1. // 矩形对象池
  2. const rectanglePool = {
  3.   objects: [],
  4.   new: function(x, y, width, height, color) {
  5.     if (this.objects.length > 0) {
  6.       const rect = this.objects.pop();
  7.       rect.x = x;
  8.       rect.y = y;
  9.       rect.width = width;
  10.       rect.height = height;
  11.       rect.color = color;
  12.       return rect;
  13.     } else {
  14.       return { x, y, width, height, color };
  15.     }
  16.   },
  17.   free: function(rect) {
  18.     this.objects.push(rect);
  19.   }
  20. };
  21. // 使用对象池
  22. function drawManyRectangles(count) {
  23.   const rectangles = [];
  24.   
  25.   // 创建矩形
  26.   for (let i = 0; i < count; i++) {
  27.     const rect = rectanglePool.new(
  28.       Math.random() * 450,
  29.       Math.random() * 250,
  30.       20 + Math.random() * 30,
  31.       20 + Math.random() * 30,
  32.       `hsl(${Math.random() * 360}, 70%, 60%)`
  33.     );
  34.     rectangles.push(rect);
  35.   }
  36.   
  37.   // 绘制矩形
  38.   rectangles.forEach(rect => {
  39.     ctx.fillStyle = rect.color;
  40.     ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
  41.   });
  42.   
  43.   // 释放矩形回对象池
  44.   rectangles.forEach(rect => {
  45.     rectanglePool.free(rect);
  46.   });
  47. }
复制代码

7. 实际应用案例

7.1 数据可视化:柱状图

矩形在数据可视化中非常常见,例如柱状图:
  1. <canvas id="chart-canvas" width="600" height="400"></canvas>
复制代码
  1. const canvas = document.getElementById('chart-canvas');
  2. const ctx = canvas.getContext('2d');
  3. // 数据
  4. const data = [
  5.   { label: 'A', value: 30 },
  6.   { label: 'B', value: 50 },
  7.   { label: 'C', value: 80 },
  8.   { label: 'D', value: 40 },
  9.   { label: 'E', value: 70 },
  10.   { label: 'F', value: 20 }
  11. ];
  12. // 图表配置
  13. const chartConfig = {
  14.   padding: 40,
  15.   barWidth: 60,
  16.   barSpacing: 20,
  17.   colors: ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c']
  18. };
  19. // 计算最大值
  20. const maxValue = Math.max(...data.map(item => item.value));
  21. // 绘制坐标轴
  22. function drawAxes() {
  23.   ctx.beginPath();
  24.   ctx.strokeStyle = '#333';
  25.   ctx.lineWidth = 2;
  26.   
  27.   // Y轴
  28.   ctx.moveTo(chartConfig.padding, chartConfig.padding);
  29.   ctx.lineTo(chartConfig.padding, canvas.height - chartConfig.padding);
  30.   
  31.   // X轴
  32.   ctx.moveTo(chartConfig.padding, canvas.height - chartConfig.padding);
  33.   ctx.lineTo(canvas.width - chartConfig.padding, canvas.height - chartConfig.padding);
  34.   
  35.   ctx.stroke();
  36.   
  37.   // 绘制Y轴刻度
  38.   const yAxisSteps = 5;
  39.   for (let i = 0; i <= yAxisSteps; i++) {
  40.     const y = canvas.height - chartConfig.padding - (i / yAxisSteps) * (canvas.height - 2 * chartConfig.padding);
  41.     const value = Math.round((i / yAxisSteps) * maxValue);
  42.    
  43.     ctx.beginPath();
  44.     ctx.moveTo(chartConfig.padding - 5, y);
  45.     ctx.lineTo(chartConfig.padding, y);
  46.     ctx.stroke();
  47.    
  48.     ctx.fillStyle = '#333';
  49.     ctx.font = '12px Arial';
  50.     ctx.textAlign = 'right';
  51.     ctx.textBaseline = 'middle';
  52.     ctx.fillText(value, chartConfig.padding - 10, y);
  53.   }
  54. }
  55. // 绘制柱状图
  56. function drawBarChart() {
  57.   // 清除画布
  58.   ctx.clearRect(0, 0, canvas.width, canvas.height);
  59.   
  60.   // 绘制坐标轴
  61.   drawAxes();
  62.   
  63.   // 计算图表高度
  64.   const chartHeight = canvas.height - 2 * chartConfig.padding;
  65.   
  66.   // 绘制柱子
  67.   data.forEach((item, index) => {
  68.     const x = chartConfig.padding + index * (chartConfig.barWidth + chartConfig.barSpacing) + chartConfig.barSpacing;
  69.     const barHeight = (item.value / maxValue) * chartHeight;
  70.     const y = canvas.height - chartConfig.padding - barHeight;
  71.    
  72.     // 绘制柱子
  73.     ctx.fillStyle = chartConfig.colors[index % chartConfig.colors.length];
  74.     ctx.fillRect(x, y, chartConfig.barWidth, barHeight);
  75.    
  76.     // 绘制标签
  77.     ctx.fillStyle = '#333';
  78.     ctx.font = '14px Arial';
  79.     ctx.textAlign = 'center';
  80.     ctx.textBaseline = 'top';
  81.     ctx.fillText(item.label, x + chartConfig.barWidth / 2, canvas.height - chartConfig.padding + 10);
  82.    
  83.     // 绘制数值
  84.     ctx.textBaseline = 'bottom';
  85.     ctx.fillText(item.value, x + chartConfig.barWidth / 2, y);
  86.   });
  87. }
  88. // 初始绘制
  89. drawBarChart();
  90. // 添加动画效果
  91. let animationProgress = 0;
  92. const animationDuration = 1000; // 1秒
  93. const startTime = Date.now();
  94. function animateChart() {
  95.   const currentTime = Date.now();
  96.   animationProgress = Math.min((currentTime - startTime) / animationDuration, 1);
  97.   
  98.   // 清除画布
  99.   ctx.clearRect(0, 0, canvas.width, canvas.height);
  100.   
  101.   // 绘制坐标轴
  102.   drawAxes();
  103.   
  104.   // 计算图表高度
  105.   const chartHeight = canvas.height - 2 * chartConfig.padding;
  106.   
  107.   // 绘制柱子(带动画)
  108.   data.forEach((item, index) => {
  109.     const x = chartConfig.padding + index * (chartConfig.barWidth + chartConfig.barSpacing) + chartConfig.barSpacing;
  110.     const targetHeight = (item.value / maxValue) * chartHeight;
  111.     const barHeight = targetHeight * animationProgress;
  112.     const y = canvas.height - chartConfig.padding - barHeight;
  113.    
  114.     // 绘制柱子
  115.     ctx.fillStyle = chartConfig.colors[index % chartConfig.colors.length];
  116.     ctx.fillRect(x, y, chartConfig.barWidth, barHeight);
  117.    
  118.     // 绘制标签
  119.     ctx.fillStyle = '#333';
  120.     ctx.font = '14px Arial';
  121.     ctx.textAlign = 'center';
  122.     ctx.textBaseline = 'top';
  123.     ctx.fillText(item.label, x + chartConfig.barWidth / 2, canvas.height - chartConfig.padding + 10);
  124.    
  125.     // 绘制数值(只在动画完成后显示)
  126.     if (animationProgress === 1) {
  127.       ctx.textBaseline = 'bottom';
  128.       ctx.fillText(item.value, x + chartConfig.barWidth / 2, y);
  129.     }
  130.   });
  131.   
  132.   // 继续动画
  133.   if (animationProgress < 1) {
  134.     requestAnimationFrame(animateChart);
  135.   }
  136. }
  137. // 开始动画
  138. animateChart();
复制代码

7.2 游戏开发:简单的平台游戏

矩形在游戏开发中非常常见,例如平台游戏中的角色、平台和障碍物:
  1. <canvas id="game-canvas" width="800" height="400"></canvas>
复制代码
  1. const canvas = document.getElementById('game-canvas');
  2. const ctx = canvas.getContext('2d');
  3. // 游戏状态
  4. const game = {
  5.   gravity: 0.5,
  6.   friction: 0.8,
  7.   isRunning: true
  8. };
  9. // 玩家对象
  10. const player = {
  11.   x: 100,
  12.   y: 200,
  13.   width: 30,
  14.   height: 50,
  15.   velocityX: 0,
  16.   velocityY: 0,
  17.   speed: 5,
  18.   jumpPower: 12,
  19.   color: '#3498db',
  20.   isJumping: false
  21. };
  22. // 平台数组
  23. const platforms = [
  24.   { x: 0, y: 350, width: 800, height: 50, color: '#2c3e50' },
  25.   { x: 200, y: 280, width: 100, height: 20, color: '#2c3e50' },
  26.   { x: 400, y: 220, width: 100, height: 20, color: '#2c3e50' },
  27.   { x: 600, y: 160, width: 100, height: 20, color: '#2c3e50' }
  28. ];
  29. // 障碍物数组
  30. const obstacles = [
  31.   { x: 300, y: 320, width: 30, height: 30, color: '#e74c3c' },
  32.   { x: 500, y: 190, width: 30, height: 30, color: '#e74c3c' }
  33. ];
  34. // 收集品数组
  35. const collectibles = [
  36.   { x: 250, y: 250, width: 20, height: 20, color: '#f1c40f', collected: false },
  37.   { x: 450, y: 190, width: 20, height: 20, color: '#f1c40f', collected: false },
  38.   { x: 650, y: 130, width: 20, height: 20, color: '#f1c40f', collected: false }
  39. ];
  40. // 键盘输入
  41. const keys = {};
  42. document.addEventListener('keydown', (e) => {
  43.   keys[e.key] = true;
  44. });
  45. document.addEventListener('keyup', (e) => {
  46.   keys[e.key] = false;
  47. });
  48. // 更新玩家状态
  49. function updatePlayer() {
  50.   // 左右移动
  51.   if (keys['ArrowLeft'] || keys['a']) {
  52.     player.velocityX = -player.speed;
  53.   } else if (keys['ArrowRight'] || keys['d']) {
  54.     player.velocityX = player.speed;
  55.   } else {
  56.     player.velocityX *= game.friction;
  57.   }
  58.   
  59.   // 跳跃
  60.   if ((keys['ArrowUp'] || keys['w'] || keys[' ']) && !player.isJumping) {
  61.     player.velocityY = -player.jumpPower;
  62.     player.isJumping = true;
  63.   }
  64.   
  65.   // 应用重力
  66.   player.velocityY += game.gravity;
  67.   
  68.   // 更新位置
  69.   player.x += player.velocityX;
  70.   player.y += player.velocityY;
  71.   
  72.   // 边界检查
  73.   if (player.x < 0) {
  74.     player.x = 0;
  75.     player.velocityX = 0;
  76.   } else if (player.x + player.width > canvas.width) {
  77.     player.x = canvas.width - player.width;
  78.     player.velocityX = 0;
  79.   }
  80.   
  81.   // 检查是否掉出屏幕
  82.   if (player.y > canvas.height) {
  83.     resetPlayer();
  84.   }
  85. }
  86. // 重置玩家位置
  87. function resetPlayer() {
  88.   player.x = 100;
  89.   player.y = 200;
  90.   player.velocityX = 0;
  91.   player.velocityY = 0;
  92. }
  93. // 碰撞检测
  94. function checkCollisions() {
  95.   // 平台碰撞
  96.   player.isJumping = true;
  97.   platforms.forEach(platform => {
  98.     if (
  99.       player.x < platform.x + platform.width &&
  100.       player.x + player.width > platform.x &&
  101.       player.y < platform.y + platform.height &&
  102.       player.y + player.height > platform.y
  103.     ) {
  104.       // 从上方碰撞
  105.       if (player.velocityY > 0 && player.y < platform.y) {
  106.         player.y = platform.y - player.height;
  107.         player.velocityY = 0;
  108.         player.isJumping = false;
  109.       }
  110.       // 从下方碰撞
  111.       else if (player.velocityY < 0 && player.y > platform.y) {
  112.         player.y = platform.y + platform.height;
  113.         player.velocityY = 0;
  114.       }
  115.       // 从左侧碰撞
  116.       else if (player.velocityX > 0 && player.x < platform.x) {
  117.         player.x = platform.x - player.width;
  118.         player.velocityX = 0;
  119.       }
  120.       // 从右侧碰撞
  121.       else if (player.velocityX < 0 && player.x > platform.x) {
  122.         player.x = platform.x + platform.width;
  123.         player.velocityX = 0;
  124.       }
  125.     }
  126.   });
  127.   
  128.   // 障碍物碰撞
  129.   obstacles.forEach(obstacle => {
  130.     if (
  131.       player.x < obstacle.x + obstacle.width &&
  132.       player.x + player.width > obstacle.x &&
  133.       player.y < obstacle.y + obstacle.height &&
  134.       player.y + player.height > obstacle.y
  135.     ) {
  136.       resetPlayer();
  137.     }
  138.   });
  139.   
  140.   // 收集品碰撞
  141.   collectibles.forEach(collectible => {
  142.     if (
  143.       !collectible.collected &&
  144.       player.x < collectible.x + collectible.width &&
  145.       player.x + player.width > collectible.x &&
  146.       player.y < collectible.y + collectible.height &&
  147.       player.y + player.height > collectible.y
  148.     ) {
  149.       collectible.collected = true;
  150.     }
  151.   });
  152. }
  153. // 绘制游戏
  154. function drawGame() {
  155.   // 清除画布
  156.   ctx.clearRect(0, 0, canvas.width, canvas.height);
  157.   
  158.   // 绘制背景
  159.   ctx.fillStyle = '#ecf0f1';
  160.   ctx.fillRect(0, 0, canvas.width, canvas.height);
  161.   
  162.   // 绘制平台
  163.   platforms.forEach(platform => {
  164.     ctx.fillStyle = platform.color;
  165.     ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
  166.   });
  167.   
  168.   // 绘制障碍物
  169.   obstacles.forEach(obstacle => {
  170.     ctx.fillStyle = obstacle.color;
  171.     ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
  172.   });
  173.   
  174.   // 绘制收集品
  175.   collectibles.forEach(collectible => {
  176.     if (!collectible.collected) {
  177.       ctx.fillStyle = collectible.color;
  178.       ctx.fillRect(collectible.x, collectible.y, collectible.width, collectible.height);
  179.     }
  180.   });
  181.   
  182.   // 绘制玩家
  183.   ctx.fillStyle = player.color;
  184.   ctx.fillRect(player.x, player.y, player.width, player.height);
  185.   
  186.   // 绘制分数
  187.   const collectedCount = collectibles.filter(c => c.collected).length;
  188.   ctx.fillStyle = '#333';
  189.   ctx.font = '20px Arial';
  190.   ctx.textAlign = 'left';
  191.   ctx.fillText(`收集品: ${collectedCount}/${collectibles.length}`, 20, 30);
  192.   
  193.   // 检查胜利条件
  194.   if (collectedCount === collectibles.length) {
  195.     ctx.fillStyle = '#2ecc71';
  196.     ctx.font = '40px Arial';
  197.     ctx.textAlign = 'center';
  198.     ctx.fillText('你赢了!', canvas.width / 2, canvas.height / 2);
  199.     game.isRunning = false;
  200.   }
  201. }
  202. // 游戏循环
  203. function gameLoop() {
  204.   if (game.isRunning) {
  205.     updatePlayer();
  206.     checkCollisions();
  207.   }
  208.   
  209.   drawGame();
  210.   requestAnimationFrame(gameLoop);
  211. }
  212. // 开始游戏
  213. gameLoop();
复制代码

7.3 图像处理:使用矩形选择区域

矩形在图像处理中也很有用,例如选择图像的特定区域:
  1. <div>
  2.   <canvas id="image-canvas" width="600" height="400"></canvas>
  3.   <canvas id="selection-canvas" width="200" height="200"></canvas>
  4. </div>
复制代码
  1. const imageCanvas = document.getElementById('image-canvas');
  2. const imageCtx = imageCanvas.getContext('2d');
  3. const selectionCanvas = document.getElementById('selection-canvas');
  4. const selectionCtx = selectionCanvas.getContext('2d');
  5. // 加载图像
  6. const img = new Image();
  7. img.crossOrigin = 'Anonymous';
  8. img.onload = function() {
  9.   // 绘制图像到画布
  10.   imageCtx.drawImage(img, 0, 0, imageCanvas.width, imageCanvas.height);
  11. };
  12. img.src = 'https://picsum.photos/seed/rectangle-demo/600/400.jpg';
  13. // 选择区域
  14. const selection = {
  15.   x: 100,
  16.   y: 100,
  17.   width: 200,
  18.   height: 150,
  19.   isDragging: false,
  20.   isResizing: false,
  21.   dragStartX: 0,
  22.   dragStartY: 0,
  23.   resizeHandle: null
  24. };
  25. // 绘制选择矩形
  26. function drawSelection() {
  27.   // 清除画布并重新绘制图像
  28.   imageCtx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);
  29.   imageCtx.drawImage(img, 0, 0, imageCanvas.width, imageCanvas.height);
  30.   
  31.   // 绘制半透明覆盖层
  32.   imageCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
  33.   imageCtx.fillRect(0, 0, imageCanvas.width, imageCanvas.height);
  34.   
  35.   // 清除选择区域
  36.   imageCtx.clearRect(selection.x, selection.y, selection.width, selection.height);
  37.   imageCtx.drawImage(
  38.     img,
  39.     selection.x * (img.width / imageCanvas.width),
  40.     selection.y * (img.height / imageCanvas.height),
  41.     selection.width * (img.width / imageCanvas.width),
  42.     selection.height * (img.height / imageCanvas.height),
  43.     selection.x,
  44.     selection.y,
  45.     selection.width,
  46.     selection.height
  47.   );
  48.   
  49.   // 绘制选择边框
  50.   imageCtx.strokeStyle = '#3498db';
  51.   imageCtx.lineWidth = 2;
  52.   imageCtx.strokeRect(selection.x, selection.y, selection.width, selection.height);
  53.   
  54.   // 绘制调整大小的手柄
  55.   const handleSize = 8;
  56.   imageCtx.fillStyle = '#3498db';
  57.   
  58.   // 四个角的手柄
  59.   imageCtx.fillRect(selection.x - handleSize/2, selection.y - handleSize/2, handleSize, handleSize); // 左上
  60.   imageCtx.fillRect(selection.x + selection.width - handleSize/2, selection.y - handleSize/2, handleSize, handleSize); // 右上
  61.   imageCtx.fillRect(selection.x - handleSize/2, selection.y + selection.height - handleSize/2, handleSize, handleSize); // 左下
  62.   imageCtx.fillRect(selection.x + selection.width - handleSize/2, selection.y + selection.height - handleSize/2, handleSize, handleSize); // 右下
  63.   
  64.   // 更新选择区域的图像
  65.   updateSelectionImage();
  66. }
  67. // 更新选择区域的图像
  68. function updateSelectionImage() {
  69.   selectionCtx.clearRect(0, 0, selectionCanvas.width, selectionCanvas.height);
  70.   selectionCtx.drawImage(
  71.     imageCanvas,
  72.     selection.x, selection.y, selection.width, selection.height,
  73.     0, 0, selectionCanvas.width, selectionCanvas.height
  74.   );
  75. }
  76. // 检查点是否在选择矩形内
  77. function isPointInSelection(x, y) {
  78.   return x >= selection.x &&
  79.          x <= selection.x + selection.width &&
  80.          y >= selection.y &&
  81.          y <= selection.y + selection.height;
  82. }
  83. // 检查点是否在调整大小的手柄上
  84. function getResizeHandle(x, y) {
  85.   const handleSize = 8;
  86.   const tolerance = handleSize;
  87.   
  88.   // 左上角
  89.   if (Math.abs(x - selection.x) < tolerance && Math.abs(y - selection.y) < tolerance) {
  90.     return 'nw';
  91.   }
  92.   // 右上角
  93.   if (Math.abs(x - (selection.x + selection.width)) < tolerance && Math.abs(y - selection.y) < tolerance) {
  94.     return 'ne';
  95.   }
  96.   // 左下角
  97.   if (Math.abs(x - selection.x) < tolerance && Math.abs(y - (selection.y + selection.height)) < tolerance) {
  98.     return 'sw';
  99.   }
  100.   // 右下角
  101.   if (Math.abs(x - (selection.x + selection.width)) < tolerance && Math.abs(y - (selection.y + selection.height)) < tolerance) {
  102.     return 'se';
  103.   }
  104.   
  105.   return null;
  106. }
  107. // 鼠标事件处理
  108. imageCanvas.addEventListener('mousedown', (e) => {
  109.   const rect = imageCanvas.getBoundingClientRect();
  110.   const x = e.clientX - rect.left;
  111.   const y = e.clientY - rect.top;
  112.   
  113.   const handle = getResizeHandle(x, y);
  114.   if (handle) {
  115.     selection.isResizing = true;
  116.     selection.resizeHandle = handle;
  117.     selection.dragStartX = x;
  118.     selection.dragStartY = y;
  119.   } else if (isPointInSelection(x, y)) {
  120.     selection.isDragging = true;
  121.     selection.dragStartX = x - selection.x;
  122.     selection.dragStartY = y - selection.y;
  123.     imageCanvas.style.cursor = 'move';
  124.   }
  125. });
  126. imageCanvas.addEventListener('mousemove', (e) => {
  127.   const rect = imageCanvas.getBoundingClientRect();
  128.   const x = e.clientX - rect.left;
  129.   const y = e.clientY - rect.top;
  130.   
  131.   if (selection.isResizing) {
  132.     const dx = x - selection.dragStartX;
  133.     const dy = y - selection.dragStartY;
  134.    
  135.     switch (selection.resizeHandle) {
  136.       case 'nw':
  137.         selection.x += dx;
  138.         selection.y += dy;
  139.         selection.width -= dx;
  140.         selection.height -= dy;
  141.         break;
  142.       case 'ne':
  143.         selection.y += dy;
  144.         selection.width += dx;
  145.         selection.height -= dy;
  146.         break;
  147.       case 'sw':
  148.         selection.x += dx;
  149.         selection.width -= dx;
  150.         selection.height += dy;
  151.         break;
  152.       case 'se':
  153.         selection.width += dx;
  154.         selection.height += dy;
  155.         break;
  156.     }
  157.    
  158.     // 确保最小尺寸
  159.     if (selection.width < 20) selection.width = 20;
  160.     if (selection.height < 20) selection.height = 20;
  161.    
  162.     selection.dragStartX = x;
  163.     selection.dragStartY = y;
  164.    
  165.     drawSelection();
  166.   } else if (selection.isDragging) {
  167.     selection.x = x - selection.dragStartX;
  168.     selection.y = y - selection.dragStartY;
  169.    
  170.     // 确保不超出边界
  171.     if (selection.x < 0) selection.x = 0;
  172.     if (selection.y < 0) selection.y = 0;
  173.     if (selection.x + selection.width > imageCanvas.width) selection.x = imageCanvas.width - selection.width;
  174.     if (selection.y + selection.height > imageCanvas.height) selection.y = imageCanvas.height - selection.height;
  175.    
  176.     drawSelection();
  177.   } else {
  178.     // 更新鼠标样式
  179.     const handle = getResizeHandle(x, y);
  180.     if (handle) {
  181.       switch (handle) {
  182.         case 'nw':
  183.         case 'se':
  184.           imageCanvas.style.cursor = 'nw-resize';
  185.           break;
  186.         case 'ne':
  187.         case 'sw':
  188.           imageCanvas.style.cursor = 'ne-resize';
  189.           break;
  190.       }
  191.     } else if (isPointInSelection(x, y)) {
  192.       imageCanvas.style.cursor = 'move';
  193.     } else {
  194.       imageCanvas.style.cursor = 'default';
  195.     }
  196.   }
  197. });
  198. imageCanvas.addEventListener('mouseup', () => {
  199.   selection.isDragging = false;
  200.   selection.isResizing = false;
  201.   selection.resizeHandle = null;
  202.   imageCanvas.style.cursor = 'default';
  203. });
  204. // 初始绘制
  205. img.onload = function() {
  206.   imageCtx.drawImage(img, 0, 0, imageCanvas.width, imageCanvas.height);
  207.   drawSelection();
  208. };
复制代码

8. 总结

本文详细介绍了使用JavaScript输出矩形的多种方法,从基础的HTML/CSS方法到高级的Canvas、SVG和WebGL技术。我们探讨了如何使用原生API以及各种JavaScript库来创建静态和动态矩形,并提供了丰富的代码示例和实际应用案例。

通过学习这些技术,你可以:

1. 使用HTML/CSS创建简单的矩形元素
2. 使用Canvas API绘制复杂的矩形图形和动画
3. 使用SVG创建可缩放的矢量矩形
4. 利用JavaScript库简化矩形绘制过程
5. 创建交互式矩形和动画效果
6. 优化矩形绘制的性能
7. 将矩形技术应用于数据可视化、游戏开发和图像处理等领域

掌握这些技能将大大提升你的前端开发能力,使你能够创建更加丰富和交互性强的Web应用。无论是简单的界面元素还是复杂的图形应用,矩形都是一个基础且重要的组成部分。

希望本文能够帮助你全面了解JavaScript输出矩形的各种方法和技巧,并在你的项目中灵活应用这些技术。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则