---恢复内容开始---
圆弧,尤其是圆,通常被用做描绘一些实物。下图所示的应用程序用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,这样的话,应用程序就可以很容易地计算出绘制文本的位置了。