-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy path17_canvas.html
More file actions
750 lines (533 loc) · 137 KB
/
17_canvas.html
File metadata and controls
750 lines (533 loc) · 137 KB
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ترسیم روی Canvas :: Eloquent JavaScript</title>
<link rel=stylesheet href="js/node_modules/codemirror/lib/codemirror.css">
<script src="js/acorn_codemirror.js"></script>
<link rel=stylesheet href="css/ejs.css">
<script src="js/sandbox.js"></script>
<script src="js/ejs.js"></script><script>var chapNum = 17;var sandboxLoadFiles = ["code/chapter/16_game.js","code/levels.js","code/chapter/17_canvas.js"];</script><script>var clicky_site_ids = clicky_site_ids || []; clicky_site_ids.push(101171577);</script>
<script async src="//static.getclicky.com/js"></script>
</head>
<article>
<nav><a href="16_game.html" title="previous chapter">◀</a> <a href="index.html" title="cover">◆</a> <a href="18_http.html" title="next chapter">▶</a></nav>
<h1><span class=chap_num>فصل 17</span>ترسیم روی Canvas</h1>
<blockquote>
<p><a class="p_ident" id="p_2jmj7l5rSw" href="#p_2jmj7l5rSw" tabindex="-1" role="presentation"></a>طراحی یک فریب است.</p>
<footer>M.C. Escher, <cite>cited by Bruno Ernst in The Magic Mirror of M.C. Escher</cite></footer>
</blockquote><figure class="chapter framed"><img src="img/chapter_picture_17.jpg" alt="Picture of a robot arm drawing on paper"></figure>
<p><a class="p_ident" id="p_h9MOz99e99" href="#p_h9MOz99e99" tabindex="-1" role="presentation"></a>مرورگرها روشهای متعددی برای نمایش عناصر گرافیکی را در اختیار ما می گذارند. سادهترین راه استفاده از سبکهای CSS برای رنگدهی و موقعیتدهی عناصر معمول DOM میباشد. این روش می تواند شما را از مسیر نسبتا دور کند، همانطور که بازی ساخته شده در <a href="16_game.html">فصل قبل</a> نشان داد. با افزودن تصاویر پسزمینه نیمه شفاف به گرهها، می توانیم گرهها را دقیقا تبدیل به چیزی کنیم که لازم داریم. حتی می شود که عناصر را با استفاده از دستور <code>transform</code> در CSS بچرخانیم یا تغییر شکل دهیم.</p>
<p><a class="p_ident" id="p_BPT5caOywU" href="#p_BPT5caOywU" tabindex="-1" role="presentation"></a>اما به هر حال ما از DOM برای کاری استفاده می کنیم که برای آن طراحی نشده است. بعضی کارها مثل ترسیم یک خط بین دو نقطهی دلخواه، کاری به شدت ناهمگون با ماهیت عناصر HTML معمولی است.</p>
<p><a class="p_ident" id="p_Tn/Y7Gs7p5" href="#p_Tn/Y7Gs7p5" tabindex="-1" role="presentation"></a>دو گزینهی دیگر پیش روی ما قرار داد. روش اول استفاده از DOM اما با بکارگیری تصاویر برداری مقیاسپذیر (SVG) نسبت به HTML است. می توانید SVG را به عنوان گویشی برای نشانهگذاری سند اما با تمرکز بر اشکال به جای متون در نظر گرفت. می توانید یک سند SVG را مستقیما درونی یک سند HTML قرار دهید یا آن را در یک برچسب <bdo><code><img></code></bdo> قرار دهید.</p>
<p><a class="p_ident" id="p_3/aLzLBdyy" href="#p_3/aLzLBdyy" tabindex="-1" role="presentation"></a>گزینهی دوم استفاده از <em>canvas</em> است. یک canvas یک عنصر DOM است که یک تصویر را کپسوله سازی می کند. این عنصر یک رابط برنامه نویسی برای ترسیم اشکال در فضای اشغال شده توسط آن را فراهم می سازد. تفاوت اصلی بین یک canvas و یک تصویر SVG این است که در SVG تعریف اصلی اشکال حفظ می شود در نتیجه می توان آن ها را در هر زمان حرکت یا تغییر اندازه داد. یک canvas، در سوی دیگر، اشکال را بهمحض اینکه ترسیم شدند، به پیکسلها (نقطههای رنگی روی یک محل تصویر) تبدیل می کند و چیزی که این پیکسلها نمایندگی می کنند را جایی نگهداری نمیکند. تنها راهی که برای حرکت دادن یک شکل درون یک canvas وجود دارد پاک کردن آن (پاک کردن قسمتی از canvas که شکل آنجا وجود دارد) و ترسیم دوبارهی شکل در جایگاه جدید است.</p>
<h2><a class="h_ident" id="h_UPzm0CiZhQ" href="#h_UPzm0CiZhQ" tabindex="-1" role="presentation"></a>SVG</h2>
<p><a class="p_ident" id="p_UPzm0CiZhQ" href="#p_UPzm0CiZhQ" tabindex="-1" role="presentation"></a>این کتاب به جزئیات کار با SVG نمی پردازد، اما به طور مختصر با نحوهی عملکرد آن آشنا می شویم. <a href="17_canvas.html#graphics_tradeoffs">در پایان این فصل</a>، به ملاحظاتی خواهیم پرداخت که در هنگام انتخاب مکانیزم ترسیم برای اپلیکیشن نیاز است در نظر گرفته شود.</p>
<p><a class="p_ident" id="p_Q4EhoqfXYQ" href="#p_Q4EhoqfXYQ" tabindex="-1" role="presentation"></a>این یک سند HTML است که حاوی یک تصویر SVG ساده می باشد.</p>
<pre class="snippet cm-s-default" data-language="text/html" data-sandbox="svg"><a class="c_ident" id="c_AkjyzdSyFr" href="#c_AkjyzdSyFr" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">p</span><span class="cm-tag cm-bracket">></span>Normal HTML here.<span class="cm-tag cm-bracket"></</span><span class="cm-tag">p</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">svg</span> <span class="cm-attribute">xmlns</span>=<span class="cm-string">"http://www.w3.org/2000/svg"</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">circle</span> <span class="cm-attribute">r</span>=<span class="cm-string">"50"</span> <span class="cm-attribute">cx</span>=<span class="cm-string">"50"</span> <span class="cm-attribute">cy</span>=<span class="cm-string">"50"</span> <span class="cm-attribute">fill</span>=<span class="cm-string">"red"</span><span class="cm-tag cm-bracket">/></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">rect</span> <span class="cm-attribute">x</span>=<span class="cm-string">"120"</span> <span class="cm-attribute">y</span>=<span class="cm-string">"5"</span> <span class="cm-attribute">width</span>=<span class="cm-string">"90"</span> <span class="cm-attribute">height</span>=<span class="cm-string">"90"</span>
<span class="cm-attribute">stroke</span>=<span class="cm-string">"blue"</span> <span class="cm-attribute">fill</span>=<span class="cm-string">"none"</span><span class="cm-tag cm-bracket">/></span>
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">svg</span><span class="cm-tag cm-bracket">></span></pre>
<p><a class="p_ident" id="p_t22Lilqrqk" href="#p_t22Lilqrqk" tabindex="-1" role="presentation"></a>خصیصهی <code>xmlns</code> باعث می شود که یک عنصر (به همراه عناصر فرزندش) به "فضای نام XML” متفاوتی تغییر کند. این فضای نام، که توسط یک URL شناسایی می شود، گویشی که در سند با آن صحبت می کنیم را مشخص می کند. برچسبهای <bdo><code><circle></code></bdo> و <bdo><code><rect></code></bdo> که در HTML وجود ندارند، در SVG معنای خاصی دارند – این برچسبها با استفاده از سبک و موقعیتی که در خصیصههایشان مشخص می شود اشکالی را ترسیم می کنند.</p>
<p>این برچسبها عناصر DOM را ایجاد می کنند، درست مثل برچسب های HTML که اسکریپتها می توانند با آنها کار کنند. به عنوان مثال، این کد عنصر <bdo><code><circle></code></bdo> را تغییر می دهد تا رنگش خاکستری شود:</p>
<pre class="snippet cm-s-default" data-language="javascript" data-sandbox="svg"><a class="c_ident" id="c_jx+UOHRvDL" href="#c_jx+UOHRvDL" tabindex="-1" role="presentation"></a><span class="cm-keyword">let</span> <span class="cm-def">circle</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"circle"</span>);
<span class="cm-variable">circle</span>.<span class="cm-property">setAttribute</span>(<span class="cm-string">"fill"</span>, <span class="cm-string">"cyan"</span>);</pre>
<h2><a class="h_ident" id="h_+AGHBXc3eK" href="#h_+AGHBXc3eK" tabindex="-1" role="presentation"></a>عنصر Canvas</h2>
<p><a class="p_ident" id="p_eqFYNfD7Zh" href="#p_eqFYNfD7Zh" tabindex="-1" role="presentation"></a>عناصر گرافیکی canvas را میتوان درون یک عنصر <bdo><code><canvas></code></bdo> ترسیم کرد. می توانید به این عنصر خصیصههای <code>width</code> و <code>height</code> را اضافه کنید تا اندازهی آن به پیکسل تعیین شود.</p>
<p>یک canvas جدید، تهی است به این معنا که یک فضای خالی را در سند نشان می دهد و کاملا شفاف است.</p>
<p><a class="p_ident" id="p_nm7N9cw7XK" href="#p_nm7N9cw7XK" tabindex="-1" role="presentation"></a>برچسب <bdo><code><canvas></code></bdo> برای این منظور تعریف شده است که سبکهای مختلف ترسیم را پشتیبانی کند. برای اینکه به یک محیط ترسیم واقعی دسترسی داشته باشیم ، ابتدا نیاز داریم تا یک بستر (<em>context</em>) تعریف کنیم، شیئی که متدهایش رابط ترسیم را فراهم می سازند. در حال حاضر دو سبک رایج ترسیم پشتیبانی می شود: <bdo><code>"2d"</code></bdo> برای گرافیکهای دوبعدی و “webgl” برای گرافیکهای سه بعدی با رابط OpenGL.</p>
<p><a class="p_ident" id="p_nD+3WfVRHJ" href="#p_nD+3WfVRHJ" tabindex="-1" role="presentation"></a>این کتاب WebGL را پوشش نمی دهد – فقط به دوبعدی خواهیم پرداخت. اما اگر به گرافیک سه بعدی علاقه دارید پیشنهاد می کنم که WebGL را بررسی کنید. در WebGL رابط مستقیمی به سختافزار گرافیکی وجود دارد که به شما امکان می دهد که حتی صحنههای پیچیده را با استفاده از جاوااسکریپت به خوبی رندر یا تولید کنید.</p>
<p><a class="p_ident" id="p_3tmKGT9to5" href="#p_3tmKGT9to5" tabindex="-1" role="presentation"></a>برای ایجاد یک بستر (context) از متد <code>getContext</code> مربوط به <bdo><code><canvas></code></bdo> در DOM استفاده می کنید.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_A0Bt33IRVE" href="#c_A0Bt33IRVE" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">p</span><span class="cm-tag cm-bracket">></span>Before canvas.<span class="cm-tag cm-bracket"></</span><span class="cm-tag">p</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span> <span class="cm-attribute">width</span>=<span class="cm-string">"120"</span> <span class="cm-attribute">height</span>=<span class="cm-string">"60"</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">p</span><span class="cm-tag cm-bracket">></span>After canvas.<span class="cm-tag cm-bracket"></</span><span class="cm-tag">p</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">canvas</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>);
<span class="cm-keyword">let</span> <span class="cm-def">context</span> <span class="cm-operator">=</span> <span class="cm-variable">canvas</span>.<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-variable">context</span>.<span class="cm-property">fillStyle</span> <span class="cm-operator">=</span> <span class="cm-string">"red"</span>;
<span class="cm-variable">context</span>.<span class="cm-property">fillRect</span>(<span class="cm-number">10</span>, <span class="cm-number">10</span>, <span class="cm-number">100</span>, <span class="cm-number">50</span>);
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p><a class="p_ident" id="p_kkW7XWMuex" href="#p_kkW7XWMuex" tabindex="-1" role="presentation"></a>بعد از ایجاد شیء context، در مثال، یک چهارضلعی صد پیکسل در پنجاه پیکسل رسم میشود که مختصات گوشهی بالا-چپ آن برابر <bdo>(10,10)</bdo> است.</p>
<p><a class="p_ident" id="p_nVWZDrvIyF" href="#p_nVWZDrvIyF" tabindex="-1" role="presentation"></a>درست مثل HTML (و SVG)، سیستم مختصاتی که canvas استفاده می کند (0,0) را در گوشهی بالا-چپ قرار می دهد و محور عمودی مثبت، پایین تر از آن در نظر گرفته می شود. بنابراین (10,10) می شود 10 پیکسل به سمت پایین و راست گوشهی بالا-چپ.</p>
<h2 id="fill_stroke"><a class="h_ident" id="h_aZWfSnzHCx" href="#h_aZWfSnzHCx" tabindex="-1" role="presentation"></a>خطوط و سطوح</h2>
<p><a class="p_ident" id="p_nas4S1pI4/" href="#p_nas4S1pI4/" tabindex="-1" role="presentation"></a>در رابط canvas، شکل را می توان پر (fill) کرد، یعنی به مساحتش رنگ یا الگو اختصاص داد، یا می توان دور آن خط کشید (stroke). همین اصطلاحات در SVG هم استفاده می شوند.</p>
<p>متد <code>fillRect</code> یک چهارضلعی را با رنگ پر می کند. این متد ابتدا مختصات طولی و عرضی گوشهی بالا-چپ چهارضلعی را میگیرد، بعد طول و ارتفاع آن را دریافت می کند. یک متد مشابه دیگر به نام <code>strokeRect</code> برای کشیدن خط دور چهارضلعی استفاده می شود.</p>
<p><a class="p_ident" id="p_7Ccns7cfB2" href="#p_7Ccns7cfB2" tabindex="-1" role="presentation"></a>هیچکدام از دو متد پارامتر دیگری دریافت نمی کنند. رنگ مورد نظر و ضخامت خط و مواردی از این دست توسط آرگومان مشخص نمی شوند (که منطقا می بایست انجام می شد) اما در عوض توسط خاصیتهای شیء بستر (context) تعیین می شوند.</p>
<p><a class="p_ident" id="p_GU4T2nIKHw" href="#p_GU4T2nIKHw" tabindex="-1" role="presentation"></a>خاصیت <code>fillStyle</code> سبک پرشدن اشکال را کنترل می کند. می توان آن را با یک رشته که نمایانگر یک رنگ خاص است با استفاده از روش مشخص کردن رنگها در CSS تنظیم کرد.</p>
<p>خاصیت <code>strokeStyle</code> به طور مشابهی کار می کند اما رنگ مشخص شده، برای خط دور شکل استفاده می شود. عرض این خط توسط خاصیت <code>lineWidth</code> مشخص می شود که می تواند شامل هر عدد مثبتی باشد.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_RXlmHDTr07" href="#c_RXlmHDTr07" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">strokeStyle</span> <span class="cm-operator">=</span> <span class="cm-string">"blue"</span>;
<span class="cm-variable">cx</span>.<span class="cm-property">strokeRect</span>(<span class="cm-number">5</span>, <span class="cm-number">5</span>, <span class="cm-number">50</span>, <span class="cm-number">50</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">lineWidth</span> <span class="cm-operator">=</span> <span class="cm-number">5</span>;
<span class="cm-variable">cx</span>.<span class="cm-property">strokeRect</span>(<span class="cm-number">135</span>, <span class="cm-number">5</span>, <span class="cm-number">50</span>, <span class="cm-number">50</span>);
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p><a class="p_ident" id="p_KA/8xZgyQw" href="#p_KA/8xZgyQw" tabindex="-1" role="presentation"></a>زمانی که <code>with</code> و <code>height</code> مشخص نمی شوند، مثل مثال بالا، عنصر canvas طول پیشفرض 300 پیکسل و ارتفاع 150 پیکسل را خواهد گرفت.</p>
<h2><a class="h_ident" id="h_zk6lIPDD3L" href="#h_zk6lIPDD3L" tabindex="-1" role="presentation"></a>مسیرها</h2>
<p>یک مسیر، امتدادی از خطوط است. رابط دوبعد canvas از روش ویژه ای برای توصیف مسیرها استفاده می کند. این کار به طور کامل توسط اثرات جانبی صورت می گیرد. مسیرها مقادیری نیستند که بتوان آن ها را ذخیره کرد یا ارسال نمود. در عوض، اگر می خواهید با مسیرها کار کنید، باید دنبالهای از فراخوانیها را برای توصیف شکل آن داشته باشید.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_q5tyCN7mU3" href="#c_q5tyCN7mU3" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">beginPath</span>();
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">y</span> <span class="cm-operator">=</span> <span class="cm-number">10</span>; <span class="cm-variable">y</span> <span class="cm-operator"><</span> <span class="cm-number">100</span>; <span class="cm-variable">y</span> <span class="cm-operator">+=</span> <span class="cm-number">10</span>) {
<span class="cm-variable">cx</span>.<span class="cm-property">moveTo</span>(<span class="cm-number">10</span>, <span class="cm-variable">y</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">lineTo</span>(<span class="cm-number">90</span>, <span class="cm-variable">y</span>);
}
<span class="cm-variable">cx</span>.<span class="cm-property">stroke</span>();
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p>در مثال بالا مسیری را توسط چند خط افقی ایجاد کرده و با استفاده از متد <code>stroke</code> دور آن خط میکشد. هر قسمتی که با <code>lineTo</code> ایجاد شده است از موقعیت فعلی مسیر شروع می شود. موقعیت مورد نظر معمولا در انتهای قسمت قبلی قرار دارد مگر اینکه <code>moveTo</code> فراخوانی شده باشد. در آن صورت، بخش بعدی از موقعیتی که به <code>moveTo</code> داده شده است شروع می شود.</p>
<p>زمانی که یک مسیر (با متد <code>fill</code>) پر می شود، هر شکل به صورت مجزا پر می شود. یک مسیر می تواند حاوی اشکال متعددی باشد – هر حرکت <code>moveTo</code> یک شکل جدید شروع می کند. اما لازم است که مسیر بسته باشد ) به این معنا که نقطهی شروع و پایانش یکسان باشد( تا بتوان آن را پر کرد. اگر مسیر هنوز بسته نشده است خطی از از نقطهی پایان به نقطهی آغاز وصل می شود و شکلی که توسط یک مسیر بسته ایجاد می شود پر می شود.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_dKfK5v1gw2" href="#c_dKfK5v1gw2" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">beginPath</span>();
<span class="cm-variable">cx</span>.<span class="cm-property">moveTo</span>(<span class="cm-number">50</span>, <span class="cm-number">10</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">lineTo</span>(<span class="cm-number">10</span>, <span class="cm-number">70</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">lineTo</span>(<span class="cm-number">90</span>, <span class="cm-number">70</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">fill</span>();
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p><a class="p_ident" id="p_+ipP1GR/rY" href="#p_+ipP1GR/rY" tabindex="-1" role="presentation"></a>در مثال بالا یک مثلث توپر کشیده می شود. توجه داشته باشید که فقط دو ضلع از مثلث صراحتا ترسیم شده اند. ضلع سوم، از گوشهی پایین-راست تا بالا، به صورت ضمنی است و اگر به مسیر، خطر مرزی (stroke) اختصاص داده می شد، آنجا دیده نمیشد.</p>
<p>شما می توانید متد <code>closePath</code> را نیز استفاده کنید تا صراحتا یک مسیر را ببندید و ضلعی واقعی را به نقطهی شروع رسم کنید. این ضلع در هنگام اختصاص خط مرزی به مسیر رسم می شود.</p>
<h2><a class="h_ident" id="h_4HEJGDEysm" href="#h_4HEJGDEysm" tabindex="-1" role="presentation"></a>خطوط منحنی</h2>
<p>یک مسیر می تواند شامل خطوط منحنی باشد. رسم این خطوط متاسفانه کمی بیشتر کار می برد.</p>
<p>متد <code>quadraticCurveTo</code> یک منحنی را از نقطهی داده شده ترسیم می نماید. برای تعیین میزان انحنای خط، این متد یک نقطهی کنترل و یک نقطهی مقصد را دریافت می کند. این نقطهی کنترل را می توان به عنوان یک خط جذبکننده در نظر گرفت که به خط انحنا می بخشد. خط از میان نقطهی کنترل نخواهد گذشت اما اگر خط مستقیمی بین نقاط ابتدایی و انتهایی رسم شود به سمت نقطهی کنترل انحنا خواهد داشد. مثال زیر این مفهوم را به تصویر می کشد.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_Jq9+Wmbm3J" href="#c_Jq9+Wmbm3J" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">beginPath</span>();
<span class="cm-variable">cx</span>.<span class="cm-property">moveTo</span>(<span class="cm-number">10</span>, <span class="cm-number">90</span>);
<span class="cm-comment">// control=(60,10) goal=(90,90)</span>
<span class="cm-variable">cx</span>.<span class="cm-property">quadraticCurveTo</span>(<span class="cm-number">60</span>, <span class="cm-number">10</span>, <span class="cm-number">90</span>, <span class="cm-number">90</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">lineTo</span>(<span class="cm-number">60</span>, <span class="cm-number">10</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">closePath</span>();
<span class="cm-variable">cx</span>.<span class="cm-property">stroke</span>();
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p><a class="p_ident" id="p_HtMprCoCcX" href="#p_HtMprCoCcX" tabindex="-1" role="presentation"></a>یک منحنی درجه دوم از چپ به راست با مرکز کنترل (60,10) رسم می کنیم و سپس دو خط ضلعی که به سمت آن نقطهی کنترل رسم می شوند و به شروع خط برمیگردند. شکل نتیجه، کمی شبیه به نماد Star Trek (مجموعهی پیشتازان فضا) می شود. می توانید اثر این نقطهی کنترل را مشاهده کنید: خطوط از گوشههای پایینی جدا می شوند و به سمت نقطهی کنترل جهت می گیرند و به سمت نقطهی هدفشان انحنا می یابند.</p>
<p>متد <code>bezierCurveTo</code> منحنی مشابهی را رسم می کند. به جای یک نقطهی کنترل، این متد دارای دو نقطه می باشد – برای هر نقطهی پایانی، یک نقطهی کنترل. در اینجا با طرح مشابهی که عملکرد این نوع منحنی را نشان می دهد آشنا می شویم:</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_88jydjz4KB" href="#c_88jydjz4KB" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">beginPath</span>();
<span class="cm-variable">cx</span>.<span class="cm-property">moveTo</span>(<span class="cm-number">10</span>, <span class="cm-number">90</span>);
<span class="cm-comment">// control1=(10,10) control2=(90,10) goal=(50,90)</span>
<span class="cm-variable">cx</span>.<span class="cm-property">bezierCurveTo</span>(<span class="cm-number">10</span>, <span class="cm-number">10</span>, <span class="cm-number">90</span>, <span class="cm-number">10</span>, <span class="cm-number">50</span>, <span class="cm-number">90</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">lineTo</span>(<span class="cm-number">90</span>, <span class="cm-number">10</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">lineTo</span>(<span class="cm-number">10</span>, <span class="cm-number">10</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">closePath</span>();
<span class="cm-variable">cx</span>.<span class="cm-property">stroke</span>();
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p>دو نقطهی کنترل در اینجا جهت دو قسمت انتهایی منحنی را مشخص می کنند. هر چه بیشتر از نقاط کنترل دور می شویم، درجهی انحنا در آن جهت بیشتر می شود.</p>
<p>کار کردن با این گونه منحنی ها می تواند سخت باشد – همیشه نمی توان به روشنی نقاط کنترل شیئی که قصد رسم آن را دارید پیدا نمود. گاهی اوقات می توان آن ها را محاسبه کرد و گاهی هم باید فقط با آزمایش و خطا آن ها را یافت.</p>
<p>متد <code>arc</code> روشی است برای ترسیم خطی که روی محیط دایرهای شکل انحنا می یابد. این متد یک جفت مختصات برای مرکز قوس، یک شعاع و زوایای شروع و پایان را دریافت می کند.</p>
<p><a class="p_ident" id="p_AAqyKSUJxt" href="#p_AAqyKSUJxt" tabindex="-1" role="presentation"></a>دو پارامتر آخر این امکان را فراهم می سازند که فقط بخشی از دایره را بتوانیم رسم کنیم. زوایا در واحد رادیان اندازهگیری می شوند نه واحد درجه. این یعنی یک دایرهی کامل دارای زاویهی <bdo>2π</bdo> یا <bdo><code>2 * Math.PI</code></bdo> می باشد که تقریبا برابر <bdo>6.28</bdo> است. زاویه از نقطهی سمت راست مرکز دایره شروع به افزایش می یابد و در جهت خلاف عقربههای ساعت حرکت می کند. می توانید از عدد 0 شروع کرده و با عددی بزرگتر از <bdo>2π</bdo> (مثلا 7) رسم یک دایرهی کامل را تکمیل کنید.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_LopNVujEda" href="#c_LopNVujEda" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">beginPath</span>();
<span class="cm-comment">// center=(50,50) radius=40 angle=0 to 7</span>
<span class="cm-variable">cx</span>.<span class="cm-property">arc</span>(<span class="cm-number">50</span>, <span class="cm-number">50</span>, <span class="cm-number">40</span>, <span class="cm-number">0</span>, <span class="cm-number">7</span>);
<span class="cm-comment">// center=(150,50) radius=40 angle=0 to ½π</span>
<span class="cm-variable">cx</span>.<span class="cm-property">arc</span>(<span class="cm-number">150</span>, <span class="cm-number">50</span>, <span class="cm-number">40</span>, <span class="cm-number">0</span>, <span class="cm-number">0.5</span> <span class="cm-operator">*</span> <span class="cm-variable">Math</span>.<span class="cm-property">PI</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">stroke</span>();
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p>تصویر تولید شده شامل خطی است که از سمت راست یک دایرهی کامل (اولین فراخوانی به <code>arc</code>) به سمت راست تصویر یک چهارم دایره (فراخوانی دوم) کشیده شده است. شبیه دیگر متدهای رسم مسیر، خطی که توسط <code>arc</code> ترسیم می شود به قسمت قبلی مسیر متصل می شود. برای جلوگیری از این کار می توانید از <code>moveTo</code> استفاده کنید یا مسیر جدیدی را ترسیم کنید.</p>
<h2 id="pie_chart"><a class="h_ident" id="h_OtyzJdxjCB" href="#h_OtyzJdxjCB" tabindex="-1" role="presentation"></a>رسم یک نمودار کیکی (pie chart)</h2>
<p><a class="p_ident" id="p_zSVzE2WLJi" href="#p_zSVzE2WLJi" tabindex="-1" role="presentation"></a>تصور کنید که به تازگی شغلی در شرکت EconomiCorp Ince پیدا کرده اید و اولین کاری که به شما سپرده می شود این باشد که یک نمودار کیکی برای نتایج رضایتسنجی مشتریان رسم کنید.</p>
<p>متغیر <code>result</code> حاوی آرایهای از اشیاء است که نتایج نظرسنجی را نشان می دهد.</p>
<pre class="snippet cm-s-default" data-language="javascript" data-sandbox="pie"><a class="c_ident" id="c_evimv7LBgO" href="#c_evimv7LBgO" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">results</span> <span class="cm-operator">=</span> [
{<span class="cm-property">name</span>: <span class="cm-string">"Satisfied"</span>, <span class="cm-property">count</span>: <span class="cm-number">1043</span>, <span class="cm-property">color</span>: <span class="cm-string">"lightblue"</span>},
{<span class="cm-property">name</span>: <span class="cm-string">"Neutral"</span>, <span class="cm-property">count</span>: <span class="cm-number">563</span>, <span class="cm-property">color</span>: <span class="cm-string">"lightgreen"</span>},
{<span class="cm-property">name</span>: <span class="cm-string">"Unsatisfied"</span>, <span class="cm-property">count</span>: <span class="cm-number">510</span>, <span class="cm-property">color</span>: <span class="cm-string">"pink"</span>},
{<span class="cm-property">name</span>: <span class="cm-string">"No comment"</span>, <span class="cm-property">count</span>: <span class="cm-number">175</span>, <span class="cm-property">color</span>: <span class="cm-string">"silver"</span>}
];</pre>
<p><a class="p_ident" id="p_2kuSN7rMzf" href="#p_2kuSN7rMzf" tabindex="-1" role="presentation"></a>برای رسم یک نمودار کیکی باید تعدادی برش کیک که هر کدام از یک قوس و دو خط از مرکز آن قوس تشکیل شده اند رسم کنیم. می توانیم زاویهای که توسط هر قوس اشغال می شود را با تقسیم کل دایره (2π) بر مجموع تعداد پاسخها و ضرب آن عدد ( زاویه مربوط به هر پاسخ) در تعداد افرادی که یک گزینهی مشخص را انتخاب کرده اند بدست بیاوریم.</p>
<pre class="snippet cm-s-default" data-language="text/html" data-sandbox="pie"><a class="c_ident" id="c_j6Un5vCZUN" href="#c_j6Un5vCZUN" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span> <span class="cm-attribute">width</span>=<span class="cm-string">"200"</span> <span class="cm-attribute">height</span>=<span class="cm-string">"200"</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-keyword">let</span> <span class="cm-def">total</span> <span class="cm-operator">=</span> <span class="cm-variable">results</span>
.<span class="cm-property">reduce</span>((<span class="cm-def">sum</span>, {<span class="cm-def">count</span>}) <span class="cm-operator">=></span> <span class="cm-variable-2">sum</span> <span class="cm-operator">+</span> <span class="cm-variable-2">count</span>, <span class="cm-number">0</span>);
<span class="cm-comment">// Start at the top</span>
<span class="cm-keyword">let</span> <span class="cm-def">currentAngle</span> <span class="cm-operator">=</span> <span class="cm-operator">-</span><span class="cm-number">0.5</span> <span class="cm-operator">*</span> <span class="cm-variable">Math</span>.<span class="cm-property">PI</span>;
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">result</span> <span class="cm-keyword">of</span> <span class="cm-variable">results</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">sliceAngle</span> <span class="cm-operator">=</span> (<span class="cm-variable">result</span>.<span class="cm-property">count</span> <span class="cm-operator">/</span> <span class="cm-variable">total</span>) <span class="cm-operator">*</span> <span class="cm-number">2</span> <span class="cm-operator">*</span> <span class="cm-variable">Math</span>.<span class="cm-property">PI</span>;
<span class="cm-variable">cx</span>.<span class="cm-property">beginPath</span>();
<span class="cm-comment">// center=100,100, radius=100</span>
<span class="cm-comment">// from current angle, clockwise by slice's angle</span>
<span class="cm-variable">cx</span>.<span class="cm-property">arc</span>(<span class="cm-number">100</span>, <span class="cm-number">100</span>, <span class="cm-number">100</span>,
<span class="cm-variable">currentAngle</span>, <span class="cm-variable">currentAngle</span> <span class="cm-operator">+</span> <span class="cm-variable-2">sliceAngle</span>);
<span class="cm-variable">currentAngle</span> <span class="cm-operator">+=</span> <span class="cm-variable-2">sliceAngle</span>;
<span class="cm-variable">cx</span>.<span class="cm-property">lineTo</span>(<span class="cm-number">100</span>, <span class="cm-number">100</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">fillStyle</span> <span class="cm-operator">=</span> <span class="cm-variable">result</span>.<span class="cm-property">color</span>;
<span class="cm-variable">cx</span>.<span class="cm-property">fill</span>();
}
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p>اما نموداری که اطلاعاتی در مورد هر برش نمایش نمی دهد زیاد کاربردی نیست. لازم است راهی برای رسم متن روی canvas پیدا کنیم.</p>
<h2><a class="h_ident" id="h_QLMSFWXLRO" href="#h_QLMSFWXLRO" tabindex="-1" role="presentation"></a>متن</h2>
<p>در یک بستر (context) ترسیم دو بعدی، متدی به نام <code>fillText</code> و <code>strokeText</code> در دسترس است. متد دوم برای رسم خط مرزی برای حروف می تواند کاربرد داشته باشد اما معمولا متدی که استفاده می شود <code>fillText</code> است. این متد فضای حروف را با سبکی که توسط <code>fillStyle</code> کنونی مشخص می شود، پر می کند.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_I5eI8lFy8O" href="#c_I5eI8lFy8O" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">font</span> <span class="cm-operator">=</span> <span class="cm-string">"28px Georgia"</span>;
<span class="cm-variable">cx</span>.<span class="cm-property">fillStyle</span> <span class="cm-operator">=</span> <span class="cm-string">"fuchsia"</span>;
<span class="cm-variable">cx</span>.<span class="cm-property">fillText</span>(<span class="cm-string">"I can draw text, too!"</span>, <span class="cm-number">10</span>, <span class="cm-number">50</span>);
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p><a class="p_ident" id="p_RkJXbYQyuH" href="#p_RkJXbYQyuH" tabindex="-1" role="presentation"></a>می توانید اندازه، سبک و قلم متن را با خاصیت font مشخص نمایید. در این مثال فقط اندازهی قلم و نام خانوادهی آن مشخص می شود. همچنین برای انتخاب یک سبک می توانید به ابتدای این رشته مقدار <code>italic</code> یا <code>bold</code> را اضافه نمایید.</p>
<p><a class="p_ident" id="p_/7vtAp3Cdi" href="#p_/7vtAp3Cdi" tabindex="-1" role="presentation"></a>دو آرگومان آخر <code>fillText</code> و <code>strokeText</code>، موقعیتی که در آن نوشته ترسیم می شود را مشخص می کنند. به صورت پیشفرض این دو آرگومان موقعیت شروع خط زمینه متن را مشخص می کنند که خطی است که حروف روی آن می ایستند البته بدون در نظر گرفتن قسمتهای بیرونزده در حروفی مثل j یا p. می توانید موقعیت افقی را با تنظیم خاصیت <code>textAlign</code> به <code>"end"</code> یا <code>"center"</code> و موقعیت عمودی را با تنظیم <code>textBaseline</code> به <code>"top"</code> ، <code>"middle"</code> یا <code>"bottom"</code> تغییر دهید.</p>
<p>در قسمت <a href="17_canvas.html#exercise_pie_chart">تمرینها</a> به مشکل افزودن متن به نمودار کیکی باز خواهیم گشت.</p>
<h2><a class="h_ident" id="h_LKMZYi6+po" href="#h_LKMZYi6+po" tabindex="-1" role="presentation"></a>تصاویر</h2>
<p><a class="p_ident" id="p_ytamWk+t5x" href="#p_ytamWk+t5x" tabindex="-1" role="presentation"></a>در گرافیک کامپیوتری بین تصاویر برداری (vector) و تصاویر نقشهبیتی (bitmap) تفاوت قائل می شوند. تصاویر برداری همانهایی هستند که در این فصل به رسم آنها می پرداختیم – یک تصویر را با توصیف اشکالی به شکلی منطقی مشخص می کردیم. تصاویر گرافیکی بیتی، از سوی دیگر، اشکال واقعی را مشخص نمی کنند بلکه با اطلاعات پیکسلها کار می کنند ( ناحیههایی از نقاط رنگ شده).</p>
<p><a class="p_ident" id="p_gNP+fW7luS" href="#p_gNP+fW7luS" tabindex="-1" role="presentation"></a>متد <code>drawImage</code> این امکان را به ما می دهد تا دادههای پیکسلی را روی canvas ترسیم کنیم. این دادههای پیکسلی می توانند ریشه در یک عنصر <bdo><code><img></code></bdo> داشته باشند یا متعلق به canvas دیگری باشند. مثال پیش رو یک عنصر آزاد <bdo><code><img></code></bdo> را ایجاد کرده و یک فایل عکس را درون آن بارگیری می کند. اما نمی تواند عکس مورد مورد نظر را شروع به ترسیم کند چرا که مرورگر ممکن است هنوز آن را بارگیری نکرده باشد. برای حل این مشکل، یک گرداننده برای رخداد <code>"load"</code> ثبت می کنیم تا بعد از بارگیری عکس آن را رسم کند.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_Uzn6msw1dJ" href="#c_Uzn6msw1dJ" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-keyword">let</span> <span class="cm-def">img</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">createElement</span>(<span class="cm-string">"img"</span>);
<span class="cm-variable">img</span>.<span class="cm-property">src</span> <span class="cm-operator">=</span> <span class="cm-string">"img/hat.png"</span>;
<span class="cm-variable">img</span>.<span class="cm-property">addEventListener</span>(<span class="cm-string">"load"</span>, () <span class="cm-operator">=></span> {
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">x</span> <span class="cm-operator">=</span> <span class="cm-number">10</span>; <span class="cm-variable-2">x</span> <span class="cm-operator"><</span> <span class="cm-number">200</span>; <span class="cm-variable-2">x</span> <span class="cm-operator">+=</span> <span class="cm-number">30</span>) {
<span class="cm-variable">cx</span>.<span class="cm-property">drawImage</span>(<span class="cm-variable">img</span>, <span class="cm-variable-2">x</span>, <span class="cm-number">10</span>);
}
});
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p>به صورت پیشفرض، <code>drawImage</code> تصویر را در اندازهی اصلیاش رسم می کند. همچنین می توانید به آن دو آرگومان اضافی ارسال کنید تا طول و عرض متفاوتی داشته باشد.</p>
<p><a class="p_ident" id="p_h+H4z6hrEN" href="#p_h+H4z6hrEN" tabindex="-1" role="presentation"></a>زمانی که به تابع <code>drawImage</code> <em>نه</em> (9) آرگومان ارسال شود، می توان از آن برای ترسیم بخش خاصی از یک عکس استفاده کرد. آرگومان های دوم تا پنجم ناحیهای چهارضلعی شکلی از عکس منبع که باید کپی بشود را مشخص می کنند (x،y،width و height) و آرگومانهای ششم تا نهم ناحیهای (روی canvas) که چهارضلعی مشخص شده قرار است قرار بگیرد را مشخص می کنند.</p>
<p><a class="p_ident" id="p_7mOoCVYaLW" href="#p_7mOoCVYaLW" tabindex="-1" role="presentation"></a>می توان از این متد برای قرار دادن عناصر تصویری متعدد درون یک فایل تصویر (sprite) و ترسیم بخشی مورد نیاز استفاده کرد. به عنوان مثال، تصویر زیر را در اختیار داریم که که شخصیت یک بازی را در حالت های مختلف نشان می دهد.</p><figure><img src="img/player_big.png" alt="Various poses of a game character"></figure>
<p>با ترسیم متوالی حالت شخصیت، می توانیم یک پویانمایی از راه رفتن را به نمایش بگذاریم.</p>
<p>برای متحرکسازی یک تصویر روی یک canvas متد <code>clearRect</code> مفید است. این متد مشابه <code>fillRect</code> عمل می کند با این تفاوت که به جای رنگکردن یک ناحیه با حذف پیکسلهای رسم شدهی قبلی باعث می شود که آن ناحیه شفاف شود.</p>
<p><a class="p_ident" id="p_Vd+DRl/TAz" href="#p_Vd+DRl/TAz" tabindex="-1" role="presentation"></a>می دانیم که در sprite، هر زیرتصویر، دارای 24 پیکسل طول و 30 پیکسل ارتفاع می باشد. کد زیر تصاویر را بارگیری کرده و یک وقفهی زمانی برای رسم فریم بعدی تنظیم می کند:</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_fHdIKXnjfm" href="#c_fHdIKXnjfm" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-keyword">let</span> <span class="cm-def">img</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">createElement</span>(<span class="cm-string">"img"</span>);
<span class="cm-variable">img</span>.<span class="cm-property">src</span> <span class="cm-operator">=</span> <span class="cm-string">"img/player.png"</span>;
<span class="cm-keyword">let</span> <span class="cm-def">spriteW</span> <span class="cm-operator">=</span> <span class="cm-number">24</span>, <span class="cm-def">spriteH</span> <span class="cm-operator">=</span> <span class="cm-number">30</span>;
<span class="cm-variable">img</span>.<span class="cm-property">addEventListener</span>(<span class="cm-string">"load"</span>, () <span class="cm-operator">=></span> {
<span class="cm-keyword">let</span> <span class="cm-def">cycle</span> <span class="cm-operator">=</span> <span class="cm-number">0</span>;
<span class="cm-variable">setInterval</span>(() <span class="cm-operator">=></span> {
<span class="cm-variable">cx</span>.<span class="cm-property">clearRect</span>(<span class="cm-number">0</span>, <span class="cm-number">0</span>, <span class="cm-variable">spriteW</span>, <span class="cm-variable">spriteH</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">drawImage</span>(<span class="cm-variable">img</span>,
<span class="cm-comment">// source rectangle</span>
<span class="cm-variable-2">cycle</span> <span class="cm-operator">*</span> <span class="cm-variable">spriteW</span>, <span class="cm-number">0</span>, <span class="cm-variable">spriteW</span>, <span class="cm-variable">spriteH</span>,
<span class="cm-comment">// destination rectangle</span>
<span class="cm-number">0</span>, <span class="cm-number">0</span>, <span class="cm-variable">spriteW</span>, <span class="cm-variable">spriteH</span>);
<span class="cm-variable-2">cycle</span> <span class="cm-operator">=</span> (<span class="cm-variable-2">cycle</span> <span class="cm-operator">+</span> <span class="cm-number">1</span>) <span class="cm-operator">%</span> <span class="cm-number">8</span>;
}, <span class="cm-number">120</span>);
});
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p><a class="p_ident" id="p_44wLnJbEIx" href="#p_44wLnJbEIx" tabindex="-1" role="presentation"></a>متغیر <code>cycle</code> موقعیت ما را در پویانمایی رصد می کند. در هر فریم، این متغیر افزایش می یابد بعد به بازهی 0 تا 7 دوباره به وسیلهی عملگر باقیمانده بر می گردد . این متغیر بعد برای محاسبه مختصات طولی آن sprite برای حالت فعلی شخصیت در تصویر استفاده می شود.</p>
<h2><a class="h_ident" id="h_y/cuVJheCA" href="#h_y/cuVJheCA" tabindex="-1" role="presentation"></a>تغییر شکل</h2>
<p>چه می شود اگر بخواهیم که شخصیت ما به جای حرکت به راست به سمت چپ حرکت کند؟ البته مجموعهی دیگری از تصاویر را رسم کنیم. اما می توان همچنین canvas را طوری تنظیم کرد که تصاویر را به سمت دیگر رسم کند.</p>
<p>فراخوانی متد <code>scale</code> موجب می شود که هرچیزی که بعد از آن رسم شود تغییر اندازه دهد. این متد دو پارامتر را دریافت می کند، یک پارامتر برای اندازهی افقی و دیگری برای تغییر عمودی.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_Ih3Ewav/dQ" href="#c_Ih3Ewav/dQ" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">scale</span>(<span class="cm-number">3</span>, <span class="cm-number">.5</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">beginPath</span>();
<span class="cm-variable">cx</span>.<span class="cm-property">arc</span>(<span class="cm-number">50</span>, <span class="cm-number">50</span>, <span class="cm-number">40</span>, <span class="cm-number">0</span>, <span class="cm-number">7</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">lineWidth</span> <span class="cm-operator">=</span> <span class="cm-number">3</span>;
<span class="cm-variable">cx</span>.<span class="cm-property">stroke</span>();
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p><a class="p_ident" id="p_I0z4jz5vHW" href="#p_I0z4jz5vHW" tabindex="-1" role="presentation"></a>تغییر اندازه در همهی قسمتهای تصویر رسم شده اعمال می شود شامل ضخامت خط که با توجه به اعداد مشخص شده کشیده یا فشرده می شود. اگر این تغییر با عددی منفی انجام شود باعث می شود که تصویر وارونه شود. این وارونگی نسبت به نقطهی <bdo>(0,0)</bdo> رخ می دهد که به این معنا است که جهت سیستم مختصات نیز وارونه می شود. با اعمال تغییر اندازهی <bdo>-1</bdo>، شکلی در موقعیت طولی 100 رسم شده در جایی قرار می گیرد که سابقا <bdo>-100</bdo> بوده است.</p>
<p><a class="p_ident" id="p_bOFj0o5apk" href="#p_bOFj0o5apk" tabindex="-1" role="presentation"></a>بنابراین برای اینکه یک تصویر را وارونه کنیم، نمی توان فقط <bdo><code>cx.scale(-1,1)</code></bdo> را قبل از فراخوانی <code>drawImage</code> اضافه کرد چرا که این کار باعث می شود که تصویر بیرون از ناحیه canvas قرار گیرد، جایی که دیگر قابل مشاهده نخواهد بود. برای رفع این مشکل می توانید مختصات داده شده به <code>drawImage</code> را تغییر دهید و تصویر را در موقعیت طولی <bdo>-50</bdo> به جای 0 رسم کنید. یک راه حل دیگر هم، که در آن نیازی نیست تغییر در کد ترسیم برای تغییر اندازه اعمال شود، این است که محوری که تغییر اندازه در آن رخ می دهد را تغییر دهیم.</p>
<p>متدهای دیگری در کنار <code>scale</code> وجود دارند که روی سیستم مختصات در canvas اثر می گذارند. می توانید متعاقبا تصاویر رسم شده را به وسیلهی متد <code>rotate</code> بچرخانید یا به وسیله متد <code>translate</code> حرکت دهید. نکتهی جالب – و گیج کننده – این است که این تغییرشکلدادنها انباشته می شوند به این معنا که هر کدام متناسب و با توجه به تغییر شکل قبلی صورت میگیرد.</p>
<p><a class="p_ident" id="p_fH4LcSPvAz" href="#p_fH4LcSPvAz" tabindex="-1" role="presentation"></a>بنابراین اگر دوبار و هر بار به اندازهی 10 پیکسل به صورت افقی تصویر را جابجا کنیم (با translate)، همه چیز 20 پیکسل در سمت راست رسم می شوند. اگر ابتدا مرکز سیستم مختصات را به نقطهی <bdo>(50,50)</bdo> منتقل کنیم سپس 20 درجه (حدود <bdo>0.1π</bdo> رادیان) بچرخانیم، آن چرخش حول نقطهی <bdo>(50,50)</bdo> رخ خواهد داد.</p><figure><img src="img/transform.svg" alt="Stacking transformations"></figure>
<p><a class="p_ident" id="p_7Yqjs/UZb+" href="#p_7Yqjs/UZb+" tabindex="-1" role="presentation"></a>اما اگر <em>ابتداد</em> 20 درجه چرخش ایجاد کنیم سپس به انتقال به مقدار <bdo>(50, 50)</bdo> بپردازیم، انتقال در سیستم مختصات چرخانده شده اعمال می شود و درنتیجه جهت متفاوت می شود. ترتیبی که تغییرشکلها در آن اعمال می شوند مهم هستند.</p>
<p><a class="p_ident" id="p_EfatjsUqKY" href="#p_EfatjsUqKY" tabindex="-1" role="presentation"></a>برای وارونه کردن یک تصویر حول خط عمودی در یک نقطهی طولی داده شده (x)، می توان به صورت زیر عمل کرد:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_gPWtMqSBLU" href="#c_gPWtMqSBLU" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">flipHorizontally</span>(<span class="cm-def">context</span>, <span class="cm-def">around</span>) {
<span class="cm-variable-2">context</span>.<span class="cm-property">translate</span>(<span class="cm-variable-2">around</span>, <span class="cm-number">0</span>);
<span class="cm-variable-2">context</span>.<span class="cm-property">scale</span>(<span class="cm-operator">-</span><span class="cm-number">1</span>, <span class="cm-number">1</span>);
<span class="cm-variable-2">context</span>.<span class="cm-property">translate</span>(<span class="cm-operator">-</span><span class="cm-variable-2">around</span>, <span class="cm-number">0</span>);
}</pre>
<p><a class="p_ident" id="p_ncCglwrZ1o" href="#p_ncCglwrZ1o" tabindex="-1" role="presentation"></a>ما محور y را به جایی که قصد داریم انعکاس آنجا رخ دهد منتقل می کنیم، تصویر را وارونه می کنیم، و در نهایت محور y را به جای مناسب خودش در فضای وارونهشده برمی گردانیم. تصویر زیر مشخص می کند چرا این روش درست کار می کند:</p><figure><img src="img/mirror.svg" alt="Mirroring around a vertical line"></figure>
<p><a class="p_ident" id="p_L7QmOMMI3n" href="#p_L7QmOMMI3n" tabindex="-1" role="presentation"></a>این تصویر سیستم های مختصات را قبل و بعد از انجام وارونگی نسبت به خط مرکزی نشان می دهد. مثلثها عددگذاری شده اند تا هر گام را نشان دهند. اگر یک مثلث را در موقعیت طولی مثبتی رسم می کردیم، به صورت پیش فرض در جایی قرار می گرفت که مثلث شماره 1 قرار دارد. فراخوانی ابتدایی <code>flipHorizontally</code> موجب انتقال به سمت راست می شود، که ما را به مثلث شماره 2 می رساند. بعد با تغییر اندازه و وارونهکردن مثلث به موقعیت 3 می رسد. این جایی نیست که با وارونه شدن نسبت به خط داده شده می بایست قرار می گرفت. فراخوانی دوم به تابع <code>translate</code> مشکل را حل می کند – این متد جابجایی اولیه را لغو کرده و موجب می شود مثلث 4 درست جایی که باید ظاهر شود.</p>
<p><a class="p_ident" id="p_hRoUnX6zLl" href="#p_hRoUnX6zLl" tabindex="-1" role="presentation"></a>اکنون می توانیم یک کاراکتر وارونه را در موقعیت <bdo>(100,0)</bdo> به وسیلهی وارونهکردن محیط نسبت به مرکز عمودی کاراکتر رسم کنیم.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_dmV/R5ifO7" href="#c_dmV/R5ifO7" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-keyword">let</span> <span class="cm-def">img</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">createElement</span>(<span class="cm-string">"img"</span>);
<span class="cm-variable">img</span>.<span class="cm-property">src</span> <span class="cm-operator">=</span> <span class="cm-string">"img/player.png"</span>;
<span class="cm-keyword">let</span> <span class="cm-def">spriteW</span> <span class="cm-operator">=</span> <span class="cm-number">24</span>, <span class="cm-def">spriteH</span> <span class="cm-operator">=</span> <span class="cm-number">30</span>;
<span class="cm-variable">img</span>.<span class="cm-property">addEventListener</span>(<span class="cm-string">"load"</span>, () <span class="cm-operator">=></span> {
<span class="cm-variable">flipHorizontally</span>(<span class="cm-variable">cx</span>, <span class="cm-number">100</span> <span class="cm-operator">+</span> <span class="cm-variable">spriteW</span> <span class="cm-operator">/</span> <span class="cm-number">2</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">drawImage</span>(<span class="cm-variable">img</span>, <span class="cm-number">0</span>, <span class="cm-number">0</span>, <span class="cm-variable">spriteW</span>, <span class="cm-variable">spriteH</span>,
<span class="cm-number">100</span>, <span class="cm-number">0</span>, <span class="cm-variable">spriteW</span>, <span class="cm-variable">spriteH</span>);
});
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<h2><a class="h_ident" id="h_wpD82DhfXE" href="#h_wpD82DhfXE" tabindex="-1" role="presentation"></a>ذخیره و حذف تغییر شکلها</h2>
<p>دگرگونیها یا تغییر شکلهای ایجاد شده باقی می مانند. هرچیزی که بعد از شخصیت وارونهشده رسم می کنیم نیز وارونه می شود. ممکن است این خواستهی ما نباشد.</p>
<p>می توان دگرگونی فعلی را ذخیره کرد، به چندین ترسیم و دگرگونی دیگر پرداخت و سپس دگرگونی ذخیره شده را بازگرداند. این کار معمولا برای تابعی که به صورت موقت مختصات سیستم را تغییر می دهد مناسب است. ابتدا، هر تغییر شکلی که کد فراخواننده تابع استفاده می کرد را ذخیره می کنیم. بعد تابع کارش را انجام می دهد (در وضعیت دگرگونی موجود)، احتمالا دگرگونیهای بیشتری اعمال می کند. و در نهایت، به دگرگونیای که با آن شروع کردیم باز می گردیم.</p>
<p>متدهای <code>save</code> و <code>restore</code> روی بستر canvas دوبعدی مدیریت این دگرگونی را به عهده می گیرند. از نظر مفهومی این متدها یک پشته از حالت های دگرگونی را نگه می دارند. زمانی که <code>save</code> را فراخوانی می کنید، حالت فعلی درون پشته <code>push</code> می شود و زمانی که <code>restore</code> را فراخوانی می کنید، وضعیت بالای پشته برداشته شده و به عنوان بستر دگرگونی فعلی استفاده می شود. می توانید همچنین <code>resetTransform</code> را فراخوانی کنید تا کل دگرگونی را بازنشانی کنید.</p>
<p>تابع <code>branch</code> در مثال پیش رو به شما نشان می دهد که چه کاری می توانید با یک تابع که دگرگونی را تغییر داده و بعد یک تابع دیگر (در اینجا خودش) را فراخوانی می کند بکنید، که به ترسیم با دگرگونی دادهشده ادامه می دهد.</p>
<p><a class="p_ident" id="p_/l27zqXOfi" href="#p_/l27zqXOfi" tabindex="-1" role="presentation"></a>این تابع یک شکل درختگونه با یک خط رسم می کند و مرکز دستگاه مختصات را به پایان خط منتقل می کند و خودش را دو مرتبه فراخوانی می کند- اول به سمت چپ می چرخد و بعد به راست. با هر بار فراخوانی طول شاخهی کشیده شده کوتاه می شود و فراخوانی بازگشتی زمانی که طول به زیر 8 برسد متوقف می شود.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_TvoVOvq541" href="#c_TvoVOvq541" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span> <span class="cm-attribute">width</span>=<span class="cm-string">"600"</span> <span class="cm-attribute">height</span>=<span class="cm-string">"300"</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-keyword">function</span> <span class="cm-def">branch</span>(<span class="cm-def">length</span>, <span class="cm-def">angle</span>, <span class="cm-def">scale</span>) {
<span class="cm-variable">cx</span>.<span class="cm-property">fillRect</span>(<span class="cm-number">0</span>, <span class="cm-number">0</span>, <span class="cm-number">1</span>, <span class="cm-variable-2">length</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">length</span> <span class="cm-operator"><</span> <span class="cm-number">8</span>) <span class="cm-keyword">return</span>;
<span class="cm-variable">cx</span>.<span class="cm-property">save</span>();
<span class="cm-variable">cx</span>.<span class="cm-property">translate</span>(<span class="cm-number">0</span>, <span class="cm-variable-2">length</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">rotate</span>(<span class="cm-operator">-</span><span class="cm-variable-2">angle</span>);
<span class="cm-variable">branch</span>(<span class="cm-variable-2">length</span> <span class="cm-operator">*</span> <span class="cm-variable-2">scale</span>, <span class="cm-variable-2">angle</span>, <span class="cm-variable-2">scale</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">rotate</span>(<span class="cm-number">2</span> <span class="cm-operator">*</span> <span class="cm-variable-2">angle</span>);
<span class="cm-variable">branch</span>(<span class="cm-variable-2">length</span> <span class="cm-operator">*</span> <span class="cm-variable-2">scale</span>, <span class="cm-variable-2">angle</span>, <span class="cm-variable-2">scale</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">restore</span>();
}
<span class="cm-variable">cx</span>.<span class="cm-property">translate</span>(<span class="cm-number">300</span>, <span class="cm-number">0</span>);
<span class="cm-variable">branch</span>(<span class="cm-number">60</span>, <span class="cm-number">0.5</span>, <span class="cm-number">0.8</span>);
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p>اگر فراخوانیهای <code>save</code> و <code>restore</code> نمی بودند، فراخوانی بازگشتی دوم به <code>branch</code> موجب می شد که موقعیت و چرخش معادل خروجی اولی فراخوانی بشود. نتیجه به شاخهی فعلی متصل نمی شد اما به جای اتصال به درونی ترین شاخه، راست ترین شاخه که با اولین فراخوانی رسم شده بود متصل می شد. شکل نتیجه ممکن بود جالب شود ولی قطعا یک درخت نمی شود.</p>
<h2 id="canvasdisplay"><a class="h_ident" id="h_XpWM2uKPG8" href="#h_XpWM2uKPG8" tabindex="-1" role="presentation"></a>بازگشت به بازی</h2>
<p>اکنون به اندازهی کافی در مورد رسم روی canvas می دانیم تا بتوانیم روی سیستم نمایش مبتنی بر canvas برای بازی <a href="16_game.html">فصل قبل</a> کار کنیم. سیستم نمایش جدید فقط شامل مستطیل های رنگی نخواهد بود. بلکه با استفاده از <code>drawImage</code> تصاویری را رسم می کنیم که عناصر بازی را به تصویر بکشند.</p>
<p>یک شیء نمایش دیگری به نام <code>CanvasDisplay</code> تعریف می کنیم، که رابطهای مثل <code>DOMDisplay</code> را از <a href="16_game.html#domdisplay">فصل 16</a> مثل متدهای <code>syncState</code> و <code>clear</code> را پشتیبانی می کند.</p>
<p><a class="p_ident" id="p_UDTiKJBEvt" href="#p_UDTiKJBEvt" tabindex="-1" role="presentation"></a>شیء ما اطلاعات بیشتری را نسبت به <code>DOMDisplay</code> دریافت می کند . به جای استفاده از موقعیت scroll مربوط به عنصر DOM، میدان دید (viewport) خودش را مدیریت می کند که قسمتی از مرحله که دیده می شود را مشخص می کند. و در آخر، یک خاصیت <code>flipPlayer</code> خواهد داشت تا حتی زمانیکه بازیکن ایستاده است، جهت صورتش بر اساس آخرین حرکت تنظیم شود.</p>
<pre class="snippet cm-s-default" data-language="javascript" data-sandbox="game"><a class="c_ident" id="c_0YCIeiiRNE" href="#c_0YCIeiiRNE" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">CanvasDisplay</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">parent</span>, <span class="cm-def">level</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">canvas</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">createElement</span>(<span class="cm-string">"canvas"</span>);
<span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>.<span class="cm-property">width</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">min</span>(<span class="cm-number">600</span>, <span class="cm-variable-2">level</span>.<span class="cm-property">width</span> <span class="cm-operator">*</span> <span class="cm-variable">scale</span>);
<span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>.<span class="cm-property">height</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">min</span>(<span class="cm-number">450</span>, <span class="cm-variable-2">level</span>.<span class="cm-property">height</span> <span class="cm-operator">*</span> <span class="cm-variable">scale</span>);
<span class="cm-variable-2">parent</span>.<span class="cm-property">appendChild</span>(<span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>);
<span class="cm-keyword">this</span>.<span class="cm-property">cx</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>.<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-keyword">this</span>.<span class="cm-property">flipPlayer</span> <span class="cm-operator">=</span> <span class="cm-atom">false</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">viewport</span> <span class="cm-operator">=</span> {
<span class="cm-property">left</span>: <span class="cm-number">0</span>,
<span class="cm-property">top</span>: <span class="cm-number">0</span>,
<span class="cm-property">width</span>: <span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>.<span class="cm-property">width</span> <span class="cm-operator">/</span> <span class="cm-variable">scale</span>,
<span class="cm-property">height</span>: <span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>.<span class="cm-property">height</span> <span class="cm-operator">/</span> <span class="cm-variable">scale</span>
};
}
<span class="cm-property">clear</span>() {
<span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>.<span class="cm-property">remove</span>();
}
}</pre>
<p>متد <code>syncState</code> ابتدا یک میداندید جدید را محاسبه می کند و سپس صحنهی بازی را در موقعیت مناسب رسم می کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" data-sandbox="game"><a class="c_ident" id="c_cERhn3J5yx" href="#c_cERhn3J5yx" tabindex="-1" role="presentation"></a><span class="cm-variable">CanvasDisplay</span>.<span class="cm-property">prototype</span>.<span class="cm-property">syncState</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">state</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">updateViewport</span>(<span class="cm-variable-2">state</span>);
<span class="cm-keyword">this</span>.<span class="cm-property">clearDisplay</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">status</span>);
<span class="cm-keyword">this</span>.<span class="cm-property">drawBackground</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">level</span>);
<span class="cm-keyword">this</span>.<span class="cm-property">drawActors</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">actors</span>);
};</pre>
<p><a class="p_ident" id="p_a4ZjSo5zki" href="#p_a4ZjSo5zki" tabindex="-1" role="presentation"></a>برخلاف <code>DOMDisplay</code> ، در این سبک نیازی نیست که پسزمینه با هر بار به روز رسانی از نو ترسیم شود. به دلیل اینکه اشکال روی بوم(canvas) همان پیکسلها هستند، بعد از این که آن ها را ترسیم کردیم، راه خوبی برای حرکت دادن (یا حذفشان) وجود ندارد. تنها راه به روز رسانی canvas نمایش، پاک کردن و از نو رسم کردن صحنه است. ممکن است scroll کرده باشیم، که موجب می شود پسزمینه در موقعیت متفاوتی قرار بگیرد.</p>
<p><a class="p_ident" id="p_3sB2GRI6F/" href="#p_3sB2GRI6F/" tabindex="-1" role="presentation"></a>متد <code>updateViewport</code> شبیه به متد <code>scrollPlayerIntoView</code> مربوط به شیء <code>DOMDisplay</code> می باشد. این متد بررسی می کند که بازیکن به لبهی صفحه نزدیک شده باشد که در آن صورت میداندید (viewport) را حرکت می دهد.</p>
<pre class="snippet cm-s-default" data-language="javascript" data-sandbox="game"><a class="c_ident" id="c_OjMIL2K7Ii" href="#c_OjMIL2K7Ii" tabindex="-1" role="presentation"></a><span class="cm-variable">CanvasDisplay</span>.<span class="cm-property">prototype</span>.<span class="cm-property">updateViewport</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">state</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">view</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">viewport</span>, <span class="cm-def">margin</span> <span class="cm-operator">=</span> <span class="cm-variable-2">view</span>.<span class="cm-property">width</span> <span class="cm-operator">/</span> <span class="cm-number">3</span>;
<span class="cm-keyword">let</span> <span class="cm-def">player</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">player</span>;
<span class="cm-keyword">let</span> <span class="cm-def">center</span> <span class="cm-operator">=</span> <span class="cm-variable-2">player</span>.<span class="cm-property">pos</span>.<span class="cm-property">plus</span>(<span class="cm-variable-2">player</span>.<span class="cm-property">size</span>.<span class="cm-property">times</span>(<span class="cm-number">0.5</span>));
<span class="cm-keyword">if</span> (<span class="cm-variable-2">center</span>.<span class="cm-property">x</span> <span class="cm-operator"><</span> <span class="cm-variable-2">view</span>.<span class="cm-property">left</span> <span class="cm-operator">+</span> <span class="cm-variable-2">margin</span>) {
<span class="cm-variable-2">view</span>.<span class="cm-property">left</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">max</span>(<span class="cm-variable-2">center</span>.<span class="cm-property">x</span> <span class="cm-operator">-</span> <span class="cm-variable-2">margin</span>, <span class="cm-number">0</span>);
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">center</span>.<span class="cm-property">x</span> <span class="cm-operator">></span> <span class="cm-variable-2">view</span>.<span class="cm-property">left</span> <span class="cm-operator">+</span> <span class="cm-variable-2">view</span>.<span class="cm-property">width</span> <span class="cm-operator">-</span> <span class="cm-variable-2">margin</span>) {
<span class="cm-variable-2">view</span>.<span class="cm-property">left</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">min</span>(<span class="cm-variable-2">center</span>.<span class="cm-property">x</span> <span class="cm-operator">+</span> <span class="cm-variable-2">margin</span> <span class="cm-operator">-</span> <span class="cm-variable-2">view</span>.<span class="cm-property">width</span>,
<span class="cm-variable-2">state</span>.<span class="cm-property">level</span>.<span class="cm-property">width</span> <span class="cm-operator">-</span> <span class="cm-variable-2">view</span>.<span class="cm-property">width</span>);
}
<span class="cm-keyword">if</span> (<span class="cm-variable-2">center</span>.<span class="cm-property">y</span> <span class="cm-operator"><</span> <span class="cm-variable-2">view</span>.<span class="cm-property">top</span> <span class="cm-operator">+</span> <span class="cm-variable-2">margin</span>) {
<span class="cm-variable-2">view</span>.<span class="cm-property">top</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">max</span>(<span class="cm-variable-2">center</span>.<span class="cm-property">y</span> <span class="cm-operator">-</span> <span class="cm-variable-2">margin</span>, <span class="cm-number">0</span>);
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">center</span>.<span class="cm-property">y</span> <span class="cm-operator">></span> <span class="cm-variable-2">view</span>.<span class="cm-property">top</span> <span class="cm-operator">+</span> <span class="cm-variable-2">view</span>.<span class="cm-property">height</span> <span class="cm-operator">-</span> <span class="cm-variable-2">margin</span>) {
<span class="cm-variable-2">view</span>.<span class="cm-property">top</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">min</span>(<span class="cm-variable-2">center</span>.<span class="cm-property">y</span> <span class="cm-operator">+</span> <span class="cm-variable-2">margin</span> <span class="cm-operator">-</span> <span class="cm-variable-2">view</span>.<span class="cm-property">height</span>,
<span class="cm-variable-2">state</span>.<span class="cm-property">level</span>.<span class="cm-property">height</span> <span class="cm-operator">-</span> <span class="cm-variable-2">view</span>.<span class="cm-property">height</span>);
}
};</pre>
<p>فراخوانی متدهای <bdo><code>Math.max</code></bdo> و <bdo><code>Math.min</code></bdo> موجب می شود اطمینان کنیم که فضای خالی خارج از طرح مرحله به وجود نیاید. <bdo><code>Math.max(x, 0)</code></bdo> باعث می شود که عدد تولیدی کمتر از صفر نباشد. <bdo><code>Math.min</code></bdo> به طور مشابه گارانتی می کند که یک مقدار کمتر از مرز مشخصی بماند.</p>
<p>در زمان پاک کردن صفحه، از رنگ متفاوتی بسته به اینکه بازی را برنده شده باشیم ( رنگی روشن تر) یا باخته باشیم (تاریکتر) استفاده می کنیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" data-sandbox="game"><a class="c_ident" id="c_P43rIXnt0B" href="#c_P43rIXnt0B" tabindex="-1" role="presentation"></a><span class="cm-variable">CanvasDisplay</span>.<span class="cm-property">prototype</span>.<span class="cm-property">clearDisplay</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">status</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">status</span> <span class="cm-operator">==</span> <span class="cm-string">"won"</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">cx</span>.<span class="cm-property">fillStyle</span> <span class="cm-operator">=</span> <span class="cm-string">"rgb(68, 191, 255)"</span>;
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">status</span> <span class="cm-operator">==</span> <span class="cm-string">"lost"</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">cx</span>.<span class="cm-property">fillStyle</span> <span class="cm-operator">=</span> <span class="cm-string">"rgb(44, 136, 214)"</span>;
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">this</span>.<span class="cm-property">cx</span>.<span class="cm-property">fillStyle</span> <span class="cm-operator">=</span> <span class="cm-string">"rgb(52, 166, 251)"</span>;
}
<span class="cm-keyword">this</span>.<span class="cm-property">cx</span>.<span class="cm-property">fillRect</span>(<span class="cm-number">0</span>, <span class="cm-number">0</span>,
<span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>.<span class="cm-property">width</span>, <span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>.<span class="cm-property">height</span>);
};</pre>
<p>برای رسم یک پسزمینه با استفاده از همان ترفندی که در متد <code>touches</code> در <a href="16_game.html#touches">فصل قبل</a> استفاده کردیم به سراغ قطعات مربعی که در میداندید فعلی قرار می گیرند می رویم.</p>
<pre class="snippet cm-s-default" data-language="javascript" data-sandbox="game"><a class="c_ident" id="c_UYqDAMiEp6" href="#c_UYqDAMiEp6" tabindex="-1" role="presentation"></a><span class="cm-keyword">let</span> <span class="cm-def">otherSprites</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">createElement</span>(<span class="cm-string">"img"</span>);
<span class="cm-variable">otherSprites</span>.<span class="cm-property">src</span> <span class="cm-operator">=</span> <span class="cm-string">"img/sprites.png"</span>;
<span class="cm-variable">CanvasDisplay</span>.<span class="cm-property">prototype</span>.<span class="cm-property">drawBackground</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">level</span>) {
<span class="cm-keyword">let</span> {<span class="cm-def">left</span>, <span class="cm-def">top</span>, <span class="cm-def">width</span>, <span class="cm-def">height</span>} <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">viewport</span>;
<span class="cm-keyword">let</span> <span class="cm-def">xStart</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">floor</span>(<span class="cm-variable-2">left</span>);
<span class="cm-keyword">let</span> <span class="cm-def">xEnd</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">ceil</span>(<span class="cm-variable-2">left</span> <span class="cm-operator">+</span> <span class="cm-variable-2">width</span>);
<span class="cm-keyword">let</span> <span class="cm-def">yStart</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">floor</span>(<span class="cm-variable-2">top</span>);
<span class="cm-keyword">let</span> <span class="cm-def">yEnd</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">ceil</span>(<span class="cm-variable-2">top</span> <span class="cm-operator">+</span> <span class="cm-variable-2">height</span>);
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">y</span> <span class="cm-operator">=</span> <span class="cm-variable-2">yStart</span>; <span class="cm-variable-2">y</span> <span class="cm-operator"><</span> <span class="cm-variable-2">yEnd</span>; <span class="cm-variable-2">y</span><span class="cm-operator">++</span>) {
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">x</span> <span class="cm-operator">=</span> <span class="cm-variable-2">xStart</span>; <span class="cm-variable-2">x</span> <span class="cm-operator"><</span> <span class="cm-variable-2">xEnd</span>; <span class="cm-variable-2">x</span><span class="cm-operator">++</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">tile</span> <span class="cm-operator">=</span> <span class="cm-variable-2">level</span>.<span class="cm-property">rows</span>[<span class="cm-variable-2">y</span>][<span class="cm-variable-2">x</span>];
<span class="cm-keyword">if</span> (<span class="cm-variable-2">tile</span> <span class="cm-operator">==</span> <span class="cm-string">"empty"</span>) <span class="cm-keyword">continue</span>;
<span class="cm-keyword">let</span> <span class="cm-def">screenX</span> <span class="cm-operator">=</span> (<span class="cm-variable-2">x</span> <span class="cm-operator">-</span> <span class="cm-variable-2">left</span>) <span class="cm-operator">*</span> <span class="cm-variable">scale</span>;
<span class="cm-keyword">let</span> <span class="cm-def">screenY</span> <span class="cm-operator">=</span> (<span class="cm-variable-2">y</span> <span class="cm-operator">-</span> <span class="cm-variable-2">top</span>) <span class="cm-operator">*</span> <span class="cm-variable">scale</span>;
<span class="cm-keyword">let</span> <span class="cm-def">tileX</span> <span class="cm-operator">=</span> <span class="cm-variable-2">tile</span> <span class="cm-operator">==</span> <span class="cm-string">"lava"</span> <span class="cm-operator">?</span> <span class="cm-variable">scale</span> : <span class="cm-number">0</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">cx</span>.<span class="cm-property">drawImage</span>(<span class="cm-variable">otherSprites</span>,
<span class="cm-variable-2">tileX</span>, <span class="cm-number">0</span>, <span class="cm-variable">scale</span>, <span class="cm-variable">scale</span>,
<span class="cm-variable-2">screenX</span>, <span class="cm-variable-2">screenY</span>, <span class="cm-variable">scale</span>, <span class="cm-variable">scale</span>);
}
}
};</pre>
<p>قطعاتی غیر تهی توسط <code>drawImage</code> رسم شده اند. تصویر <code>otherSprites</code> حاوی عکسهای عناصر بازی به جز شخصیت اصلی می باشد. شامل از چپ به راست کاشی دیوار، کاشی گدازه، و sprite یک سکه.</p><figure><img src="img/sprites_big.png" alt="Sprites for our game"></figure>
<p><a class="p_ident" id="p_tLSMzjY7gw" href="#p_tLSMzjY7gw" tabindex="-1" role="presentation"></a>ابعداد کاشیهای پسزمینه 20 در 20 می باشد به دلیل اینکه در <code>DOMDisplay</code> از همین ابعاد استفاده کرده ایم. بنابراین میزان جابجایی (offset) برای کاشیهای گدازه 20 است (مقدار متغیر <code>scale</code>) و این مقدار برای کاشیهای دیوار 0 خواهد بود.</p>
<p>نیازی نیست که برای بارگیری sprite تصویر زمانی منتظر بمانیم. فراخوانی <code>drawImage</code> با تصویری که هنوز بارگیری نشده نتیجهای نخواهد داشت. بنابراین وقتی در حال بارگیری تصاویر هستیم، ممکن است برای رسم چند فریم ابتدایی در بازی با مشکل روبرو شویم؛ اما این مشکل جدی نیست زیرا تصویر آن به آن به روز می شود و به محض اینکه بارگیری تمام شود صحنهی بازی تکمیل می شود.</p>
<p><a class="p_ident" id="p_m+SIeYCK3k" href="#p_m+SIeYCK3k" tabindex="-1" role="presentation"></a>تصویر آدمکی که پیشتر نمایش داده شد را برای نمایش بازیکن استفاده خواهیم کرد. کدی که وظیفهی رسم آن را دارد باید sprite و جهت صورت مناسبی را با توجه به حرکت فعلی بازیکن انتخاب کند. هشت sprite اول نمایانگر راهرفتن شخصیت هستند. زمانی که بازیگر روی زمین راه می رود، با توجه به زمان، بین این تصاویر انتخاب می کنیم. قصد داریم هر 60 هزارم ثانیه فریم را تغییر دهیم در نتیجه زمان در ابتدا بر 60 تقسیم می گردد. در زمانی که بازیکن در حالت ایستاده است، نهمین sprite را رسم می کنیم. در زمان انجام پرش، که وقتی سرعت عمودی صفر نباشد تشخیص داده می شود، از دهمین، راستترین تصویر sprite استفاده می کنیم.</p>
<p><a class="p_ident" id="p_UB/PvjBjwT" href="#p_UB/PvjBjwT" tabindex="-1" role="presentation"></a>به دلیل این که spriteها اندکی عریض تر از شیء بازیکن هستند (24 به جای 16) ، -که برای افزودن کمی فضا برای پاها و دستان شخصیت می باشد — متد باید مختصات طولی و طول (width) را با مقدار داده شده (<code>playerXOverlap</code>) تنظیم کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" data-sandbox="game"><a class="c_ident" id="c_T61uCWX04T" href="#c_T61uCWX04T" tabindex="-1" role="presentation"></a><span class="cm-keyword">let</span> <span class="cm-def">playerSprites</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">createElement</span>(<span class="cm-string">"img"</span>);
<span class="cm-variable">playerSprites</span>.<span class="cm-property">src</span> <span class="cm-operator">=</span> <span class="cm-string">"img/player.png"</span>;
<span class="cm-keyword">const</span> <span class="cm-def">playerXOverlap</span> <span class="cm-operator">=</span> <span class="cm-number">4</span>;
<span class="cm-variable">CanvasDisplay</span>.<span class="cm-property">prototype</span>.<span class="cm-property">drawPlayer</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">player</span>, <span class="cm-def">x</span>, <span class="cm-def">y</span>,
<span class="cm-def">width</span>, <span class="cm-def">height</span>){
<span class="cm-variable-2">width</span> <span class="cm-operator">+=</span> <span class="cm-variable">playerXOverlap</span> <span class="cm-operator">*</span> <span class="cm-number">2</span>;
<span class="cm-variable-2">x</span> <span class="cm-operator">-=</span> <span class="cm-variable">playerXOverlap</span>;
<span class="cm-keyword">if</span> (<span class="cm-variable-2">player</span>.<span class="cm-property">speed</span>.<span class="cm-property">x</span> <span class="cm-operator">!=</span> <span class="cm-number">0</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">flipPlayer</span> <span class="cm-operator">=</span> <span class="cm-variable-2">player</span>.<span class="cm-property">speed</span>.<span class="cm-property">x</span> <span class="cm-operator"><</span> <span class="cm-number">0</span>;
}
<span class="cm-keyword">let</span> <span class="cm-def">tile</span> <span class="cm-operator">=</span> <span class="cm-number">8</span>;
<span class="cm-keyword">if</span> (<span class="cm-variable-2">player</span>.<span class="cm-property">speed</span>.<span class="cm-property">y</span> <span class="cm-operator">!=</span> <span class="cm-number">0</span>) {
<span class="cm-variable-2">tile</span> <span class="cm-operator">=</span> <span class="cm-number">9</span>;
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">player</span>.<span class="cm-property">speed</span>.<span class="cm-property">x</span> <span class="cm-operator">!=</span> <span class="cm-number">0</span>) {
<span class="cm-variable-2">tile</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">floor</span>(<span class="cm-variable">Date</span>.<span class="cm-property">now</span>() <span class="cm-operator">/</span> <span class="cm-number">60</span>) <span class="cm-operator">%</span> <span class="cm-number">8</span>;
}
<span class="cm-keyword">this</span>.<span class="cm-property">cx</span>.<span class="cm-property">save</span>();
<span class="cm-keyword">if</span> (<span class="cm-keyword">this</span>.<span class="cm-property">flipPlayer</span>) {
<span class="cm-variable">flipHorizontally</span>(<span class="cm-keyword">this</span>.<span class="cm-property">cx</span>, <span class="cm-variable-2">x</span> <span class="cm-operator">+</span> <span class="cm-variable-2">width</span> <span class="cm-operator">/</span> <span class="cm-number">2</span>);
}
<span class="cm-keyword">let</span> <span class="cm-def">tileX</span> <span class="cm-operator">=</span> <span class="cm-variable-2">tile</span> <span class="cm-operator">*</span> <span class="cm-variable-2">width</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">cx</span>.<span class="cm-property">drawImage</span>(<span class="cm-variable">playerSprites</span>, <span class="cm-variable-2">tileX</span>, <span class="cm-number">0</span>, <span class="cm-variable-2">width</span>, <span class="cm-variable-2">height</span>,
<span class="cm-variable-2">x</span>, <span class="cm-variable-2">y</span>, <span class="cm-variable-2">width</span>, <span class="cm-variable-2">height</span>);
<span class="cm-keyword">this</span>.<span class="cm-property">cx</span>.<span class="cm-property">restore</span>();
};</pre>
<p>متد <code>drawPlayer</code> توسط <code>drawActors</code> فراخوانی می شود که مسئول ترسیم تمامی بازیگران در بازی می باشد.</p>
<pre class="snippet cm-s-default" data-language="javascript" data-sandbox="game"><a class="c_ident" id="c_XwZEfLqKhO" href="#c_XwZEfLqKhO" tabindex="-1" role="presentation"></a><span class="cm-variable">CanvasDisplay</span>.<span class="cm-property">prototype</span>.<span class="cm-property">drawActors</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">actors</span>) {
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">actor</span> <span class="cm-keyword">of</span> <span class="cm-variable-2">actors</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">width</span> <span class="cm-operator">=</span> <span class="cm-variable-2">actor</span>.<span class="cm-property">size</span>.<span class="cm-property">x</span> <span class="cm-operator">*</span> <span class="cm-variable">scale</span>;
<span class="cm-keyword">let</span> <span class="cm-def">height</span> <span class="cm-operator">=</span> <span class="cm-variable-2">actor</span>.<span class="cm-property">size</span>.<span class="cm-property">y</span> <span class="cm-operator">*</span> <span class="cm-variable">scale</span>;
<span class="cm-keyword">let</span> <span class="cm-def">x</span> <span class="cm-operator">=</span> (<span class="cm-variable-2">actor</span>.<span class="cm-property">pos</span>.<span class="cm-property">x</span> <span class="cm-operator">-</span> <span class="cm-keyword">this</span>.<span class="cm-property">viewport</span>.<span class="cm-property">left</span>) <span class="cm-operator">*</span> <span class="cm-variable">scale</span>;
<span class="cm-keyword">let</span> <span class="cm-def">y</span> <span class="cm-operator">=</span> (<span class="cm-variable-2">actor</span>.<span class="cm-property">pos</span>.<span class="cm-property">y</span> <span class="cm-operator">-</span> <span class="cm-keyword">this</span>.<span class="cm-property">viewport</span>.<span class="cm-property">top</span>) <span class="cm-operator">*</span> <span class="cm-variable">scale</span>;
<span class="cm-keyword">if</span> (<span class="cm-variable-2">actor</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"player"</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">drawPlayer</span>(<span class="cm-variable-2">actor</span>, <span class="cm-variable-2">x</span>, <span class="cm-variable-2">y</span>, <span class="cm-variable-2">width</span>, <span class="cm-variable-2">height</span>);
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">let</span> <span class="cm-def">tileX</span> <span class="cm-operator">=</span> (<span class="cm-variable-2">actor</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"coin"</span> <span class="cm-operator">?</span> <span class="cm-number">2</span> : <span class="cm-number">1</span>) <span class="cm-operator">*</span> <span class="cm-variable">scale</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">cx</span>.<span class="cm-property">drawImage</span>(<span class="cm-variable">otherSprites</span>,
<span class="cm-variable-2">tileX</span>, <span class="cm-number">0</span>, <span class="cm-variable-2">width</span>, <span class="cm-variable-2">height</span>,
<span class="cm-variable-2">x</span>, <span class="cm-variable-2">y</span>, <span class="cm-variable-2">width</span>, <span class="cm-variable-2">height</span>);
}
}
};</pre>
<p><a class="p_ident" id="p_qpj1V19q0c" href="#p_qpj1V19q0c" tabindex="-1" role="presentation"></a>در هنگام رسم چیزی به جز بازیکن اصلی، به نوع آن نگاه می کنیم تا میزان جابجایی لازم برای پیدا کردن sprite مورد نظر را پیدا کنیم. کاشی گدازه با 20 و سکه با در 40 ( دو برابر <code>scale</code>) پیدا می شوند.</p>
<p><a class="p_ident" id="p_jbr14RuCIg" href="#p_jbr14RuCIg" tabindex="-1" role="presentation"></a>لازم است تا موقعیت میداندید را در هنگام محاسبهی موقعیت بازیگر کم کنیم به این دلیل که موقعیت <bdo>(0,0)</bdo> روی canvas ما به گوشهی بالاچپ میدان دید ارتباط دارد، نه گوشهی بالاچپ مرحله. همچنین میتوانستیم از <code>translate</code> برای این کار استفاده کنیم. هر دو روش صحیح است.</p>
<p>این کار، سیستم نمایش جدید را به <code>runGame</code> متصل می کند:</p>
<pre class="snippet cm-s-default" data-language="text/html" data-focus="true" data-sandbox="game"><a class="c_ident" id="c_TSR2vcnWZv" href="#c_TSR2vcnWZv" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">body</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-variable">runGame</span>(<span class="cm-variable">GAME_LEVELS</span>, <span class="cm-variable">CanvasDisplay</span>);
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">body</span><span class="cm-tag cm-bracket">></span></pre>
<h2 id="graphics_tradeoffs"><a class="h_ident" id="h_KoLjuW5hXv" href="#h_KoLjuW5hXv" tabindex="-1" role="presentation"></a>انتخاب یک رابط گرافیکی</h2>
<p><a class="p_ident" id="p_HFb3dBFu4W" href="#p_HFb3dBFu4W" tabindex="-1" role="presentation"></a>زمانی که لازم است عناصر گرافیکی در مرورگر ایجاد شوند، می توانید بین HTML، SVG و استفاده از canvas انتخاب کنید. روش واحدی که به بهترین شکل در همهی شرایط مناسب باشد وجود ندارد. هر گزینهای نقاط قوت و ضعفی دارد.</p>
<p><a class="p_ident" id="p_Xhe5zodJFB" href="#p_Xhe5zodJFB" tabindex="-1" role="presentation"></a>استفاده از HTML ساده، مزیت سادگی را به همراه دارد. همچنین این گزینه با متنها به خوبی یکپارچه می شود. هر دوی SVG و Canvas به شما امکان رسم متن را می دهند اما برای موقعیت دهی متن یا شکست آن به خطوط جدید در صورت جا نشدن در یک خط کمکی نمی کنند. در یک تصویر مبتنی بر HTML خیلی آسان تر می توان بلوکهای متنی را قرار داد.</p>
<p><a class="p_ident" id="p_D1ZRiAtR4F" href="#p_D1ZRiAtR4F" tabindex="-1" role="presentation"></a>از SVG می توان برای تولید گرافیکهایی با وضوح بالا که در هر سطحی از بزرگنمایی خوب به نظر می رسند استفاده کرد. برخلاف HTML، در واقع SVG برای ترسیم طراحی شده است بنابراین گزینهی مناسبتری برای این کار است.</p>
<p><a class="p_ident" id="p_iA8Puv51bw" href="#p_iA8Puv51bw" tabindex="-1" role="presentation"></a>هر دوی SVG و HTML ساختار دادهای را فراهم می سازند (DOM) که نمایانگر تصویر شما خواهد بود. این باعث میشود که بتوان عناصر را پس از ترسیم تغییر داد. اگر نیاز دارید که به طور مداوم بخش کوچکی از یک تصویر بزرگ را در پاسخ به فعالیت کاربر یا به دلیل متحرکسازی تغییر دهید، استفاده از canvas بدون اینکه کمک شایانی بکند هزینهی زیادی خواهد داشت. DOM نیز به ما این امکان را می دهد که گردانندههای رخداد موس را روی هر عنصر در تصویر (حتی اشکالی که با SVG رسم شده اند) ثبت کنیم. این کار با canvas شدنی نیست.</p>
<p><a class="p_ident" id="p_L2YySodyCS" href="#p_L2YySodyCS" tabindex="-1" role="presentation"></a>اما روش مبتنی بر پیکسل canvas در مواقعی که تعداد زیادی عناصر کوچک رسم می کنیم مزیت محسوب می شود. این واقعیت که canvas یک ساختار داده تشکیل نمی دهد بلکه فقط به طور مداوم در همان سطح پیکسل به ترسیم می پردازد هزینهی کمتری برای هر شکل در canvas ایجاد می شود.</p>
<p><a class="p_ident" id="p_r/Ew7drQvE" href="#p_r/Ew7drQvE" tabindex="-1" role="presentation"></a>همچنین جلوههایی وجود دارند که فقط زمانی قابل اعمال هستند که از روشی مبتنی بر پیکسل استفاده شده باشد؛ مانند رندر یک صحنه به صورت یک پیکسل در آن واحد (مثلا با استفاده از روش رهگیری نور (ray tracer)) یا پسپردازش یک تصویر با جاوااسکریپت ( مثل تار کردن یا distort).</p>
<p><a class="p_ident" id="p_FnDdSs5gmN" href="#p_FnDdSs5gmN" tabindex="-1" role="presentation"></a>در بعضی موارد، ممکن است بخواهید چندتا از این تکنیکها را باهم ترکیب کنید. مثلا ممکن است یک گراف را با SVG یا canvas ترسیم کنید اما اطلاعات متنی را با استفاده از یک عنصر HTML که روی تصویر موقعیت دهی میشود نشان دهید.</p>
<p>برای برنامههایی که تعداد کاربران زیادی ندارند، زیاد مهم نیست از کدام رابط استفاده می کنید. صفحهی نمایشی که ما برای بازیمان در این فصل ساختیم می توانست با هر کدام از این سه تکنولوژی گرافیکی پیاده سازی شود چرا که نه نیاز به ترسیم متن است نه تعاملات با موس یا کار با تعداد بیش از اندازه از عناصر.</p>
<h2><a class="h_ident" id="h_EzvDUHyjs2" href="#h_EzvDUHyjs2" tabindex="-1" role="presentation"></a>خلاصه</h2>
<p>در این فصل به بحث دربارهی تکنیکهای ترسیم گرافیک در مرورگر پرداختیم و تمرکز ما روی عنصر <bdo><code><canvas></code></bdo> بود.</p>
<p><a class="p_ident" id="p_dBxM5IdGaK" href="#p_dBxM5IdGaK" tabindex="-1" role="presentation"></a>یک گرهی canvas نمایانگر ناحیهای است در سند که برنامهی ما در آن قسمت به ترسیم خواهد پرداخت. این ترسیم توسط یک شیء بستر (context) ترسیم انجام می شود که توسط متد <code>getContext</code> ایجاد می گردد.</p>
<p><a class="p_ident" id="p_3w9NaMq92y" href="#p_3w9NaMq92y" tabindex="-1" role="presentation"></a>رابط ترسیم دوبعدی (2D) این امکان را به ما می دهد تا اشکال متنوعی را رنگ کرده یا خط مرزی بدهیم. خاصیت <code>fillStyle</code> این بستر (context) نحوهی رنگآمیزی اشکال را مشخص می کند. خاصیتهای <code>strokeStyle</code> و <code>lineWidth</code> نحوهی ترسیم خطوط را کنترل می کنند.</p>
<p>چهارضلعی ها و بخشهای متنی را می توان با یک فراخوانی متد ترسیم کرد. دو متد <code>fillRect</code> و <code>strokeRect</code> برای ترسیم چهارضلعی و متدهای <code>fillText</code> و <code>strokeText</code> برای رسم متن استفاده می شوند. برای ترسیم اشکال دلخواه، ابتدا باید یک مسیر ایجاد کنید.</p>
<p>فراخوانی متد <code>beginPath</code> باعث ایجاد یک مسیر جدید می شود. چند متد دیگر برای افزودن خطوط و منحنیها به همین مسیر فراخوانی می شوند. به عنوان مثال، <code>lineTo</code> یک خط مستقیم اضافه می کند. زمانی که یک مسیر به پایان رسید، می توان با متد <code>fill</code> آن را پر (رنگ) کرد یا با استفاده از متد <code>stroke</code> دور آن خط مرزی رسم کرد.</p>
<p>حرکت دادن پیکسلها از یک تصویر یا یک canvas دیگر به canvas ما توسط متد <code>drawImage</code> انجام می پذیرد. به صورت پیشفرض، این متد کل تصویر مبدا را رسم می کند، اما با مشخص کردن پارامترهای بیشتر می توانی یک ناحیهی خاص از تصویر را کپی کرد. ما از این روش برای بازی خودمان و کپی کردن حالتهای کاراکتر بازی از یک تصویر که شامل همهی حالت ها بود استفاده کردیم.</p>
<p><a class="p_ident" id="p_GzrQKcovuP" href="#p_GzrQKcovuP" tabindex="-1" role="presentation"></a>دگرگونسازی (transformation) این امکان را به شما می دهد که یک شکل را به صورتهای متعدد ترسیم کنید. یک بستر ترسیم دوبعدی، دارای شکلی است که میتوان آن را با استفاده از <code>translate</code>، <code>scale</code> و <code>rotate</code> تغییر داد. این تغییرات روی تمامی ترسیمهای بعدی تاثیر می گذارد. یک حالت دگرگونسازی را می توان با استفاده از متد <code>save</code> ذخیره کرد و با متد <code>restore</code> بازگردانی کرد.</p>
<p>زمانی که یک تصویر متحرک را روی یک canvas نمایش می دهیم، متد <code>clearRect</code> را می توان برای پاکسازی یک قسمت از canvas قبل از ترسیم دوباره استفاده کرد.</p>
<h2><a class="h_ident" id="h_ggOFdVwDCk" href="#h_ggOFdVwDCk" tabindex="-1" role="presentation"></a>تمرینها</h2>
<h3><a class="i_ident" id="i_3Wy7uJ2wGq" href="#i_3Wy7uJ2wGq" tabindex="-1" role="presentation"></a>شکلها</h3>
<p>برنامهای بنویسید که اشکال زیر را روی یک canvas رسم نماید:</p>
<ol>
<li>
<p>یک ذوزنقه (یک چهارضلعی که یک طرف آن پهنتر است)</p></li>
<li>
<p><a class="p_ident" id="p_+2RDUVYNgp" href="#p_+2RDUVYNgp" tabindex="-1" role="presentation"></a>یک لوزی قرمز (یک چهارگوش که 45 درجه یا <bdo>¼π</bdo> رادیان چرخانده شده است)</p></li>
<li>
<p>یک خط زیگزاگی</p></li>
<li>
<p><a class="p_ident" id="p_MQuG4LYrgo" href="#p_MQuG4LYrgo" tabindex="-1" role="presentation"></a>یک مارپیچ که از 100 قسمت خط مستقیم تشکیل شده است</p></li>
<li>
<p>یک ستارهی زرد</p></li>
</ol><figure><img src="img/exercise_shapes.png" alt="The shapes to draw"></figure>
<p>زمانی که دو شکل آخر را رسم می کنید ممکن است لازم باشد به توضیحات مربوط به <bdo><code>Math.cos</code></bdo> و <bdo><code>Math.sin</code></bdo> در <a href="14_dom.html#sin_cos">فصل 14</a> رجوع کنید که توضیح می دهد چگونه مختصات روی یک دایره را به وسیلهی این توابع بهدست بیاورید.</p>
<p>پیشنهاد من این است که برای هر شکل یک تابع بنویسید. موقعیت را به آن به همراه دیگر خاصیتهای اختیاری مثل اندازه یا تعداد نقاط به عنوان پارامتر ارسال کنید. روش دیگر که نوشتن اعداد به طور مستقیم در بدنه کد است باعث می شود که تغییر دادن و خوانایی کد سخت شود.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_kWm/btAd42" href="#c_kWm/btAd42" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span> <span class="cm-attribute">width</span>=<span class="cm-string">"600"</span> <span class="cm-attribute">height</span>=<span class="cm-string">"200"</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-comment">// Your code here.</span>
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<div class="solution"><div class="solution-text">
<p><a class="p_ident" id="p_LBxrDOE/wA" href="#p_LBxrDOE/wA" tabindex="-1" role="presentation"></a>آسان ترین روش ترسیم ذوزنقه (1) استفاده از یک مسیر (path) است. مختصات مرکزی مناسبی را انتخاب کنید و هر یک از چهار گوشه را اطراف آن اضافه نمایید.</p>
<p><a class="p_ident" id="p_Jaie/CPcb3" href="#p_Jaie/CPcb3" tabindex="-1" role="presentation"></a>برای ترسیم لوزی (2)، می توان از راه سرراست استفاده از مسیر یا روش جالب اسفاده از یک <code>rotate</code> (دگرگونی) استفاده نمود. برای استفاده از چرخش، باید از یک ترفند مانند کاری که در تابع <code>flipHorizontally</code> انجام دادیم،استفاده کنید. به دلیل اینکه می خواهیم حول مرکز چهارضلعی چرخش صورت گیرد نه پیرامون نقطهی <bdo>(0,0)</bdo>، ابتدا باید به آن نقطه <code>translate</code> کنید، سپس چرخش، و دوباره بازگشت به وسیلهی translate.</p>
<p><a class="p_ident" id="p_ke8MFgiyDJ" href="#p_ke8MFgiyDJ" tabindex="-1" role="presentation"></a>اطمینان حاصل کنید که دگرگونی انجام شده را پس از ترسیم هر شکل بازنشانی (reset) کنید.</p>
<p><a class="p_ident" id="p_d95o2uzYI7" href="#p_d95o2uzYI7" tabindex="-1" role="presentation"></a>برای شمارهی (3)، زیگزاگ، استفاده مکرر از فراخوانیهای <code>lineTo</code> برای هر قسمت خط ، مناسب نیست؛ بلکه باید از یک حلقه استفاده کنید. در هر گام تکرار، می توانید یک یا دو قسمت خط (راست و سپس چپ) را ترسیم کنید، که در این صورت باید از (<bdo><code>% 2</code></bdo>) برای تشخیص زوج بودن شاخص حلقه استفاده کنید تا راست و چپ را مشخص نمایید.</p>
<p><a class="p_ident" id="p_G2RTiSRzpG" href="#p_G2RTiSRzpG" tabindex="-1" role="presentation"></a>همچنین برای رسم مارپیچ (4) نیز به حلقه نیاز دارید. اگر مجموعهای از نقاط که هر نقطه پیرامون دایرهای به مرکزیت مارپیچ حرکت میکنند، رسم کنید، به دایره خواهید رسید. اگر در طول حلقه، شعاع دایرهای که در حال حاضر روی نقطهی فعلی را قرار می دهید تغییر دهید و بیش از یک مرتبه حرکت کنید، نتیجهی کار یک مارپیچ خواهد شد.</p>
<p><a class="p_ident" id="p_rDR41po8gf" href="#p_rDR41po8gf" tabindex="-1" role="presentation"></a>ستاره (5) به وسیلهی خطوط <code>quadraticCurveTo</code> ترسیم میشود. همچنین می توانید آن را به وسیلهی خطوط مستقیم رسم کنید. یک دایره را به هشت قسمت برای ستارهای با هشت نقطه تقسیم کنید یا به هر تعدادی که مایل هستید. بین این نقاط خط رسم کنید، انحنا را به سمت مرکز ستاره مشخص کنید. با استفاده از <code>quadraticCurveTo</code>، می توانید از مرکز به عنوان نقطهی کنترل استفاده کنید.</p>
</div></div>
<h3 id="exercise_pie_chart"><a class="i_ident" id="i_cEvuoBMPSL" href="#i_cEvuoBMPSL" tabindex="-1" role="presentation"></a>نمودار کیکی</h3>
<p><a href="17_canvas.html#pie_chart">پیشتر</a> در این فصل مثالی از یک برنامه را مشاهده کردیم که یک نمودار کیکی رسم می کرد. این برنامه را تغییر داده تا نام هر دسته کنار برش مربوطه در نمودار پدیدار شود. سعی کنید تا روشی پیدا کنید که متنها را به گونهای مرتب و خودکار موقعیت دهی کند که برای مجموعهی دادههای دیگر نیز کار کند. می توانید فرض کنید که دستهها دارای فروانی زیاد و کافی هستند که فضا برای نوشتن برچسبهایشان فراهم باشد.</p>
<p>ممکن است دوباره به توابع <bdo><code>Math.sin</code></bdo> و <bdo><code>Math.cos</code></bdo> که در <a href="14_dom.html#sin_cos">فصل 14</a> توضیح داده شده نیاز داشته باشید.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_DTliIvEhY1" href="#c_DTliIvEhY1" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span> <span class="cm-attribute">width</span>=<span class="cm-string">"600"</span> <span class="cm-attribute">height</span>=<span class="cm-string">"300"</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-keyword">let</span> <span class="cm-def">total</span> <span class="cm-operator">=</span> <span class="cm-variable">results</span>
.<span class="cm-property">reduce</span>((<span class="cm-def">sum</span>, {<span class="cm-def">count</span>}) <span class="cm-operator">=></span> <span class="cm-variable-2">sum</span> <span class="cm-operator">+</span> <span class="cm-variable-2">count</span>, <span class="cm-number">0</span>);
<span class="cm-keyword">let</span> <span class="cm-def">currentAngle</span> <span class="cm-operator">=</span> <span class="cm-operator">-</span><span class="cm-number">0.5</span> <span class="cm-operator">*</span> <span class="cm-variable">Math</span>.<span class="cm-property">PI</span>;
<span class="cm-keyword">let</span> <span class="cm-def">centerX</span> <span class="cm-operator">=</span> <span class="cm-number">300</span>, <span class="cm-def">centerY</span> <span class="cm-operator">=</span> <span class="cm-number">150</span>;
<span class="cm-comment">// Add code to draw the slice labels in this loop.</span>
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">result</span> <span class="cm-keyword">of</span> <span class="cm-variable">results</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">sliceAngle</span> <span class="cm-operator">=</span> (<span class="cm-variable">result</span>.<span class="cm-property">count</span> <span class="cm-operator">/</span> <span class="cm-variable">total</span>) <span class="cm-operator">*</span> <span class="cm-number">2</span> <span class="cm-operator">*</span> <span class="cm-variable">Math</span>.<span class="cm-property">PI</span>;
<span class="cm-variable">cx</span>.<span class="cm-property">beginPath</span>();
<span class="cm-variable">cx</span>.<span class="cm-property">arc</span>(<span class="cm-variable">centerX</span>, <span class="cm-variable">centerY</span>, <span class="cm-number">100</span>,
<span class="cm-variable">currentAngle</span>, <span class="cm-variable">currentAngle</span> <span class="cm-operator">+</span> <span class="cm-variable-2">sliceAngle</span>);
<span class="cm-variable">currentAngle</span> <span class="cm-operator">+=</span> <span class="cm-variable-2">sliceAngle</span>;
<span class="cm-variable">cx</span>.<span class="cm-property">lineTo</span>(<span class="cm-variable">centerX</span>, <span class="cm-variable">centerY</span>);
<span class="cm-variable">cx</span>.<span class="cm-property">fillStyle</span> <span class="cm-operator">=</span> <span class="cm-variable">result</span>.<span class="cm-property">color</span>;
<span class="cm-variable">cx</span>.<span class="cm-property">fill</span>();
}
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<div class="solution"><div class="solution-text">
<p>لازم است تا <code>fillText</code> را فراخوانی نموده و خاصیتهای <code>textAlign</code> و <code>textBaseline</code> مرتبط با context آن را طوری تنظیم کنید که متن جایی که می خواهید ظاهر شود.</p>
<p>یک روش روشن برای موقعیتدادن برچسبها این است که متن را روی خطی قرار دهید که از مرکز نمودار به سمت میانهی برش میرود.</p>
<p>قطعا نمیخواهید که متن را مستقیما کنار برش قرار دهید بلکه با چندین پیکسل فاصله کنار نمودار باید نمایش داده شود.</p>
<p><a class="p_ident" id="p_d1vFww4n8O" href="#p_d1vFww4n8O" tabindex="-1" role="presentation"></a>زاویهی این خط برابر است با <bdo><code>currentAngle + 0.<wbr>5 * sliceAngle</code></bdo>. کد پیش رو، جایی روی این خط با فاصلهی 120 پیکسل از مرکز می یابد:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_7kVY28rLLf" href="#c_7kVY28rLLf" tabindex="-1" role="presentation"></a><span class="cm-keyword">let</span> <span class="cm-def">middleAngle</span> <span class="cm-operator">=</span> <span class="cm-variable">currentAngle</span> <span class="cm-operator">+</span> <span class="cm-number">0.5</span> <span class="cm-operator">*</span> <span class="cm-variable">sliceAngle</span>;
<span class="cm-keyword">let</span> <span class="cm-def">textX</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">cos</span>(<span class="cm-variable">middleAngle</span>) <span class="cm-operator">*</span> <span class="cm-number">120</span> <span class="cm-operator">+</span> <span class="cm-variable">centerX</span>;
<span class="cm-keyword">let</span> <span class="cm-def">textY</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">sin</span>(<span class="cm-variable">middleAngle</span>) <span class="cm-operator">*</span> <span class="cm-number">120</span> <span class="cm-operator">+</span> <span class="cm-variable">centerY</span>;</pre>
<p>برای <code>textBaseLine</code>، مقدار <code>"middle"</code> احتمالا با این روش مناسب باشد. مقدار <code>textAlign</code> بستگی دارد که در حال حاضر در کدام سمت دایره قرار داریم. سمت چپ، باید مقدار آن <code>"right"</code> باشد و سمت راست نیز مقدار <code>"left"</code> مناسب است که باعث می شود متن از کیک فاصله بگیرد.</p>
<p>اگر در به دست آوردن سمت دایره با توجه با زاویهی در دسترس دچار مشکل شدید، به توضیحات مربوط به <bdo><code>Math.cos</code></bdo> در <a href="14_dom.html#sin_cos">فصل 14</a> رجوع کنید. کسینوس یک زاویه، متختصات x مرتبط با آن را مشخص می کند که سمتی از دایره که در آن قرار داریم را روشن می کند.</p>
</div></div>
<h3><a class="i_ident" id="i_yrw/70gHLZ" href="#i_yrw/70gHLZ" tabindex="-1" role="presentation"></a>جست و خیز توپ</h3>
<p>با استفاده از تکنیک <code>requestAnimationFrame</code> که در <a href="14_dom.html#animationFrame">فصل 14</a> و <a href="16_game.html#runAnimation">فصل 16</a> مشاهده کردیم مستطیلی رسم کنید که یک توپ متحرک درون آن باشد. توپ با سرعتی ثابت حرکت می کند و با برخورد به دیوارهای مستطیل برگشته و جهت حرکتش عوض می شود.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_9Io71wlUw7" href="#c_9Io71wlUw7" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">canvas</span> <span class="cm-attribute">width</span>=<span class="cm-string">"400"</span> <span class="cm-attribute">height</span>=<span class="cm-string">"400"</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">canvas</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span>
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"canvas"</span>).<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-keyword">let</span> <span class="cm-def">lastTime</span> <span class="cm-operator">=</span> <span class="cm-atom">null</span>;
<span class="cm-keyword">function</span> <span class="cm-def">frame</span>(<span class="cm-def">time</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable">lastTime</span> <span class="cm-operator">!=</span> <span class="cm-atom">null</span>) {
<span class="cm-variable">updateAnimation</span>(<span class="cm-variable">Math</span>.<span class="cm-property">min</span>(<span class="cm-number">100</span>, <span class="cm-variable-2">time</span> <span class="cm-operator">-</span> <span class="cm-variable">lastTime</span>) <span class="cm-operator">/</span> <span class="cm-number">1000</span>);
}
<span class="cm-variable">lastTime</span> <span class="cm-operator">=</span> <span class="cm-variable-2">time</span>;
<span class="cm-variable">requestAnimationFrame</span>(<span class="cm-variable">frame</span>);
}
<span class="cm-variable">requestAnimationFrame</span>(<span class="cm-variable">frame</span>);
<span class="cm-keyword">function</span> <span class="cm-def">updateAnimation</span>(<span class="cm-def">step</span>) {
<span class="cm-comment">// Your code here.</span>
}
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<div class="solution"><div class="solution-text">
<p>رسم یک مستطیل با استفاده از <code>strokeRect</code> کاری آسان است. یک متغیر برای نگهداری اندازهی چهارضلعی یا دو متغیر اگر طول و عرض چهارضلعی شما متفاوت است، تعریف کنید. برای ایجاد یک توپ، از یک مسیر و یک فراخوانی <bdo><code>arc(x, y, radius, 0, 7)</code></bdo> استفاده کنید که کمانی رسم می کند که از صفر تا بیش از یک دایرهی کامل ادامه خواهد داشت. سپس مسیر را پر کنید.</p>
<p><a class="p_ident" id="p_c97Fk0rcGV" href="#p_c97Fk0rcGV" tabindex="-1" role="presentation"></a>برای مدلسازی موقعیت و سرعت توپ، می توانید از کلاس <code>Vec</code> متعلق به <a href="16_game.html#vector">فصل 16</a> (که در این صفحه موجود است) استفاده کنید. به این کلاس یک سرعت اولیه که ترجیحا کاملا عمودی یا افقی نباشد، و برای هر فریم آن سرعت را در زمان سپری شده ضرب کنید. زمانی که توپ خیلی به دیوار عمودی نزدیک شد، مولفهی x سرعت آن را معکوس کنید. همین کار را برای مولفهی y آن در هنگام برخورد به دیوار افقی انجام دهید.</p>
<p>پس از پیداکردن موقعیت و سرعت جدید توپ، از <code>clearRect</code> برای پاک کردن صحنه و بازترسیم آن به وسیلهی موقعیت جدید استفاده کنید.</p>
</div></div>
<h3><a class="i_ident" id="i_2RRWJwLorO" href="#i_2RRWJwLorO" tabindex="-1" role="presentation"></a>پیشمحاسبه وارونهسازی</h3>
<p>یک اشکال که در دگرگونسازی (transformation) وجود دارد این است که استفاده از آن باعث می شود رسم تصاویر بیتی کند شود. موقعیت و اندازه هر پیکسل باید تغییر داده شود و اگرچه محتمل است که مرورگرها در این مساله بهتر و باهوش تر در آینده عمل کنند، در حال حاضر این امر باعث می شود که زمان ترسیم یک نقشهی بیتی به شکل محسوسی زیاد شود.</p>
<p>در یک بازی مثل بازی ما، که فقط یک sprite تغییر شکل داده رسم می کنیم، مشکلی به وجود نمی آورد. اما تصور کنید که لازم باشد صدها کاراکتر یا هزاران ذرهی چرخان برای یک انفجار رسم کنیم.</p>
<p>به دنبال راه حلی بگردید که به ما این امکان را بدهد که یک کارکتر برعکس را بتوانیم بدون بارگیری فایلهای تصویری اضافی و بدون فراخوانی <code>drawImage</code> برای هر فریم رسم کنیم.</p>
<div class="solution"><div class="solution-text">
<p><a class="p_ident" id="p_kYxE6Xaitv" href="#p_kYxE6Xaitv" tabindex="-1" role="presentation"></a>نکتهی کلیدی به راه حل این است که ما می توانیم از یک عنصر canvas به عنوان منبع یک تصویر در هنگام استفاده از <code>drawImage</code> استفاده کنیم. می توان یک <bdo><code><canvas></code></bdo> اضافه بدون اضافه کردن آن به سند، ایجاد کرد و sprite های وارونهشده مان را یک بار در آن رسم نمود. در هنگام رسم یک فریم، کافی است تنها spriteهای وارونهشده را به canvas اصلی کپی کنیم.</p>
<p>با توجه نمود که تصاویر بلافاصله بارگیری نمیشوند. ما عمل وارونهسازی را یک بار انجام می دهیم و اگر این کار قبل از بارگیری تصاویر صورت گیرد، چیزی رسم نخواهد شد. یک گردانندهی <code>"load"</code> روی تصویر در اینجا میتواند برای ترسیم تصاویر وارونه روی canvas اضافه استفاده شود. این canvas را می توان به عنوان منبع ترسیم بلافاصله استفاده نمود (تا زمانی که ما کاراکتر را روی آن رسم کنیم خالی خواهد بود).</p>
</div></div><nav><a href="16_game.html" title="previous chapter">◀</a> <a href="index.html" title="cover">◆</a> <a href="18_http.html" title="next chapter">▶</a></nav>
</article>