---恢复内容开始---
圆弧,尤其是圆,通常被用做描绘一些实物。下图所示的应用程序用5个圆形实现了一个仪表盘。仪表盘的刻度代表了圆周上的角度值。用户可以通过它来交互式地旋转多边形物体。
该应用程序使用了本章到目前为止所讲的很多技术。为了绘制这个仪表盘,该应用程序画了许多圆形与线段,使用了各种颜色及透明度,对圆形路径进行了描边与填充。同时为了使盘面上的刻度看起来有深度感,它还运用了阴影效果。该程序还运用了剪纸效果。使得仪表盘外围的那一圈看起来有半透明的效果。
仪表盘的绘制
html代码:
1 <head>
2 <title>A Dial Showing the Degrees of a Circle</title>
3
4 <style>
5 body {
6 background: #eeeeee;
7 }
8
9 #canvas {
10 background: #ffffff;
11 cursor: crosshair;
12 margin-left: 10px;
13 margin-top: 10px;
14 -webkit-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
15 -moz-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
16 box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
17 }
18
19 </style>
20 </head>
21
22 <body>
23 <canvas id=‘canvas‘ width=‘650‘ height=‘450‘>
24 Canvas not supported
25 </canvas>
26
27 <script src = ‘example.js‘></script>
28 </body>
29 </html>
example.js
1 var canvas = document.getElementById(‘canvas‘),
2 context = canvas.getContext(‘2d‘),
3
4 CENTROID_RADIUS = 10,
5 CENTROID_STROKE_STYLE = ‘rgba(0, 0, 0, 0.5)‘,
6 CENTROID_FILL_STYLE = ‘rgba(80, 190, 240, 0.6)‘,
7
8 RING_INNER_RADIUS = 35,
9 RING_OUTER_RADIUS = 55,
10
11 ANNOTATIONS_FILL_STYLE = ‘rgba(0, 0, 230, 0.9)‘,
12 ANNOTATIONS_TEXT_SIZE = 12,
13
14 TICK_WIDTH = 10,
15 TICK_LONG_STROKE_STYLE = ‘rgba(100, 140, 230, 0.9)‘,
16 TICK_SHORT_STROKE_STYLE = ‘rgba(100, 140, 230, 0.7)‘,
17
18 TRACKING_DIAL_STROKING_STYLE = ‘rgba(100, 140, 230, 0.5)‘,
19
20 GUIDEWIRE_STROKE_STYLE = ‘goldenrod‘,
21 GUIDEWIRE_FILL_STYLE = ‘rgba(250, 250, 0, 0.6)‘,
22
23 circle = { x: canvas.width/2,
24 y: canvas.height/2,
25 radius: 150
26 };
27
28 // Functions..........................................................
29
30 function drawGrid(color, stepx, stepy) {
31 context.save()
32
33 context.shadowColor = undefined;
34 context.shadowOffsetX = 0;
35 context.shadowOffsetY = 0;
36
37 context.strokeStyle = color;
38 context.fillStyle = ‘#ffffff‘;
39 context.lineWidth = 0.5;
40 context.fillRect(0, 0, context.canvas.width,
41 context.canvas.height);
42
43 for (var i = stepx + 0.5;
44 i < context.canvas.width; i += stepx) {
45 context.beginPath();
46 context.moveTo(i, 0);
47 context.lineTo(i, context.canvas.height);
48 context.stroke();
49 }
50
51 for (var i = stepy + 0.5;
52 i < context.canvas.height; i += stepy) {
53 context.beginPath();
54 context.moveTo(0, i);
55 context.lineTo(context.canvas.width, i);
56 context.stroke();
57 }
58
59 context.restore();
60 }
61
62 function drawDial() {
63 var loc = {x: circle.x, y: circle.y};
64
65 drawCentroid();
66 drawCentroidGuidewire(loc);
67
68 drawRing();
69 drawTickInnerCircle();
70 drawTicks();
71 drawAnnotations();
72 }
73
74 function drawCentroid() {
75 context.beginPath();
76 context.save();
77 context.strokeStyle = CENTROID_STROKE_STYLE;
78 context.fillStyle = CENTROID_FILL_STYLE;
79 context.arc(circle.x, circle.y,
80 CENTROID_RADIUS, 0, Math.PI*2, false);
81 context.stroke();
82 context.fill();
83 context.restore();
84 }
85
86 function drawCentroidGuidewire(loc) {
87 var angle = -Math.PI/4,
88 radius, endpt;
89
90 radius = circle.radius + RING_OUTER_RADIUS;
91
92 if (loc.x >= circle.x) {
93 endpt = { x: circle.x + radius * Math.cos(angle),
94 y: circle.y + radius * Math.sin(angle)
95 };
96 }
97 else {
98 endpt = { x: circle.x - radius * Math.cos(angle),
99 y: circle.y - radius * Math.sin(angle)
100 };
101 }
102
103 context.save();
104
105 context.strokeStyle = GUIDEWIRE_STROKE_STYLE;
106 context.fillStyle = GUIDEWIRE_FILL_STYLE;
107
108 context.beginPath();
109 context.moveTo(circle.x, circle.y);
110 context.lineTo(endpt.x, endpt.y);
111 context.stroke();
112
113 context.beginPath();
114 context.strokeStyle = TICK_LONG_STROKE_STYLE;
115 context.arc(endpt.x, endpt.y, 5, 0, Math.PI*2, false);
116 context.fill();
117 context.stroke();
118
119 context.restore();
120 }
121
122 function drawRing() {
123 drawRingOuterCircle();
124
125 context.strokeStyle = ‘rgba(0, 0, 0, 0.1)‘;
126 context.arc(circle.x, circle.y,
127 circle.radius + RING_INNER_RADIUS,
128 0, Math.PI*2, false);
129
130 context.fillStyle = ‘rgba(100, 140, 230, 0.1)‘;
131 context.fill();
132 context.stroke();
133 }
134
135 function drawRingOuterCircle() {
136 context.shadowColor = ‘rgba(0, 0, 0, 0.7)‘;
137 context.shadowOffsetX = 3,
138 context.shadowOffsetY = 3,
139 context.shadowBlur = 6,
140 context.strokeStyle = TRACKING_DIAL_STROKING_STYLE;
141 context.beginPath();
142 context.arc(circle.x, circle.y, circle.radius +
143 RING_OUTER_RADIUS, 0, Math.PI*2, true);
144 context.stroke();
145 }
146
147 function drawTickInnerCircle() {
148 context.save();
149 context.beginPath();
150 context.strokeStyle = ‘rgba(0, 0, 0, 0.1)‘;
151 context.arc(circle.x, circle.y,
152 circle.radius + RING_INNER_RADIUS - TICK_WIDTH,
153 0, Math.PI*2, false);
154 context.stroke();
155 context.restore();
156 }
157
158 function drawTick(angle, radius, cnt) {
159 var tickWidth = cnt % 4 === 0 ? TICK_WIDTH : TICK_WIDTH/2;
160
161 context.beginPath();
162
163 context.moveTo(circle.x + Math.cos(angle) * (radius - tickWidth),
164 circle.y + Math.sin(angle) * (radius - tickWidth));
165
166 context.lineTo(circle.x + Math.cos(angle) * (radius),
167 circle.y + Math.sin(angle) * (radius));
168
169 context.strokeStyle = TICK_SHORT_STROKE_STYLE;
170 context.stroke();
171 }
172
173 function drawTicks() {
174 var radius = circle.radius + RING_INNER_RADIUS,
175 ANGLE_MAX = 2*Math.PI,
176 ANGLE_DELTA = Math.PI/64,
177 tickWidth;
178
179 context.save();
180
181 for (var angle = 0, cnt = 0; angle < ANGLE_MAX;
182 angle += ANGLE_DELTA, cnt++) {
183 drawTick(angle, radius, cnt++);
184 }
185
186 context.restore();
187 }
188
189 function drawAnnotations() {
190 var radius = circle.radius + RING_INNER_RADIUS;
191
192 context.save();
193 context.fillStyle = ANNOTATIONS_FILL_STYLE;
194 context.font = ANNOTATIONS_TEXT_SIZE + ‘px Helvetica‘;
195
196 for (var angle=0; angle < 2*Math.PI; angle += Math.PI/8) {
197 context.beginPath();
198 context.fillText((angle * 180 / Math.PI).toFixed(0),
199 circle.x + Math.cos(angle) * (radius - TICK_WIDTH*2),
200 circle.y - Math.sin(angle) * (radius - TICK_WIDTH*2));
201 }
202 context.restore();
203 }
204
205 // Initialization....................................................
206
207 context.shadowOffsetX = 2;
208 context.shadowOffsetY = 2;
209 context.shadowBlur = 4;
210
211 context.textAlign = ‘center‘;
212 context.textBaseline = ‘middle‘;
213 drawGrid(‘lightgray‘, 10, 10);
214 drawDial();
在上述的javascript代码中,有一些问题值得注意。首先,像往常一样,该应用程序在每次调用ARC()方法之前,几乎都会调用beginpath()方法,以便在创建弧形路径之前先开始一段新的路径。原来说过,arc()方法会将上一条子路径的终点与圆弧路径的起点相连,所以我们调用beginpath(),将当期路径的所有子路径都清除。这样的话,arc()方法就不会画出那些不美观的线段了。
该应用程序使用了绘制剪纸效果的技巧,来让表盘背景看起来有些透明。代码调用了arc()方法,按照顺时针方向来绘制外围的圆形,且按照逆时针方向来绘制里面的圆形。在这种情况下,为了做出剪纸的效果,应用程序并没有在第二次调用arc()方法之前先调用beginPath()方法。
第二个要注意的是,save()方法与restore()方法之间的那段代码,对绘图环境对象的某些属性做了一个临时性的修改,例如strokeStyle与fillStyle()等。通过Canvas绘图环境的save()与restrore()方法,你可以实现各自独立且互不干扰的绘图函数来。
最后,请注意应用程序是如何绘制仪表盘周围文字的。先把绘图环境对象的textAlign与textBaseline属性分别设置为center与middle,这样的话,应用程序就可以很容易地计算出绘制文本的位置了。
---恢复内容结束---
圆弧,尤其是圆,通常被用做描绘一些实物。下图所示的应用程序用5个圆形实现了一个仪表盘。仪表盘的刻度代表了圆周上的角度值。用户可以通过它来交互式地旋转多边形物体。
该应用程序使用了本章到目前为止所讲的很多技术。为了绘制这个仪表盘,该应用程序画了许多圆形与线段,使用了各种颜色及透明度,对圆形路径进行了描边与填充。同时为了使盘面上的刻度看起来有深度感,它还运用了阴影效果。该程序还运用了剪纸效果。使得仪表盘外围的那一圈看起来有半透明的效果。
仪表盘的绘制
html代码:
1 <head>
2 <title>A Dial Showing the Degrees of a Circle</title>
3
4 <style>
5 body {
6 background: #eeeeee;
7 }
8
9 #canvas {
10 background: #ffffff;
11 cursor: crosshair;
12 margin-left: 10px;
13 margin-top: 10px;
14 -webkit-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
15 -moz-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
16 box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
17 }
18
19 </style>
20 </head>
21
22 <body>
23 <canvas id=‘canvas‘ width=‘650‘ height=‘450‘>
24 Canvas not supported
25 </canvas>
26
27 <script src = ‘example.js‘></script>
28 </body>
29 </html>
example.js
1 var canvas = document.getElementById(‘canvas‘),
2 context = canvas.getContext(‘2d‘),
3
4 CENTROID_RADIUS = 10,
5 CENTROID_STROKE_STYLE = ‘rgba(0, 0, 0, 0.5)‘,
6 CENTROID_FILL_STYLE = ‘rgba(80, 190, 240, 0.6)‘,
7
8 RING_INNER_RADIUS = 35,
9 RING_OUTER_RADIUS = 55,
10
11 ANNOTATIONS_FILL_STYLE = ‘rgba(0, 0, 230, 0.9)‘,
12 ANNOTATIONS_TEXT_SIZE = 12,
13
14 TICK_WIDTH = 10,
15 TICK_LONG_STROKE_STYLE = ‘rgba(100, 140, 230, 0.9)‘,
16 TICK_SHORT_STROKE_STYLE = ‘rgba(100, 140, 230, 0.7)‘,
17
18 TRACKING_DIAL_STROKING_STYLE = ‘rgba(100, 140, 230, 0.5)‘,
19
20 GUIDEWIRE_STROKE_STYLE = ‘goldenrod‘,
21 GUIDEWIRE_FILL_STYLE = ‘rgba(250, 250, 0, 0.6)‘,
22
23 circle = { x: canvas.width/2,
24 y: canvas.height/2,
25 radius: 150
26 };
27
28 // Functions..........................................................
29
30 function drawGrid(color, stepx, stepy) {
31 context.save()
32
33 context.shadowColor = undefined;
34 context.shadowOffsetX = 0;
35 context.shadowOffsetY = 0;
36
37 context.strokeStyle = color;
38 context.fillStyle = ‘#ffffff‘;
39 context.lineWidth = 0.5;
40 context.fillRect(0, 0, context.canvas.width,
41 context.canvas.height);
42
43 for (var i = stepx + 0.5;
44 i < context.canvas.width; i += stepx) {
45 context.beginPath();
46 context.moveTo(i, 0);
47 context.lineTo(i, context.canvas.height);
48 context.stroke();
49 }
50
51 for (var i = stepy + 0.5;
52 i < context.canvas.height; i += stepy) {
53 context.beginPath();
54 context.moveTo(0, i);
55 context.lineTo(context.canvas.width, i);
56 context.stroke();
57 }
58
59 context.restore();
60 }
61
62 function drawDial() {
63 var loc = {x: circle.x, y: circle.y};
64
65 drawCentroid();
66 drawCentroidGuidewire(loc);
67
68 drawRing();
69 drawTickInnerCircle();
70 drawTicks();
71 drawAnnotations();
72 }
73
74 function drawCentroid() {
75 context.beginPath();
76 context.save();
77 context.strokeStyle = CENTROID_STROKE_STYLE;
78 context.fillStyle = CENTROID_FILL_STYLE;
79 context.arc(circle.x, circle.y,
80 CENTROID_RADIUS, 0, Math.PI*2, false);
81 context.stroke();
82 context.fill();
83 context.restore();
84 }
85
86 function drawCentroidGuidewire(loc) {
87 var angle = -Math.PI/4,
88 radius, endpt;
89
90 radius = circle.radius + RING_OUTER_RADIUS;
91
92 if (loc.x >= circle.x) {
93 endpt = { x: circle.x + radius * Math.cos(angle),
94 y: circle.y + radius * Math.sin(angle)
95 };
96 }
97 else {
98 endpt = { x: circle.x - radius * Math.cos(angle),
99 y: circle.y - radius * Math.sin(angle)
100 };
101 }
102
103 context.save();
104
105 context.strokeStyle = GUIDEWIRE_STROKE_STYLE;
106 context.fillStyle = GUIDEWIRE_FILL_STYLE;
107
108 context.beginPath();
109 context.moveTo(circle.x, circle.y);
110 context.lineTo(endpt.x, endpt.y);
111 context.stroke();
112
113 context.beginPath();
114 context.strokeStyle = TICK_LONG_STROKE_STYLE;
115 context.arc(endpt.x, endpt.y, 5, 0, Math.PI*2, false);
116 context.fill();
117 context.stroke();
118
119 context.restore();
120 }
121
122 function drawRing() {
123 drawRingOuterCircle();
124
125 context.strokeStyle = ‘rgba(0, 0, 0, 0.1)‘;
126 context.arc(circle.x, circle.y,
127 circle.radius + RING_INNER_RADIUS,
128 0, Math.PI*2, false);
129
130 context.fillStyle = ‘rgba(100, 140, 230, 0.1)‘;
131 context.fill();
132 context.stroke();
133 }
134
135 function drawRingOuterCircle() {
136 context.shadowColor = ‘rgba(0, 0, 0, 0.7)‘;
137 context.shadowOffsetX = 3,
138 context.shadowOffsetY = 3,
139 context.shadowBlur = 6,
140 context.strokeStyle = TRACKING_DIAL_STROKING_STYLE;
141 context.beginPath();
142 context.arc(circle.x, circle.y, circle.radius +
143 RING_OUTER_RADIUS, 0, Math.PI*2, true);
144 context.stroke();
145 }
146
147 function drawTickInnerCircle() {
148 context.save();
149 context.beginPath();
150 context.strokeStyle = ‘rgba(0, 0, 0, 0.1)‘;
151 context.arc(circle.x, circle.y,
152 circle.radius + RING_INNER_RADIUS - TICK_WIDTH,
153 0, Math.PI*2, false);
154 context.stroke();
155 context.restore();
156 }
157
158 function drawTick(angle, radius, cnt) {
159 var tickWidth = cnt % 4 === 0 ? TICK_WIDTH : TICK_WIDTH/2;
160
161 context.beginPath();
162
163 context.moveTo(circle.x + Math.cos(angle) * (radius - tickWidth),
164 circle.y + Math.sin(angle) * (radius - tickWidth));
165
166 context.lineTo(circle.x + Math.cos(angle) * (radius),
167 circle.y + Math.sin(angle) * (radius));
168
169 context.strokeStyle = TICK_SHORT_STROKE_STYLE;
170 context.stroke();
171 }
172
173 function drawTicks() {
174 var radius = circle.radius + RING_INNER_RADIUS,
175 ANGLE_MAX = 2*Math.PI,
176 ANGLE_DELTA = Math.PI/64,
177 tickWidth;
178
179 context.save();
180
181 for (var angle = 0, cnt = 0; angle < ANGLE_MAX;
182 angle += ANGLE_DELTA, cnt++) {
183 drawTick(angle, radius, cnt++);
184 }
185
186 context.restore();
187 }
188
189 function drawAnnotations() {
190 var radius = circle.radius + RING_INNER_RADIUS;
191
192 context.save();
193 context.fillStyle = ANNOTATIONS_FILL_STYLE;
194 context.font = ANNOTATIONS_TEXT_SIZE + ‘px Helvetica‘;
195
196 for (var angle=0; angle < 2*Math.PI; angle += Math.PI/8) {
197 context.beginPath();
198 context.fillText((angle * 180 / Math.PI).toFixed(0),
199 circle.x + Math.cos(angle) * (radius - TICK_WIDTH*2),
200 circle.y - Math.sin(angle) * (radius - TICK_WIDTH*2));
201 }
202 context.restore();
203 }
204
205 // Initialization....................................................
206
207 context.shadowOffsetX = 2;
208 context.shadowOffsetY = 2;
209 context.shadowBlur = 4;
210
211 context.textAlign = ‘center‘;
212 context.textBaseline = ‘middle‘;
213 drawGrid(‘lightgray‘, 10, 10);
214 drawDial();
在上述的javascript代码中,有一些问题值得注意。首先,像往常一样,该应用程序在每次调用ARC()方法之前,几乎都会调用beginpath()方法,以便在创建弧形路径之前先开始一段新的路径。原来说过,arc()方法会将上一条子路径的终点与圆弧路径的起点相连,所以我们调用beginpath(),将当期路径的所有子路径都清除。这样的话,arc()方法就不会画出那些不美观的线段了。
该应用程序使用了绘制剪纸效果的技巧,来让表盘背景看起来有些透明。代码调用了arc()方法,按照顺时针方向来绘制外围的圆形,且按照逆时针方向来绘制里面的圆形。在这种情况下,为了做出剪纸的效果,应用程序并没有在第二次调用arc()方法之前先调用beginPath()方法。
第二个要注意的是,save()方法与restore()方法之间的那段代码,对绘图环境对象的某些属性做了一个临时性的修改,例如strokeStyle与fillStyle()等。通过Canvas绘图环境的save()与restrore()方法,你可以实现各自独立且互不干扰的绘图函数来。
最后,请注意应用程序是如何绘制仪表盘周围文字的。先把绘图环境对象的textAlign与textBaseline属性分别设置为center与middle,这样的话,应用程序就可以很容易地计算出绘制文本的位置了。