Canvas绘图

Canvas绘图概述

Canvas是HTML5新增的绘图元素,通过JavaScript可以在Canvas上绘制图形、动画、游戏等。Canvas提供了一个2D渲染上下文,可以绘制各种形状、图像和文本。

东巴文(db-w.cn) 认为:Canvas为Web开发打开了图形编程的大门,让浏览器成为强大的图形平台。

canvas标签

基本用法

<canvas id="myCanvas" width="400" height="300">
    您的浏览器不支持canvas标签。
</canvas>

属性说明

属性 说明 默认值
width 画布宽度 300
height 画布高度 150
id 唯一标识符 -

获取渲染上下文

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

东巴文点评:Canvas本身没有绘图能力,必须通过JavaScript获取渲染上下文才能绘图。

坐标系统

Canvas使用二维坐标系统,原点(0,0)在左上角。

(0,0) ────────► X轴
  │
  │
  │
  ▼
 Y轴

坐标示例

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 在坐标(10, 10)绘制一个点
ctx.fillRect(10, 10, 1, 1);

// 在坐标(100, 50)绘制一个矩形
ctx.fillRect(100, 50, 80, 60);

绘制矩形

Canvas提供了三种绘制矩形的方法。

填充矩形

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 设置填充颜色
ctx.fillStyle = 'red';

// 绘制填充矩形
ctx.fillRect(10, 10, 100, 50);

描边矩形

// 设置描边颜色
ctx.strokeStyle = 'blue';

// 设置线宽
ctx.lineWidth = 3;

// 绘制描边矩形
ctx.strokeRect(120, 10, 100, 50);

清除矩形

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

矩形方法对比

方法 说明
fillRect(x, y, width, height) 填充矩形
strokeRect(x, y, width, height) 描边矩形
clearRect(x, y, width, height) 清除矩形

东巴文点评:矩形是Canvas中唯一可以直接绘制的形状,其他形状需要使用路径。

绘制路径

路径是Canvas绘图的核心概念,可以绘制各种复杂形状。

路径步骤

  1. beginPath() - 开始路径
  2. 绘制路径(moveTo、lineTo等)
  3. closePath() - 闭合路径(可选)
  4. fill()stroke() - 填充或描边

绘制三角形

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 开始路径
ctx.beginPath();

// 移动到起点
ctx.moveTo(50, 50);

// 连线到其他点
ctx.lineTo(150, 50);
ctx.lineTo(100, 150);

// 闭合路径
ctx.closePath();

// 填充
ctx.fillStyle = 'green';
ctx.fill();

// 描边
ctx.strokeStyle = 'darkgreen';
ctx.lineWidth = 2;
ctx.stroke();

路径方法

方法 说明
beginPath() 开始新路径
closePath() 闭合路径
moveTo(x, y) 移动到点
lineTo(x, y) 连线到点
fill() 填充路径
stroke() 描边路径

绘制圆弧

arc方法

ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);

参数说明

参数 说明
x 圆心X坐标
y 圆心Y坐标
radius 半径
startAngle 起始角度(弧度)
endAngle 结束角度(弧度)
counterclockwise 是否逆时针(默认false)

绘制圆形

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 开始路径
ctx.beginPath();

// 绘制圆形
ctx.arc(100, 100, 50, 0, Math.PI * 2);

// 填充
ctx.fillStyle = 'blue';
ctx.fill();

绘制半圆

ctx.beginPath();
ctx.arc(200, 100, 50, 0, Math.PI);
ctx.fillStyle = 'orange';
ctx.fill();

绘制扇形

ctx.beginPath();
ctx.moveTo(100, 200); // 移动到圆心
ctx.arc(100, 200, 50, 0, Math.PI / 2); // 绘制圆弧
ctx.closePath(); // 闭合路径
ctx.fillStyle = 'purple';
ctx.fill();

东巴文点评:角度使用弧度制,0度在3点钟方向,顺时针旋转。

绘制贝塞尔曲线

Canvas支持二次和三次贝塞尔曲线。

二次贝塞尔曲线

ctx.quadraticCurveTo(cpx, cpy, x, y);
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

ctx.beginPath();
ctx.moveTo(50, 200);
ctx.quadraticCurveTo(150, 50, 250, 200);
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.stroke();

三次贝塞尔曲线

ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
ctx.beginPath();
ctx.moveTo(50, 200);
ctx.bezierCurveTo(100, 50, 200, 350, 250, 200);
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.stroke();

绘制文本

填充文本

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 设置字体
ctx.font = '30px Arial';

// 设置填充颜色
ctx.fillStyle = 'black';

// 绘制填充文本
ctx.fillText('东巴文Canvas', 50, 50);

描边文本

ctx.font = '40px Arial';
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.strokeText('东巴文Canvas', 50, 100);

文本属性

属性/方法 说明
font 字体样式
textAlign 水平对齐(start/end/left/right/center)
textBaseline 垂直对齐(top/middle/bottom等)
fillText(text, x, y) 填充文本
strokeText(text, x, y) 描边文本
measureText(text) 测量文本宽度

文本对齐示例

ctx.font = '20px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('居中文本', canvas.width / 2, canvas.height / 2);

东巴文点评:Canvas文本渲染能力强大,但不如HTML文本灵活,适合图表、游戏等场景。

绘制图像

drawImage方法

ctx.drawImage(image, x, y);
ctx.drawImage(image, x, y, width, height);
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

基本绘制

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

const img = new Image();
img.onload = function() {
    ctx.drawImage(img, 0, 0);
};
img.src = 'image.jpg';

缩放绘制

img.onload = function() {
    ctx.drawImage(img, 0, 0, 200, 150);
};

裁剪绘制

img.onload = function() {
    // 裁剪源图像的(50,50,100,100)区域,绘制到画布的(0,0,200,200)
    ctx.drawImage(img, 50, 50, 100, 100, 0, 0, 200, 200);
};

颜色与样式

填充颜色

// 颜色名称
ctx.fillStyle = 'red';

// 十六进制
ctx.fillStyle = '#ff0000';

// RGB
ctx.fillStyle = 'rgb(255, 0, 0)';

// RGBA
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';

描边颜色

ctx.strokeStyle = 'blue';
ctx.strokeStyle = '#0000ff';
ctx.strokeStyle = 'rgb(0, 0, 255)';
ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';

透明度

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

渐变填充

线性渐变

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 创建线性渐变
const gradient = ctx.createLinearGradient(0, 0, 200, 0);

// 添加颜色停止点
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.5, 'yellow');
gradient.addColorStop(1, 'blue');

// 使用渐变填充
ctx.fillStyle = gradient;
ctx.fillRect(10, 10, 200, 100);

径向渐变

// 创建径向渐变
const gradient = ctx.createRadialGradient(100, 100, 0, 100, 100, 100);

// 添加颜色停止点
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.5, 'yellow');
gradient.addColorStop(1, 'blue');

// 使用渐变填充
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(100, 100, 100, 0, Math.PI * 2);
ctx.fill();

东巴文点评:渐变可以为Canvas图形添加丰富的视觉效果。

图案填充

创建图案

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

const img = new Image();
img.onload = function() {
    // 创建图案
    const pattern = ctx.createPattern(img, 'repeat');
    
    // 使用图案填充
    ctx.fillStyle = pattern;
    ctx.fillRect(0, 0, 400, 300);
};
img.src = 'pattern.png';

重复方式

说明
repeat 水平和垂直重复
repeat-x 水平重复
repeat-y 垂直重复
no-repeat 不重复

阴影效果

阴影属性

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 设置阴影
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;

// 绘制带阴影的矩形
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 100, 80);

阴影属性说明

属性 说明
shadowColor 阴影颜色
shadowBlur 模糊程度
shadowOffsetX X轴偏移
shadowOffsetY Y轴偏移

变换操作

平移

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 50, 50);

// 平移
ctx.translate(100, 100);

ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 50, 50);

旋转

ctx.translate(100, 100); // 移动到旋转中心
ctx.rotate(Math.PI / 4); // 旋转45度

ctx.fillStyle = 'green';
ctx.fillRect(-25, -25, 50, 50);

缩放

ctx.scale(2, 2); // 放大2倍

ctx.fillStyle = 'yellow';
ctx.fillRect(10, 10, 50, 50);

变换方法

方法 说明
translate(x, y) 平移
rotate(angle) 旋转(弧度)
scale(x, y) 缩放
transform(a, b, c, d, e, f) 矩阵变换
setTransform(a, b, c, d, e, f) 设置变换矩阵

状态保存与恢复

save和restore

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 保存状态
ctx.save();

// 设置样式并绘制
ctx.fillStyle = 'red';
ctx.translate(100, 100);
ctx.fillRect(0, 0, 50, 50);

// 恢复状态
ctx.restore();

// 使用之前的样式绘制
ctx.fillRect(0, 0, 50, 50); // 不会被平移

东巴文点评save()restore()是Canvas状态管理的核心,可以保存和恢复样式、变换等状态。

动画实现

基本动画

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

let x = 0;

function animate() {
    // 清除画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // 绘制矩形
    ctx.fillStyle = 'blue';
    ctx.fillRect(x, 100, 50, 50);
    
    // 更新位置
    x += 2;
    
    // 循环动画
    requestAnimationFrame(animate);
}

animate();

弹跳球动画

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

const ball = {
    x: 50,
    y: 50,
    vx: 4,
    vy: 4,
    radius: 20,
    color: 'blue'
};

function drawBall() {
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
    ctx.fillStyle = ball.color;
    ctx.fill();
    ctx.closePath();
}

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    drawBall();
    
    // 更新位置
    ball.x += ball.vx;
    ball.y += ball.vy;
    
    // 边界检测
    if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
        ball.vx = -ball.vx;
    }
    if (ball.y + ball.radius > canvas.height || ball.y - ball.radius < 0) {
        ball.vy = -ball.vy;
    }
    
    requestAnimationFrame(animate);
}

animate();

东巴文点评requestAnimationFrame是实现Canvas动画的最佳方式,它会在浏览器重绘之前调用指定的回调函数。

综合示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas绘图示例 - 东巴文</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        
        canvas {
            border: 1px solid #ccc;
            display: block;
            margin: 20px 0;
        }
        
        .controls {
            margin: 20px 0;
        }
        
        button {
            padding: 10px 20px;
            margin: 5px;
            border: none;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border-radius: 5px;
            cursor: pointer;
        }
        
        button:hover {
            opacity: 0.9;
        }
    </style>
</head>
<body>
    <h1>Canvas绘图示例</h1>
    
    <canvas id="myCanvas" width="600" height="400"></canvas>
    
    <div class="controls">
        <button onclick="drawRectangles()">绘制矩形</button>
        <button onclick="drawCircles()">绘制圆形</button>
        <button onclick="drawText()">绘制文本</button>
        <button onclick="drawGradient()">绘制渐变</button>
        <button onclick="animate()">动画演示</button>
        <button onclick="clearCanvas()">清除画布</button>
    </div>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 绘制矩形
        function drawRectangles() {
            clearCanvas();
            
            ctx.fillStyle = 'red';
            ctx.fillRect(50, 50, 100, 80);
            
            ctx.strokeStyle = 'blue';
            ctx.lineWidth = 3;
            ctx.strokeRect(200, 50, 100, 80);
            
            const gradient = ctx.createLinearGradient(350, 50, 450, 130);
            gradient.addColorStop(0, 'green');
            gradient.addColorStop(1, 'yellow');
            ctx.fillStyle = gradient;
            ctx.fillRect(350, 50, 100, 80);
        }
        
        // 绘制圆形
        function drawCircles() {
            clearCanvas();
            
            ctx.beginPath();
            ctx.arc(100, 200, 50, 0, Math.PI * 2);
            ctx.fillStyle = 'purple';
            ctx.fill();
            
            ctx.beginPath();
            ctx.arc(250, 200, 50, 0, Math.PI);
            ctx.fillStyle = 'orange';
            ctx.fill();
            
            ctx.beginPath();
            ctx.moveTo(400, 200);
            ctx.arc(400, 200, 50, 0, Math.PI / 2);
            ctx.closePath();
            ctx.fillStyle = 'cyan';
            ctx.fill();
        }
        
        // 绘制文本
        function drawText() {
            clearCanvas();
            
            ctx.font = 'bold 40px Arial';
            ctx.fillStyle = 'blue';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText('东巴文Canvas', canvas.width / 2, canvas.height / 2);
            
            ctx.font = '20px Arial';
            ctx.strokeStyle = 'red';
            ctx.lineWidth = 1;
            ctx.strokeText('Canvas绘图教程', canvas.width / 2, canvas.height / 2 + 50);
        }
        
        // 绘制渐变
        function drawGradient() {
            clearCanvas();
            
            // 线性渐变
            const linearGradient = ctx.createLinearGradient(50, 50, 250, 50);
            linearGradient.addColorStop(0, 'red');
            linearGradient.addColorStop(0.5, 'yellow');
            linearGradient.addColorStop(1, 'blue');
            
            ctx.fillStyle = linearGradient;
            ctx.fillRect(50, 50, 200, 100);
            
            // 径向渐变
            const radialGradient = ctx.createRadialGradient(450, 100, 0, 450, 100, 80);
            radialGradient.addColorStop(0, 'white');
            radialGradient.addColorStop(0.5, 'yellow');
            radialGradient.addColorStop(1, 'red');
            
            ctx.fillStyle = radialGradient;
            ctx.beginPath();
            ctx.arc(450, 100, 80, 0, Math.PI * 2);
            ctx.fill();
        }
        
        // 动画演示
        let animationId;
        let x = 0;
        
        function animate() {
            if (animationId) {
                cancelAnimationFrame(animationId);
            }
            
            x = 0;
            
            function loop() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                
                ctx.fillStyle = 'blue';
                ctx.fillRect(x, canvas.height / 2 - 25, 50, 50);
                
                x += 3;
                
                if (x > canvas.width) {
                    x = -50;
                }
                
                animationId = requestAnimationFrame(loop);
            }
            
            loop();
        }
        
        // 清除画布
        function clearCanvas() {
            if (animationId) {
                cancelAnimationFrame(animationId);
            }
            ctx.clearRect(0, 0, canvas.width, canvas.height);
        }
    </script>
</body>
</html>

Canvas API速查表

绘制方法

方法 说明
fillRect(x, y, w, h) 填充矩形
strokeRect(x, y, w, h) 描边矩形
clearRect(x, y, w, h) 清除矩形
beginPath() 开始路径
closePath() 闭合路径
moveTo(x, y) 移动到点
lineTo(x, y) 连线到点
arc(x, y, r, start, end) 绘制圆弧
fill() 填充
stroke() 描边

样式属性

属性 说明
fillStyle 填充样式
strokeStyle 描边样式
lineWidth 线宽
lineCap 线端样式
lineJoin 线连接样式
globalAlpha 全局透明度

变换方法

方法 说明
translate(x, y) 平移
rotate(angle) 旋转
scale(x, y) 缩放
save() 保存状态
restore() 恢复状态

最佳实践

1. 性能优化

// 推荐:避免在动画中频繁创建对象
const gradient = ctx.createLinearGradient(0, 0, 200, 0);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, 200, 100);
    requestAnimationFrame(animate);
}

// 不推荐:在动画中创建渐变
function animate() {
    const gradient = ctx.createLinearGradient(0, 0, 200, 0);
    gradient.addColorStop(0, 'red');
    gradient.addColorStop(1, 'blue');
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, 200, 100);
    requestAnimationFrame(animate);
}

2. 响应式Canvas

function resizeCanvas() {
    const canvas = document.getElementById('myCanvas');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
}

window.addEventListener('resize', resizeCanvas);
resizeCanvas();

3. 高清屏适配

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1;

canvas.width = 400 * dpr;
canvas.height = 300 * dpr;
canvas.style.width = '400px';
canvas.style.height = '300px';

ctx.scale(dpr, dpr);

学习检验

知识点测试

问题1:Canvas中用于开始新路径的方法是?

A. startPath() B. beginPath() C. newPath() D. createPath()

<details> <summary>点击查看答案</summary>

答案:B

东巴文解释beginPath()方法用于开始一个新的路径,这是Canvas绘图的标准方法。

</details>

问题2:以下哪个方法用于绘制圆形?

A. drawCircle() B. circle() C. arc() D. ellipse()

<details> <summary>点击查看答案</summary>

答案:C

东巴文解释:Canvas使用arc()方法绘制圆形和圆弧,通过设置起始角度为0,结束角度为Math.PI * 2可以绘制完整的圆。

</details>

实践任务

任务:创建一个Canvas动画,实现一个在画布中弹跳的小球。

<details> <summary>点击查看参考答案</summary>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>弹跳球动画</title>
    <style>
        canvas {
            border: 1px solid #ccc;
            display: block;
            margin: 20px auto;
        }
    </style>
</head>
<body>
    <canvas id="myCanvas" width="600" height="400"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        const ball = {
            x: 100,
            y: 100,
            vx: 5,
            vy: 3,
            radius: 20,
            color: 'blue'
        };
        
        function drawBall() {
            ctx.beginPath();
            ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
            ctx.fillStyle = ball.color;
            ctx.fill();
            ctx.closePath();
        }
        
        function update() {
            ball.x += ball.vx;
            ball.y += ball.vy;
            
            if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
                ball.vx = -ball.vx;
            }
            if (ball.y + ball.radius > canvas.height || ball.y - ball.radius < 0) {
                ball.vy = -ball.vy;
            }
        }
        
        function animate() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            drawBall();
            update();
            
            requestAnimationFrame(animate);
        }
        
        animate();
    </script>
</body>
</html>
</details>

东巴文(db-w.cn) - 让编程学习更有趣、更高效!