首页 > 代码库 > html5 canvas绘图-刻度仪表盘的绘制

html5 canvas绘图-刻度仪表盘的绘制

---恢复内容开始---

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