Canvas怎么实现绘制地球功能?

在Canvas中绘制路径的知识,我们已经掌握了一些技巧。现在,令人兴奋的是,Canvas路径可以实现与SVG路径一样的功能!例如绘制地球轮廓,它就是使用Canvas路径绘制出来的。为了让大家更清楚地看到效果,我还在图中添加了网格线来区分。

示例效果与源代码:

运行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
<!DOCTYPE html>
<html>

<head>
<title>绘制复杂路径</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="图形系统开发实战:基础篇 示例">
<meta name="author" content="hjq">
<meta name="keywords" content="canvas,ladder,javascript,图形">
<script src="./js/helper.js"></script>
<script src="./js/path.js"></script>
</head>

<body style="overflow: hidden; margin:10px;">
<canvas id="canvas" width="600" height="520" style="border:solid 1px #CCCCCC;"></canvas>
</body>
<script>
// 从页面中获取画板对象
let canvas = document.getElementById('canvas');
// 从画板中获取“2D渲染上下文”对象
let ctx = canvas.getContext('2d');
// 绘制背景网格线
drawGrid('lightgray', 10, 10);

// 地球网格数据
let datas = [
"m782.98 577.54a387.3 397.46 0 1 0 -774.61 0 387.3 397.46 0 1 0 774.61 0z",
"m378.74 182.34s -109.95 45.977 -112.21 398.28 c -2.2584 352.3 123.5 394.4 123.5 394.4",
"m378.74 182.34s338.75 45.166 336.49 397.46 c -2.2583 352.3 -325.2 395.21 -325.2 395.21",
"m378.74 182.34s121.21 47.46 118.95 399.76 c -2.2584 352.3 -107.66 392.91 -107.66 392.91",
"m378.74 182.34s -307.84 40.684 -310.1 392.98 c -2.2583 352.3 321.39 399.69 321.39 399.69",
"m378.74 182.34s225.83 40.65 223.57 392.95 c -2.2582 352.3 -212.28 399.72 -212.28 399.72",
"m378.74 182.34s -227.72 42.236 -229.98 394.54 c -2.2584 352.3 241.27 398.14 241.27 398.14",
"m378.74 182.34s6.775 33.875 4.5166 386.17 c -2.2582 352.3 6.7749 406.5 6.7749 406.5",
"m240.98 213.95s9.0333 51.942 140.02 51.942c130.98 0 169.37 -47.425 169.37 -47.425",
"m113.88 306.54s6.4347 79.041 265.11 79.041 307.51 -74.525 302.99 -72.266",
"m48.895 399.13s29.877 101.62 332.11 101.62 363.72 -92.591 363.72 -92.591",
"m13.009 523.34s49.446 83.558 373.46 83.558 391.88 -79.041 391.88 -79.041",
"m13.012 615.93s47.121 94.85 370.17 94.85 392.9 -90.333 392.9 -90.333",
"m35.368 715.3s45.75 88.075 348.92 88.075 367.19 -83.558 367.19 -83.558",
"m84.569 812.41s38.426 76.783 300.89 76.783 319.07 -72.266 319.07 -72.266",
"m156.1 884.67s31.99 72.267 233.89 72.267 243.01 -67.75 243.01 -67.75"];
ctx.scale(0.6, 0.6);
ctx.translate(100, -140);

// datas = ["M 110,10 l 80,80 v -80 h -40", "M 10,10 L 90,90 V 10 H 50"];

ctx.strokeStyle = "black";
ctx.lineWidth = 3;

// 逐个城市绘制成多边形
for (let i = 0; i < datas.length; i++) {
// 将字符串转换为命令和坐标数据
let pathData = pathParse(datas[i]);
let lastPoint = [0, 0];
let c_lastControlPoint = [0, 0]; // 三次贝塞尔曲线控制点
let q_lastControlPoint = [0, 0]; // 二次贝塞尔曲线控制点

// 开始绘制多边形
ctx.beginPath();

// 生成多边形路径
for (let j = 0; j < pathData.length; j++) {
// 移动位置 (x,y)
if (pathData[j][0] == "M") {
ctx.moveTo(pathData[j][1], pathData[j][2]);
lastPoint = [pathData[j][1], pathData[j][2]];
}
// 直线 (x,y)
else if (pathData[j][0] == "L") {
ctx.lineTo(pathData[j][1], pathData[j][2]);
lastPoint = [pathData[j][1], pathData[j][2]];
}
// 水平直线 (x)
else if (pathData[j][0] == "H"){
ctx.lineTo(pathData[j][1], lastPoint[1]);
lastPoint = [pathData[j][1], lastPoint[1]];
}
// 垂直直线 (y)
else if (pathData[j][0] == "V"){
ctx.lineTo(lastPoint[0], pathData[j][1]);
lastPoint = [lastPoint[0], pathData[j][1]];
}
// 椭圆曲线 (rx ry angle large-arc-flag sweep-flag x y)+
else if (pathData[j][0] == "A") {
let arcArray = fromArcToBeziers(lastPoint[0], lastPoint[1], pathData[j]);
for(let idx = 0; idx<arcArray.length; idx ++) {
ctx.bezierCurveTo(arcArray[idx][1], arcArray[idx][2], arcArray[idx][3], arcArray[idx][4], arcArray[idx][5], arcArray[idx][6]);
lastPoint = [arcArray[idx][5], arcArray[idx][6]];
}
}
// 三次贝塞尔曲线 (x2,y2,x,y)
else if (pathData[j][0] == "S") {
if (c_lastControlPoint[0] == 0 && c_lastControlPoint[1] == 0) {
c_lastControlPoint = lastPoint.slice();
} else {
c_lastControlPoint = getSymmetricPointRelative(c_lastControlPoint, lastPoint); // 控制点1
}
ctx.bezierCurveTo(c_lastControlPoint[0], c_lastControlPoint[1], pathData[j][1], pathData[j][2], pathData[j][3], pathData[j][4]);
c_lastControlPoint = [pathData[j][1], pathData[j][2]]
lastPoint = [pathData[j][3], pathData[j][4]];
}
// 三次贝塞尔曲线 (x1,y1,x2,y2,x,y)
else if (pathData[j][0] == "C") {
ctx.bezierCurveTo(pathData[j][1], pathData[j][2], pathData[j][3], pathData[j][4], pathData[j][5], pathData[j][6]);
c_lastControlPoint = [pathData[j][3], pathData[j][4]];
lastPoint = [pathData[j][5], pathData[j][6]];
}
// 二次贝塞尔曲线 (x1,y1,x,y)
else if (pathData[j][0] == "Q") {
ctx.quadraticCurveTo(pathData[j][1], pathData[j][2], pathData[j][3], pathData[j][4]);
q_lastControlPoint = [pathData[j][1], pathData[j][2]];
lastPoint = [pathData[j][3], pathData[j][4]];
}
// 二次贝塞尔曲线 (x,y)
else if (pathData[j][0] == "T") {
if (q_lastControlPoint[0] == 0 && q_lastControlPoint[1] == 0) {
q_lastControlPoint = lastPoint.slice();
} else {
q_lastControlPoint = getSymmetricPointRelative(q_lastControlPoint, lastPoint);
}
lastPoint = [pathData[j][1], pathData[j][2]];
}
// 移动到初始为止
else if (pathData[j][0] == "Z" || pathData[j][0] == "z") {
ctx.closePath();
}
}

ctx.stroke();
}
</script>

</html>

尝试一下 »

绘制地球在Canvas上的实现逻辑并不复杂,这里简单概述一下步骤。首先,我们需要读取SVG中的路径数据,这通常包括一系列的命令和参数。然后,我们使用Canvas提供的绘图方法,根据这些命令和参数在Canvas上创建对应的路径。这个过程就像是在一张白纸上,按照给定的指令一步一步画出图案。最后,通过Canvas的绘图功能,将这个路径呈现出来。

如果你想深入了解这个过程的具体实现细节,我推荐你关注我们图形开发学院的实战课程。在那里,你可以找到更详细的教程和案例,帮助你更好地掌握Canvas绘图技巧。