1. 矩形绘制

1.1 基础矩形方法

Canvas提供了三个基础的矩形绘制方法:

// 获取canvas和上下文
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// 1. fillRect() - 绘制填充矩形
ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 100, 80); // x, y, width, height

// 2. strokeRect() - 绘制矩形边框
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.strokeRect(200, 50, 100, 80);

// 3. clearRect() - 清除矩形区域
ctx.clearRect(75, 75, 50, 30);

1.2 矩形样式设置

// 填充样式
ctx.fillStyle = '#ff6b6b';        // 十六进制颜色
ctx.fillStyle = 'rgb(255, 107, 107)'; // RGB颜色
ctx.fillStyle = 'rgba(255, 107, 107, 0.5)'; // RGBA颜色
ctx.fillStyle = 'hsl(0, 100%, 71%)'; // HSL颜色

// 描边样式
ctx.strokeStyle = '#4ecdc4';
ctx.lineWidth = 3;
ctx.lineCap = 'round';    // 线条端点样式: butt, round, square
ctx.lineJoin = 'round';   // 线条连接样式: miter, round, bevel

// 虚线样式
ctx.setLineDash([5, 10]); // 设置虚线模式
ctx.lineDashOffset = 0;   // 虚线偏移

1.3 渐变填充矩形

// 线性渐变
function drawLinearGradientRect() {
    const gradient = ctx.createLinearGradient(0, 0, 200, 0);
    gradient.addColorStop(0, '#ff6b6b');
    gradient.addColorStop(0.5, '#4ecdc4');
    gradient.addColorStop(1, '#45b7d1');
    
    ctx.fillStyle = gradient;
    ctx.fillRect(50, 150, 200, 100);
}

// 径向渐变
function drawRadialGradientRect() {
    const gradient = ctx.createRadialGradient(150, 200, 0, 150, 200, 100);
    gradient.addColorStop(0, '#ff6b6b');
    gradient.addColorStop(1, '#4ecdc4');
    
    ctx.fillStyle = gradient;
    ctx.fillRect(100, 150, 100, 100);
}

drawLinearGradientRect();
drawRadialGradientRect();

2. 圆形与弧形绘制

2.1 基础圆形绘制

// arc() 方法绘制圆弧
// arc(x, y, radius, startAngle, endAngle, anticlockwise)

// 完整圆形
function drawCircle(x, y, radius, fillColor, strokeColor) {
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, Math.PI * 2);
    
    if (fillColor) {
        ctx.fillStyle = fillColor;
        ctx.fill();
    }
    
    if (strokeColor) {
        ctx.strokeStyle = strokeColor;
        ctx.stroke();
    }
}

// 绘制不同样式的圆形
drawCircle(100, 100, 40, 'red', null);           // 填充圆
drawCircle(250, 100, 40, null, 'blue');         // 描边圆
drawCircle(400, 100, 40, 'green', 'purple');    // 填充+描边圆

2.2 弧形与扇形

// 绘制弧形
function drawArc() {
    ctx.beginPath();
    ctx.arc(150, 250, 50, 0, Math.PI); // 半圆
    ctx.strokeStyle = 'orange';
    ctx.lineWidth = 3;
    ctx.stroke();
}

// 绘制扇形
function drawSector() {
    ctx.beginPath();
    ctx.moveTo(300, 250); // 移动到圆心
    ctx.arc(300, 250, 50, 0, Math.PI / 2); // 四分之一圆
    ctx.closePath();
    ctx.fillStyle = 'lightblue';
    ctx.fill();
    ctx.strokeStyle = 'darkblue';
    ctx.stroke();
}

// 绘制圆环
function drawRing() {
    // 外圆
    ctx.beginPath();
    ctx.arc(450, 250, 50, 0, Math.PI * 2);
    ctx.fillStyle = 'yellow';
    ctx.fill();
    
    // 内圆(使用globalCompositeOperation)
    ctx.globalCompositeOperation = 'destination-out';
    ctx.beginPath();
    ctx.arc(450, 250, 30, 0, Math.PI * 2);
    ctx.fill();
    ctx.globalCompositeOperation = 'source-over'; // 恢复默认
}

drawArc();
drawSector();
drawRing();

2.3 椭圆绘制

// ellipse() 方法绘制椭圆
// ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)

function drawEllipse() {
    ctx.beginPath();
    ctx.ellipse(200, 400, 80, 40, 0, 0, Math.PI * 2);
    ctx.fillStyle = 'lightgreen';
    ctx.fill();
    ctx.strokeStyle = 'darkgreen';
    ctx.stroke();
}

// 旋转的椭圆
function drawRotatedEllipse() {
    ctx.beginPath();
    ctx.ellipse(400, 400, 80, 40, Math.PI / 4, 0, Math.PI * 2);
    ctx.fillStyle = 'lightcoral';
    ctx.fill();
}

drawEllipse();
drawRotatedEllipse();

3. 线条绘制

3.1 基础线条

// 基础直线
function drawBasicLines() {
    // 简单直线
    ctx.beginPath();
    ctx.moveTo(50, 50);
    ctx.lineTo(200, 100);
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 2;
    ctx.stroke();
    
    // 连续线条
    ctx.beginPath();
    ctx.moveTo(50, 120);
    ctx.lineTo(100, 150);
    ctx.lineTo(150, 120);
    ctx.lineTo(200, 150);
    ctx.strokeStyle = 'red';
    ctx.lineWidth = 3;
    ctx.stroke();
}

drawBasicLines();

3.2 线条样式

// 不同线条样式
function drawLineStyles() {
    const styles = [
        { cap: 'butt', join: 'miter', dash: [] },
        { cap: 'round', join: 'round', dash: [] },
        { cap: 'square', join: 'bevel', dash: [] },
        { cap: 'round', join: 'round', dash: [5, 5] },
        { cap: 'round', join: 'round', dash: [10, 5, 5, 5] }
    ];
    
    styles.forEach((style, index) => {
        const y = 200 + index * 40;
        
        ctx.beginPath();
        ctx.moveTo(50, y);
        ctx.lineTo(150, y);
        ctx.lineTo(200, y - 20);
        ctx.lineTo(250, y);
        
        ctx.lineCap = style.cap;
        ctx.lineJoin = style.join;
        ctx.setLineDash(style.dash);
        ctx.lineWidth = 8;
        ctx.strokeStyle = `hsl(${index * 60}, 70%, 50%)`;
        ctx.stroke();
    });
    
    // 重置虚线
    ctx.setLineDash([]);
}

drawLineStyles();

3.3 曲线绘制

// 二次贝塞尔曲线
function drawQuadraticCurve() {
    ctx.beginPath();
    ctx.moveTo(300, 200);
    ctx.quadraticCurveTo(350, 150, 400, 200); // 控制点, 结束点
    ctx.strokeStyle = 'blue';
    ctx.lineWidth = 3;
    ctx.stroke();
    
    // 绘制控制点(辅助线)
    ctx.beginPath();
    ctx.moveTo(300, 200);
    ctx.lineTo(350, 150);
    ctx.lineTo(400, 200);
    ctx.strokeStyle = 'rgba(255, 0, 0, 0.3)';
    ctx.lineWidth = 1;
    ctx.stroke();
    
    // 标记控制点
    ctx.fillStyle = 'red';
    ctx.fillRect(348, 148, 4, 4);
}

// 三次贝塞尔曲线
function drawCubicCurve() {
    ctx.beginPath();
    ctx.moveTo(300, 280);
    ctx.bezierCurveTo(320, 230, 380, 230, 400, 280); // 控制点1, 控制点2, 结束点
    ctx.strokeStyle = 'green';
    ctx.lineWidth = 3;
    ctx.stroke();
    
    // 绘制控制点(辅助线)
    ctx.beginPath();
    ctx.moveTo(300, 280);
    ctx.lineTo(320, 230);
    ctx.moveTo(400, 280);
    ctx.lineTo(380, 230);
    ctx.strokeStyle = 'rgba(255, 0, 0, 0.3)';
    ctx.lineWidth = 1;
    ctx.stroke();
    
    // 标记控制点
    ctx.fillStyle = 'red';
    ctx.fillRect(318, 228, 4, 4);
    ctx.fillRect(378, 228, 4, 4);
}

drawQuadraticCurve();
drawCubicCurve();

4. 路径系统

4.1 路径基础

// 路径的基本概念
function pathBasics() {
    // 开始新路径
    ctx.beginPath();
    
    // 移动画笔(不绘制)
    ctx.moveTo(50, 350);
    
    // 绘制线条
    ctx.lineTo(100, 350);
    ctx.lineTo(75, 320);
    
    // 闭合路径
    ctx.closePath();
    
    // 填充和描边
    ctx.fillStyle = 'yellow';
    ctx.fill();
    ctx.strokeStyle = 'orange';
    ctx.lineWidth = 2;
    ctx.stroke();
}

pathBasics();

4.2 复杂路径

// 组合路径绘制星形
function drawStar(x, y, outerRadius, innerRadius, points) {
    ctx.beginPath();
    
    for (let i = 0; i < points * 2; i++) {
        const angle = (i * Math.PI) / points;
        const radius = i % 2 === 0 ? outerRadius : innerRadius;
        const px = x + Math.cos(angle) * radius;
        const py = y + Math.sin(angle) * radius;
        
        if (i === 0) {
            ctx.moveTo(px, py);
        } else {
            ctx.lineTo(px, py);
        }
    }
    
    ctx.closePath();
    ctx.fillStyle = 'gold';
    ctx.fill();
    ctx.strokeStyle = 'darkorange';
    ctx.lineWidth = 2;
    ctx.stroke();
}

// 绘制多边形
function drawPolygon(x, y, radius, sides) {
    ctx.beginPath();
    
    for (let i = 0; i < sides; i++) {
        const angle = (i * 2 * Math.PI) / sides;
        const px = x + Math.cos(angle) * radius;
        const py = y + Math.sin(angle) * radius;
        
        if (i === 0) {
            ctx.moveTo(px, py);
        } else {
            ctx.lineTo(px, py);
        }
    }
    
    ctx.closePath();
    ctx.fillStyle = 'lightblue';
    ctx.fill();
    ctx.strokeStyle = 'darkblue';
    ctx.stroke();
}

// 绘制示例
drawStar(200, 380, 30, 15, 5);     // 五角星
drawPolygon(300, 380, 25, 6);      // 六边形
drawPolygon(400, 380, 25, 8);      // 八边形

4.3 路径检测

// 点击检测
function setupPathDetection() {
    // 创建一个圆形路径
    const circleX = 500;
    const circleY = 380;
    const circleRadius = 30;
    
    ctx.beginPath();
    ctx.arc(circleX, circleY, circleRadius, 0, Math.PI * 2);
    ctx.fillStyle = 'lightgreen';
    ctx.fill();
    
    // 添加点击事件
    canvas.addEventListener('click', (e) => {
        const rect = canvas.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;
        
        // 重新创建路径进行检测
        ctx.beginPath();
        ctx.arc(circleX, circleY, circleRadius, 0, Math.PI * 2);
        
        // 检测点是否在路径内
        if (ctx.isPointInPath(x, y)) {
            console.log('点击了圆形!');
            // 改变颜色
            ctx.fillStyle = 'red';
            ctx.fill();
        }
    });
}

setupPathDetection();

5. 图案与纹理

5.1 图案填充

// 创建图案
function createPatterns() {
    // 创建一个小的canvas作为图案
    const patternCanvas = document.createElement('canvas');
    patternCanvas.width = 20;
    patternCanvas.height = 20;
    const pCtx = patternCanvas.getContext('2d');
    
    // 绘制图案
    pCtx.fillStyle = '#ff6b6b';
    pCtx.fillRect(0, 0, 10, 10);
    pCtx.fillStyle = '#4ecdc4';
    pCtx.fillRect(10, 10, 10, 10);
    
    // 创建图案
    const pattern = ctx.createPattern(patternCanvas, 'repeat');
    
    // 使用图案填充
    ctx.fillStyle = pattern;
    ctx.fillRect(50, 450, 150, 100);
}

createPatterns();

5.2 图像图案

// 使用图像创建图案
function createImagePattern() {
    const img = new Image();
    img.onload = function() {
        const pattern = ctx.createPattern(img, 'repeat');
        ctx.fillStyle = pattern;
        ctx.fillRect(250, 450, 150, 100);
    };
    // 这里需要一个实际的图像URL
    // img.src = 'pattern.png';
}

// createImagePattern();

6. 阴影效果

6.1 基础阴影

// 阴影设置
function drawWithShadow() {
    // 设置阴影属性
    ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
    ctx.shadowBlur = 10;
    ctx.shadowOffsetX = 5;
    ctx.shadowOffsetY = 5;
    
    // 绘制带阴影的矩形
    ctx.fillStyle = 'blue';
    ctx.fillRect(450, 450, 100, 80);
    
    // 清除阴影设置
    ctx.shadowColor = 'transparent';
    ctx.shadowBlur = 0;
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 0;
}

drawWithShadow();

7. 实用绘图函数

7.1 绘图工具函数

// 绘图工具类
class DrawingUtils {
    constructor(ctx) {
        this.ctx = ctx;
    }
    
    // 绘制圆角矩形
    roundRect(x, y, width, height, radius) {
        this.ctx.beginPath();
        this.ctx.moveTo(x + radius, y);
        this.ctx.lineTo(x + width - radius, y);
        this.ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
        this.ctx.lineTo(x + width, y + height - radius);
        this.ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
        this.ctx.lineTo(x + radius, y + height);
        this.ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
        this.ctx.lineTo(x, y + radius);
        this.ctx.quadraticCurveTo(x, y, x + radius, y);
        this.ctx.closePath();
    }
    
    // 绘制箭头
    arrow(fromX, fromY, toX, toY, headSize = 10) {
        const angle = Math.atan2(toY - fromY, toX - fromX);
        
        // 绘制线条
        this.ctx.beginPath();
        this.ctx.moveTo(fromX, fromY);
        this.ctx.lineTo(toX, toY);
        this.ctx.stroke();
        
        // 绘制箭头头部
        this.ctx.beginPath();
        this.ctx.moveTo(toX, toY);
        this.ctx.lineTo(
            toX - headSize * Math.cos(angle - Math.PI / 6),
            toY - headSize * Math.sin(angle - Math.PI / 6)
        );
        this.ctx.moveTo(toX, toY);
        this.ctx.lineTo(
            toX - headSize * Math.cos(angle + Math.PI / 6),
            toY - headSize * Math.sin(angle + Math.PI / 6)
        );
        this.ctx.stroke();
    }
    
    // 绘制网格
    grid(step = 50, color = '#e0e0e0') {
        this.ctx.strokeStyle = color;
        this.ctx.lineWidth = 1;
        
        // 垂直线
        for (let x = 0; x <= this.ctx.canvas.width; x += step) {
            this.ctx.beginPath();
            this.ctx.moveTo(x, 0);
            this.ctx.lineTo(x, this.ctx.canvas.height);
            this.ctx.stroke();
        }
        
        // 水平线
        for (let y = 0; y <= this.ctx.canvas.height; y += step) {
            this.ctx.beginPath();
            this.ctx.moveTo(0, y);
            this.ctx.lineTo(this.ctx.canvas.width, y);
            this.ctx.stroke();
        }
    }
}

// 使用示例
const utils = new DrawingUtils(ctx);

// 绘制圆角矩形
utils.roundRect(600, 50, 120, 80, 10);
ctx.fillStyle = 'lightcoral';
ctx.fill();
ctx.strokeStyle = 'darkred';
ctx.stroke();

// 绘制箭头
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
utils.arrow(600, 200, 720, 250);

// 绘制网格(可选,用于调试)
// utils.grid(50, 'rgba(0, 0, 0, 0.1)');

8. 性能优化技巧

8.1 批量绘制

// 低效的绘制方式
function inefficientDrawing() {
    for (let i = 0; i < 1000; i++) {
        ctx.fillStyle = `hsl(${i % 360}, 50%, 50%)`;
        ctx.fillRect(Math.random() * 800, Math.random() * 600, 5, 5);
    }
}

// 高效的绘制方式
function efficientDrawing() {
    // 批量设置相同样式
    const colors = ['red', 'green', 'blue', 'yellow', 'purple'];
    
    colors.forEach(color => {
        ctx.fillStyle = color;
        ctx.beginPath();
        
        // 批量绘制相同颜色的图形
        for (let i = 0; i < 200; i++) {
            const x = Math.random() * 800;
            const y = Math.random() * 600;
            ctx.rect(x, y, 5, 5);
        }
        
        ctx.fill();
    });
}

8.2 离屏渲染

// 离屏Canvas优化
function offscreenOptimization() {
    // 创建离屏canvas
    const offscreen = document.createElement('canvas');
    offscreen.width = 200;
    offscreen.height = 200;
    const offCtx = offscreen.getContext('2d');
    
    // 在离屏canvas上绘制复杂图形
    offCtx.fillStyle = 'blue';
    offCtx.fillRect(0, 0, 200, 200);
    
    for (let i = 0; i < 50; i++) {
        offCtx.fillStyle = `hsl(${i * 7}, 70%, 50%)`;
        offCtx.beginPath();
        offCtx.arc(
            Math.random() * 200,
            Math.random() * 200,
            Math.random() * 10 + 5,
            0,
            Math.PI * 2
        );
        offCtx.fill();
    }
    
    // 将离屏canvas绘制到主canvas(只需要一次drawImage调用)
    ctx.drawImage(offscreen, 600, 300);
}

offscreenOptimization();

9. 小结

本章详细介绍了Canvas的基本绘图API:

  1. 矩形绘制:fillRect、strokeRect、clearRect方法及样式设置
  2. 圆形绘制:arc方法绘制圆形、弧形、扇形和椭圆
  3. 线条绘制:直线、曲线、贝塞尔曲线及线条样式
  4. 路径系统:路径的创建、组合和检测
  5. 图案纹理:使用图案和图像进行填充
  6. 阴影效果:为图形添加阴影
  7. 实用函数:常用的绘图工具函数
  8. 性能优化:批量绘制和离屏渲染技巧

下一章我们将学习更复杂的路径绘制技术和高级图形效果。

10. 练习题

  1. 绘制一个彩虹色的同心圆
  2. 创建一个时钟表盘(包含刻度和数字)
  3. 绘制一个心形图案
  4. 实现一个简单的绘图板功能
  5. 创建一个动态的波浪线效果