---恢復內容開始---
圓弧,尤其是圓,通常被用做描繪一些實物。下圖所示的應用程序用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,這樣的話,應用程序就可以很容易地計算出繪制文本的位置了。