-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy path19_paint.html
More file actions
767 lines (577 loc) · 135 KB
/
19_paint.html
File metadata and controls
767 lines (577 loc) · 135 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
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>پروژه: یک ویرایشگر پیکسلی :: 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 = 19;var sandboxLoadFiles = ["code/chapter/19_paint.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="18_http.html" title="previous chapter">◀</a> <a href="index.html" title="cover">◆</a> <a href="20_node.html" title="next chapter">▶</a></nav>
<h1><span class=chap_num>فصل 19</span>پروژه: یک ویرایشگر پیکسلی</h1>
<blockquote>
<p><a class="p_ident" id="p_2jmj7l5rSw" href="#p_2jmj7l5rSw" tabindex="-1" role="presentation"></a>به رنگهای بیشمار پیش رویم نگاه می کنم. به بوم نقاشی خالیام نیز. آنگاه، سعی می کنم رنگها را مانند واژههایی که شعرها را میسازند به کار ببرم، مانند نوتهایی که به موسیقی شکل میدهند.</p>
<footer>خوان میرو</footer>
</blockquote><figure class="chapter framed"><img src="img/chapter_picture_19.jpg" alt="Picture of a tiled mosaic"></figure>
<p>هرآنچه برای ساخت یک اپلیکیشن وب ساده مورد نیاز است، در فصلهای پیشین آمده است. در این فصل، فقط قرار است از آنها استفاده کنیم.</p>
<p>اپلیکیشن ما قرار است یک برنامهی ترسیم پیکسلی باشد، جایی که می توانید یک تصویر را پیکسل به پیکسل تغییر دهید. این کار با دستکاری حالت بزرگنمایی شدهی تصویر، که جدولی از خانههای رنگ شده است صورت می گیرد. می توانید از آن برای باز کردن فایلهای تصویری و خط خطی کردن روی آنها با موس یا هر وسیلهی اشارهگر دیگر استفاده کنید و سپس آن را ذخیره نمایید. برنامه کاربردی ما شبیه به تصویر زیر خواهد بود:</p><figure><img src="img/pixel_editor.png" alt="The pixel editor interface, with colored pixels at the top and a number of controls below that"></figure>
<p>نقاشی روی یک کامپیوتر خیلی جالب است. نیازی نیست نگران ابزار، توانایی یا استعداد خاصی باشید. کافی است فقط شروع به کشیدن کنید.</p>
<h2><a class="h_ident" id="h_xh7m2KDR84" href="#h_xh7m2KDR84" tabindex="-1" role="presentation"></a>مؤلفهها یا اجزاء</h2>
<p>رابط برنامهی کاربردی ما، شامل یک <code><canvas></code> بزرگ در بالای صفحه می باشد که چندین فیلد فرم نیز در زیر آن قرار گرفته است. کاربر با انتخاب یک ابزار از لیست فیلد <code><select></code> و کلیک کردن، لمس کردن یا کشیدن موس روی بوم به ترسیم می پردازد. همچنین ابزارهایی برای رسم تکپیکسلها، چهارضلعیها، رنگکردن یک ناحیه، و انتخاب یک رنگ از روی یک تصویر وجود دارد.</p>
<p><a class="p_ident" id="p_Yfb0Ie8yZw" href="#p_Yfb0Ie8yZw" tabindex="-1" role="presentation"></a>ما رابط ویرایشگر را به شکل تعدادی مؤلفه ساختاردهی می کنیم، اشیائی که مسئول بخشهایی از DOM می باشند و ممکن است حاوی دیگر مؤلفه ها در درون خود باشند.</p>
<p><a class="p_ident" id="p_qkpfgSXyNB" href="#p_qkpfgSXyNB" tabindex="-1" role="presentation"></a>وضعیت (state) برنامهی کاربردی شامل تصویر فعلی، ابزار انتخاب شده و رنگ انتخاب شده می باشد. ما کارها را طوری تنظیم می کنیم که در نتیجه وضعیت با یک مقدار مشخص شود و دیگر مؤلفهها همیشه با توجه به وضعیت فعلی شکل بگیرند.</p>
<p>برای پی بردن به اهمیت این موضوع، اجازه بدهید راه دیگر را هم بررسی کنیم – پخش قسمتهای وضعیت (state) در سراسر رابط. تا نقطهی مشخصی، این روش، برنامهنویسی ساده تری دارد. می توانیم تنها یک فیلد رنگ در نظر بگیریم و هر زمان که نیاز به دانستن رنگ فعلی داشتیم، مقدار آن را بخوانیم.</p>
<p>اما در ادامه تصمیم می گیریم یک گزینشگر رنگ اضافه کنیم – ابزاری که به شما اجازه می دهد تا با کلیک روی تصویر، رنگ پیکسل دادهشده را انتخاب کنید. برای اینکه کاری کنیم تا فیلد رنگ ، رنگ درست را نمایش دهد، ابزار ما بایستی بداند که فیلد رنگ مورد نظر موجود است و با هر بار انتخاب رنگ جدید آن را بهروز رسانی کند. اگر جای دیگری را نیز در نظر گرفتید که رنگ انتخاب شده را نمایان کند( ممکن است اشارهگر موس برای این کار در نظر گرفته شده باشد)، باید کد مربوط به تغییر رنگ را نیز بهروزرسانی کنید تا آن جای جدید را نیز هماهنگ نگه دارید.</p>
<p>در عمل، این کار شما را با مشکل روبرو می کند طوری که هر بخش از رابط لازم است تا دربارهی همه بخشهای دیگر آگاه باشد، که خیلی ماژولار نیست. برای برنامههای کاربردی کوچک مثل مورد این فصل، ممکن است مشکل خاصی نباشد. برای پروژه های بزرگتر می تواند این کار به کابوس بزرگی ختم شود.</p>
<p><a class="p_ident" id="p_jDI0V6TNRO" href="#p_jDI0V6TNRO" tabindex="-1" role="presentation"></a>خوب برای پیشگیری از بروز این کابوس، ما سعی می کنیم که در مورد جریان داده (data flow) خیلی سختگیرانه عمل کنیم. یک وضعیت وجود دارد و رابط قرار است بر اساس آن وضعیت ترسیم شود. یک مؤلفهی رابط ممکن است به کارهای کاربر با بهروز رسانی وضعیت پاسخ دهد، که در آن نقطه، مؤلفهها شانس دارند تا خودشان را با وضعیت جدید هماهنگ سازند.</p>
<p>در عمل، هر مؤلفه به گونهای تنظیم می شود که زمانی که به آن وضعیت جدیدی داده شود، این وضعیت را به مؤلفههای فرزندش نیز اعلام کند تا آنهایی که نیاز دارند بهروز شوند. درست کردن این قسمت کمی با زحمت همراه است. اجرای مناسب و راحت این بخش، مزیت و نقطهی برتری خیلی از کتابخانههای مربوط به برنامه نویسی مرورگر محسوب می شود. اما برای برنامهی کوچکی مثل برنامهی ما ، می توانیم بدون داشتن این زیرساخت نیز جلو برویم.</p>
<p><a class="p_ident" id="p_0hSC8s9tCJ" href="#p_0hSC8s9tCJ" tabindex="-1" role="presentation"></a>بهروز رسانیهای وضعیت به شکل اشیاء نمایش داده می شوند که ما آن ها را actions می نامیم. مؤلفهها ممکن است این گونه اکشن ها را ایجاد کنند و گسیل (<em>dispatch</em>) دهند – آن ها را به تابع مرکزی مدیریت وضعیت ارسال نمایند. آن تابع وضعیت بعدی را محاسبه می کند که پس از آن ، مؤلفههای رابط، خودشان را به وضعیت جدید بهروز رسانی میکنند.</p>
<p><a class="p_ident" id="p_Zt79lK3yfN" href="#p_Zt79lK3yfN" tabindex="-1" role="presentation"></a>ما در حال پرداختن به بخش پرکار اجرای یک رابط کاربری و اعمال ساختار به آن هستیم. اگرچه بخشهای مرتبط با DOM هنوز پر از اثرات جانبی هستند، با این وجود توسط یک زیرساخت مفهومی ساده نگهداری می شوند – چرخهی بهروز رسانی وضعیت. وضعیت مشخص می کند که DOM چگونه ظاهر یابد، و تنها راهی که رخدادهای DOM دارند برای تغییر وضعیت این است که اکشنها را به وضعیت مورد نظر گسیل دهند.</p>
<p>راههای زیادی برای انجام این کار وجود دارد که هر کدام مزایا و معایب خودشان را دارند، اما ایدهی اصلی همهی آن ها مشترک است: تغییرات وضعیت بایستی از طریق یک کانال یگانهی مشخص انجام شود نه در همهی قسمتهای موجود.</p>
<p>مؤلفههای ما کلاس هایی خواهند بود که با یک رابط مطابقت دارند. سازندهی آن ها ، وضعیتی را دریافت می کند که ممکن است وضعیت کلی برنامه باشد یا بخشی از آن اگر نیازی به دسترسی به همه چیز نیست و از آن برای ساختن خاصیت <code>dom</code> استفاده می کند، DOMای که نمایانگر مؤلفه می باشد. اغلب سازندهها مقدارهای دیگری را نیز دریافت می کنند که در طول زمان ثابت هستند مانند تابعی که می توانند از آن برای گسیل یک اکشن استفاده کنند.</p>
<p>هر مؤلفه دارای متدی به نام <code>syncState</code> می باشد که برای هماهنگسازی آن با یک مقدار وضعیت جدید استفاده می شود. این متد یک آرگومان دریافت می کند، وضعیت، که از نوع مشابه اولین آرگومان سازندهاش می باشد.</p>
<h2><a class="h_ident" id="h_SM5hjFZCBM" href="#h_SM5hjFZCBM" tabindex="-1" role="presentation"></a>وضعیت یا State</h2>
<p><a class="p_ident" id="p_Dma0nQSkPB" href="#p_Dma0nQSkPB" tabindex="-1" role="presentation"></a>وضعیت برنامه شیئی خواهد بود که دارای خاصیتهای <code>picture،</code> <code>tool،</code> و <code>color</code> می باشد. picture خودش نیز یک شی است که طول، ارتفاع و محتوای پیکسل تصویر را ذخیره می نماید. پیکسلها درون یک آرایه ذخیره می شوند به همان شکلی که کلاس ماتریس از<a href="06_object.html">فصل 6</a> عمل می کرد – ردیف به ردیف و از بالا به پایین.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_FaXS4E1Q9/" href="#c_FaXS4E1Q9/" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">Picture</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">width</span>, <span class="cm-def">height</span>, <span class="cm-def">pixels</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">width</span> <span class="cm-operator">=</span> <span class="cm-variable-2">width</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">height</span> <span class="cm-operator">=</span> <span class="cm-variable-2">height</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">pixels</span> <span class="cm-operator">=</span> <span class="cm-variable-2">pixels</span>;
}
<span class="cm-keyword">static</span> <span class="cm-property">empty</span>(<span class="cm-def">width</span>, <span class="cm-def">height</span>, <span class="cm-def">color</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">pixels</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">Array</span>(<span class="cm-variable-2">width</span> <span class="cm-operator">*</span> <span class="cm-variable-2">height</span>).<span class="cm-property">fill</span>(<span class="cm-variable-2">color</span>);
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Picture</span>(<span class="cm-variable-2">width</span>, <span class="cm-variable-2">height</span>, <span class="cm-variable-2">pixels</span>);
}
<span class="cm-property">pixel</span>(<span class="cm-def">x</span>, <span class="cm-def">y</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">this</span>.<span class="cm-property">pixels</span>[<span class="cm-variable-2">x</span> <span class="cm-operator">+</span> <span class="cm-variable-2">y</span> <span class="cm-operator">*</span> <span class="cm-keyword">this</span>.<span class="cm-property">width</span>];
}
<span class="cm-property">draw</span>(<span class="cm-def">pixels</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">copy</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">pixels</span>.<span class="cm-property">slice</span>();
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> {<span class="cm-def">x</span>, <span class="cm-def">y</span>, <span class="cm-def">color</span>} <span class="cm-keyword">of</span> <span class="cm-variable-2">pixels</span>) {
<span class="cm-variable-2">copy</span>[<span class="cm-variable-2">x</span> <span class="cm-operator">+</span> <span class="cm-variable-2">y</span> <span class="cm-operator">*</span> <span class="cm-keyword">this</span>.<span class="cm-property">width</span>] <span class="cm-operator">=</span> <span class="cm-variable-2">color</span>;
}
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Picture</span>(<span class="cm-keyword">this</span>.<span class="cm-property">width</span>, <span class="cm-keyword">this</span>.<span class="cm-property">height</span>, <span class="cm-variable-2">copy</span>);
}
}</pre>
<p><a class="p_ident" id="p_ZG9+oLlVxE" href="#p_ZG9+oLlVxE" tabindex="-1" role="presentation"></a>قصد ما این است که بتوانیم با تصویر مانند یک مقدار غیر قابل تغییر رفتار کنیم به دلایلی که بعدا در ادامه فصل خواهیم گفت. اما گاهی نیز لازم است تا بخشی از پیکسلها را یکجا تغییر دهیم. برای این که بتوانیم این کار را انجام دهیم کلاس ما متدی به نام <code>draw</code> دارد که آرایهای را میگیرد که شامل پیکسلهای تغییر یافته میباشد – اشیائی با خاصیتهای <code>x</code> ، <code>y</code> و <code>color</code> – و تصویر جدیدی با آن پیکسلهای بازنویسی شده ایجاد کند. این متد از <code>slice</code> بدون استفاده از آرگومان بهره می برد تا تمام آرایهی پیکسلها را کپی کند – شروع slice به صورت پیشفرض برابر 0 و مقدار پیشفرض انتهای آن طول آرایه می باشد.</p>
<p>متد <code>empty</code> از دو ویژگی آرایهها بهره می برد که تاکنون مشاهده نکرده ایم. سازنده <code>Array</code> را می توان با یک عدد برای ساخت یک آرایه تهی با طولی مشخص فراخوانی کرد. و متد <code>fill</code> را می توان برای پر کردن این آرایه با یک مقدار مشخص استفاده کرد. این متدها برای ساختن آرایهای که در آن همهی پیکسلها رنگ مشابهی دارند استفاده می شود.</p>
<p><a class="p_ident" id="p_2fLcED8O/w" href="#p_2fLcED8O/w" tabindex="-1" role="presentation"></a>رنگها به عنوان رشتههایی که حاوی کدهای رنگ معمول در CSS می باشند – یک علامت هش (<code>#</code>) که بعد از آن شش رقم هگزادسیمال (مبنای 16) قرار می گیرد، دو رقم برای جزء قرمز، دو رقم برای سبز و دو رقم برای بخش آبی – ذخیره می شوند. این شیوهی نوشتن رنگ ها مقداری رمزگونه و نامناسب به نظر می رسد اما این فرمتی است که فیلدهای ورودی رنگ در HTML استفاده می کنند و می توان از آن برای خاصیت <code>fillColor</code> مربوط به بستر (context) ترسیم در canvas استفاده کرد. بنابراین در جایی که قصد استفاده از رنگها در این برنامه را داریم، مناسب هستند.</p>
<p><a class="p_ident" id="p_UQF0o7QTo1" href="#p_UQF0o7QTo1" tabindex="-1" role="presentation"></a>رنگ سیاه، زمانی اتفاق می افتد که همهی اجزای رنگ برابر صفر باشند و به صورت <bdo><code>"#000000"</code></bdo> نوشته می شود ، و رنگ صورتی روشن شبیه به <bdo><code>"#ff00ff"</code></bdo> نوشته می شود، به صورتی که قسمت رنگ قرمز و آبی در آن در بیشینهی مقدار خود، 255، هستند که به صورت <code>ff</code> در هگزادسیمال نوشته می شود ( که از <em>a</em> تا <em>f</em> را به عنوان ارقامی معادل 10 تا 15 در نظر می گیرند).</p>
<p><a class="p_ident" id="p_vsK14sNFNo" href="#p_vsK14sNFNo" tabindex="-1" role="presentation"></a>ما به رابط کاربری این امکان را می دهیم که actionها را به عنوان اشیائی گسیل (dispatch) دهد که خاصیتهایشان، خاصیتهای وضعیت قبلی را بازنویسی می کنند. فیلد رنگ، زمانی که کاربر آن را تغییر می دهد، می تواند شیئی مثل <code>{color: field.<wbr>value}</code> را گسیل دهد که از آن، تابع بهروز رسانی زیر می تواند مقدار جدید را محاسبه کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_0xovfSx+PU" href="#c_0xovfSx+PU" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">updateState</span>(<span class="cm-def">state</span>, <span class="cm-def">action</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable">Object</span>.<span class="cm-property">assign</span>({}, <span class="cm-variable-2">state</span>, <span class="cm-variable-2">action</span>);
}</pre>
<p>این الگوی نسبتا پیچیده، که در آن از <code>Object.assign</code> برای افزودن خاصیتهای <code>state</code> به یک شیء خالی در ابتدا استفاده می کند و بعد بعضی از آن ها را با خاصیتهایی از <code>action</code> بازنویسی می کند، در کدهای جاوااسکریپتی که از اشیاء غیرقابل تغییر استفاده می کنند معمول است. روش راحت و مناسب تر این کار که در آن از عملگر سه نقطه استفاده می شود تا همهی خاصیتهای شیء دیگر را در یک عبارت شیء قرار دهد، در آخرین مراحل استاندارد شدن در جاوااسکریپت به سر می برد. با این ویژگی، می توانستید به جای شکل قبلی، بنویسید <code>{.<wbr>.<wbr>.<wbr>state, .<wbr>.<wbr>.<wbr>action}</code>. در زمان نوشتن این کتاب، این ویژگی هنوز در همهی مرورگرها پشتیبانی نمی شود.</p>
<h2><a class="h_ident" id="h_pnLImU5TzA" href="#h_pnLImU5TzA" tabindex="-1" role="presentation"></a>ساختن DOM</h2>
<p><a class="p_ident" id="p_AEaGKg4haX" href="#p_AEaGKg4haX" tabindex="-1" role="presentation"></a>یکی از کارهای اصلی که مؤلفههای رابط کاربری انجام می دهند ساخت ساختار DOM است. ما قصد نداریم که دوباره به صورت مستقیم از متدهای صریح DOM برای این کار استفاده کنیم، بنابراین نسخهای از تابع <code>elt</code> که کمی توسعه یافته است را به کار خواهیم برد:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_bpc/osm9kD" href="#c_bpc/osm9kD" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">elt</span>(<span class="cm-def">type</span>, <span class="cm-def">props</span>, <span class="cm-meta">...</span><span class="cm-def">children</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">document</span>.<span class="cm-property">createElement</span>(<span class="cm-variable-2">type</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">props</span>) <span class="cm-variable">Object</span>.<span class="cm-property">assign</span>(<span class="cm-variable-2">dom</span>, <span class="cm-variable-2">props</span>);
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">child</span> <span class="cm-keyword">of</span> <span class="cm-variable-2">children</span>) {
<span class="cm-keyword">if</span> (<span class="cm-keyword">typeof</span> <span class="cm-variable-2">child</span> <span class="cm-operator">!=</span> <span class="cm-string">"string"</span>) <span class="cm-variable-2">dom</span>.<span class="cm-property">appendChild</span>(<span class="cm-variable-2">child</span>);
<span class="cm-keyword">else</span> <span class="cm-variable-2">dom</span>.<span class="cm-property">appendChild</span>(<span class="cm-variable">document</span>.<span class="cm-property">createTextNode</span>(<span class="cm-variable-2">child</span>));
}
<span class="cm-keyword">return</span> <span class="cm-variable-2">dom</span>;
}</pre>
<p><a class="p_ident" id="p_ua8lPy/cy1" href="#p_ua8lPy/cy1" tabindex="-1" role="presentation"></a>تفاوت اصلی بین این نسخه و نسخهای که از آن در <a href="16_game.html#domdisplay">فصل 16</a> استفاده کرده ام آن است که این نسخه خاصیتها را به گرههای DOM اختصاص می دهد نه به attributes. این بدان معنی است که نمی توانیم از آن برای تنظیم خصیصههای (attribute) دلخواه استفاده کنیم، اما می توانیم از آن برای تنظیم خاصیتهایی که مقدارشان رشتهای نیست استفاده کنیم مانند <code>onclick</code>، که میتواند تابعی را به عنوان گردانندهی رخداد کلیک ثبت کند.</p>
<p>این باعث می شود که این سبک از ثبت گردانندههای رخداد مجاز شود:</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_UrXW9qBNM+" href="#c_UrXW9qBNM+" 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">document</span>.<span class="cm-property">body</span>.<span class="cm-property">appendChild</span>(<span class="cm-variable">elt</span>(<span class="cm-string">"button"</span>, {
<span class="cm-property">onclick</span>: () <span class="cm-operator">=></span> <span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-string">"click"</span>)
}, <span class="cm-string">"The button"</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><a class="h_ident" id="h_eqFYNfD7Zh" href="#h_eqFYNfD7Zh" tabindex="-1" role="presentation"></a>canvas</h2>
<p>اولین مؤلفه ای که تعریف خواهیم کرد بخشی از رابط خواهد بود که قرار است تصویر را به صورت جدولی از مربعهای رنگ شده نشان دهد. این مؤلفه مسئول دو کار می باشد: نمایش یک تصویر و ایجاد تعامل بین رخدادهای مربوط به اشارهگر و دیگر قسمتهای برنامهی کاربردی.</p>
<p><a class="p_ident" id="p_tNWze/ephs" href="#p_tNWze/ephs" tabindex="-1" role="presentation"></a>همینطور، می توانیم آن را به صورت یک مؤلفه که فقط اطلاعاتی در مورد تصویر فعلی دارد تعریف کنیم، نه دربارهی وضعیت کلی برنامه. به دلیل اینکه این مؤلفه نمی داند که برنامه به صورت کلی چگونه کار می کند، قادر نیست تا اکشنها را مستقیما گسیل دهد. در عوض، در زمان پاسخ به رخدادهای اشارهگر، تابع callbackای را فراخوانی می کند که توسط کدی که آن را به وجود آورده فراهم شده است، که بخشهای مربوط به برنامه را مدیریت می کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_uknkIx/3Hs" href="#c_uknkIx/3Hs" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">scale</span> <span class="cm-operator">=</span> <span class="cm-number">10</span>;
<span class="cm-keyword">class</span> <span class="cm-def">PictureCanvas</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">picture</span>, <span class="cm-def">pointerDown</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"canvas"</span>, {
<span class="cm-property">onmousedown</span>: <span class="cm-def">event</span> <span class="cm-operator">=></span> <span class="cm-keyword">this</span>.<span class="cm-property">mouse</span>(<span class="cm-variable-2">event</span>, <span class="cm-variable-2">pointerDown</span>),
<span class="cm-property">ontouchstart</span>: <span class="cm-def">event</span> <span class="cm-operator">=></span> <span class="cm-keyword">this</span>.<span class="cm-property">touch</span>(<span class="cm-variable-2">event</span>, <span class="cm-variable-2">pointerDown</span>)
});
<span class="cm-keyword">this</span>.<span class="cm-property">syncState</span>(<span class="cm-variable-2">picture</span>);
}
<span class="cm-property">syncState</span>(<span class="cm-def">picture</span>) {
<span class="cm-keyword">if</span> (<span class="cm-keyword">this</span>.<span class="cm-property">picture</span> <span class="cm-operator">==</span> <span class="cm-variable-2">picture</span>) <span class="cm-keyword">return</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">picture</span> <span class="cm-operator">=</span> <span class="cm-variable-2">picture</span>;
<span class="cm-variable">drawPicture</span>(<span class="cm-keyword">this</span>.<span class="cm-property">picture</span>, <span class="cm-keyword">this</span>.<span class="cm-property">dom</span>, <span class="cm-variable">scale</span>);
}
}</pre>
<p><a class="p_ident" id="p_tMQ5huxh9h" href="#p_tMQ5huxh9h" tabindex="-1" role="presentation"></a>هر پیکسل را با مربعهای 10 در 10 رسم می کنیم، همانطور که در ثابت <code>scale</code> مشخص شده است. برای اجتناب از کار اضافی، مولفه، تصویر فعلی اش را حفظ و رصد می کند و فقط زمانی باز ترسیم را انجام می دهد که تصویر جدیدی به <code>syncState</code> داده شود.</p>
<p>تابع اصلی که مسئول ترسیم است ، اندازهی بوم را براساس اندازهی تصویر و ثابت مقیاس تنظیم می کند و آن را با مربعها پر می کند، یک مربع به ازای هر پیکسل.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_kMF9hx2xSw" href="#c_kMF9hx2xSw" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">drawPicture</span>(<span class="cm-def">picture</span>, <span class="cm-def">canvas</span>, <span class="cm-def">scale</span>) {
<span class="cm-variable-2">canvas</span>.<span class="cm-property">width</span> <span class="cm-operator">=</span> <span class="cm-variable-2">picture</span>.<span class="cm-property">width</span> <span class="cm-operator">*</span> <span class="cm-variable-2">scale</span>;
<span class="cm-variable-2">canvas</span>.<span class="cm-property">height</span> <span class="cm-operator">=</span> <span class="cm-variable-2">picture</span>.<span class="cm-property">height</span> <span class="cm-operator">*</span> <span class="cm-variable-2">scale</span>;
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable-2">canvas</span>.<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</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">0</span>; <span class="cm-variable-2">y</span> <span class="cm-operator"><</span> <span class="cm-variable-2">picture</span>.<span class="cm-property">height</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-number">0</span>; <span class="cm-variable-2">x</span> <span class="cm-operator"><</span> <span class="cm-variable-2">picture</span>.<span class="cm-property">width</span>; <span class="cm-variable-2">x</span><span class="cm-operator">++</span>) {
<span class="cm-variable-2">cx</span>.<span class="cm-property">fillStyle</span> <span class="cm-operator">=</span> <span class="cm-variable-2">picture</span>.<span class="cm-property">pixel</span>(<span class="cm-variable-2">x</span>, <span class="cm-variable-2">y</span>);
<span class="cm-variable-2">cx</span>.<span class="cm-property">fillRect</span>(<span class="cm-variable-2">x</span> <span class="cm-operator">*</span> <span class="cm-variable-2">scale</span>, <span class="cm-variable-2">y</span> <span class="cm-operator">*</span> <span class="cm-variable-2">scale</span>, <span class="cm-variable-2">scale</span>, <span class="cm-variable-2">scale</span>);
}
}
}</pre>
<p><a class="p_ident" id="p_jU9HKeneWd" href="#p_jU9HKeneWd" tabindex="-1" role="presentation"></a>زمانی که دکمهی چپ موس فشرده شود در حالیکه اشارهگر روی تصویر است، مؤلفه تابع <code>pointerDown</code> را فراخوانی می کند و موقعیت مکانی پیکسلی که روی آن کلیک شده را به آن ارسال می نماید- در مختصات تصویر. این کار برای پیادهسازی تعاملات موس با تصویر استفاده می شود. تابع callback ممکن است callback دیگری را بازگرداند تا از حرکت اشارهگر به پیکسل دیگر آگاه شود در حالیکه دکمهی موس پایین نگه داشته شده است.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_G1LWj2jAqP" href="#c_G1LWj2jAqP" tabindex="-1" role="presentation"></a><span class="cm-variable">PictureCanvas</span>.<span class="cm-property">prototype</span>.<span class="cm-property">mouse</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">downEvent</span>, <span class="cm-def">onDown</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">downEvent</span>.<span class="cm-property">button</span> <span class="cm-operator">!=</span> <span class="cm-number">0</span>) <span class="cm-keyword">return</span>;
<span class="cm-keyword">let</span> <span class="cm-def">pos</span> <span class="cm-operator">=</span> <span class="cm-variable">pointerPosition</span>(<span class="cm-variable-2">downEvent</span>, <span class="cm-keyword">this</span>.<span class="cm-property">dom</span>);
<span class="cm-keyword">let</span> <span class="cm-def">onMove</span> <span class="cm-operator">=</span> <span class="cm-variable-2">onDown</span>(<span class="cm-variable-2">pos</span>);
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">onMove</span>) <span class="cm-keyword">return</span>;
<span class="cm-keyword">let</span> <span class="cm-def">move</span> <span class="cm-operator">=</span> <span class="cm-def">moveEvent</span> <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">moveEvent</span>.<span class="cm-property">buttons</span> <span class="cm-operator">==</span> <span class="cm-number">0</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">removeEventListener</span>(<span class="cm-string">"mousemove"</span>, <span class="cm-variable-2">move</span>);
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">let</span> <span class="cm-def">newPos</span> <span class="cm-operator">=</span> <span class="cm-variable">pointerPosition</span>(<span class="cm-variable-2">moveEvent</span>, <span class="cm-keyword">this</span>.<span class="cm-property">dom</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">newPos</span>.<span class="cm-property">x</span> <span class="cm-operator">==</span> <span class="cm-variable-2">pos</span>.<span class="cm-property">x</span> <span class="cm-operator">&</span><span class="cm-operator">&</span> <span class="cm-variable-2">newPos</span>.<span class="cm-property">y</span> <span class="cm-operator">==</span> <span class="cm-variable-2">pos</span>.<span class="cm-property">y</span>) <span class="cm-keyword">return</span>;
<span class="cm-variable-2">pos</span> <span class="cm-operator">=</span> <span class="cm-variable-2">newPos</span>;
<span class="cm-variable-2">onMove</span>(<span class="cm-variable-2">newPos</span>);
}
};
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">addEventListener</span>(<span class="cm-string">"mousemove"</span>, <span class="cm-variable-2">move</span>);
};
<span class="cm-keyword">function</span> <span class="cm-def">pointerPosition</span>(<span class="cm-def">pos</span>, <span class="cm-def">domNode</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">rect</span> <span class="cm-operator">=</span> <span class="cm-variable-2">domNode</span>.<span class="cm-property">getBoundingClientRect</span>();
<span class="cm-keyword">return</span> {<span class="cm-property">x</span>: <span class="cm-variable">Math</span>.<span class="cm-property">floor</span>((<span class="cm-variable-2">pos</span>.<span class="cm-property">clientX</span> <span class="cm-operator">-</span> <span class="cm-variable-2">rect</span>.<span class="cm-property">left</span>) <span class="cm-operator">/</span> <span class="cm-variable">scale</span>),
<span class="cm-property">y</span>: <span class="cm-variable">Math</span>.<span class="cm-property">floor</span>((<span class="cm-variable-2">pos</span>.<span class="cm-property">clientY</span> <span class="cm-operator">-</span> <span class="cm-variable-2">rect</span>.<span class="cm-property">top</span>) <span class="cm-operator">/</span> <span class="cm-variable">scale</span>)};
}</pre>
<p>چون ما اندازهی پیکسلها را می دانیم و می توانیم از تابع <code>getBoundingClientRect</code> برای پیدا کردن موقعیت بوم روی صفحه استفاده کنیم، می توان از مختصات رخداد موس (<code>clientX</code> و <code>clientY</code>) به مختصات تصویر دست پیدا کرد. این اعداد همیشه رند می شوند پس به یک پیکسل مشخص اشاره می کنند.</p>
<p><a class="p_ident" id="p_qqQc7niaTy" href="#p_qqQc7niaTy" tabindex="-1" role="presentation"></a>با رخدادهای مربوط به لمس، می بایست کاری مشابه انجام دهیم، اما با استفاده از رخدادهای متفاوت و اطمینان از اینکه <code>preventDefault</code> را روی <code>touchstart</code> فراخوانی کرده باشیم تا از جابجایی تصویر (panning) جلوگیری شود.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_HUiWca6Gb7" href="#c_HUiWca6Gb7" tabindex="-1" role="presentation"></a><span class="cm-variable">PictureCanvas</span>.<span class="cm-property">prototype</span>.<span class="cm-property">touch</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">startEvent</span>,
<span class="cm-def">onDown</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">pos</span> <span class="cm-operator">=</span> <span class="cm-variable">pointerPosition</span>(<span class="cm-variable-2">startEvent</span>.<span class="cm-property">touches</span>[<span class="cm-number">0</span>], <span class="cm-keyword">this</span>.<span class="cm-property">dom</span>);
<span class="cm-keyword">let</span> <span class="cm-def">onMove</span> <span class="cm-operator">=</span> <span class="cm-variable-2">onDown</span>(<span class="cm-variable-2">pos</span>);
<span class="cm-variable-2">startEvent</span>.<span class="cm-property">preventDefault</span>();
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">onMove</span>) <span class="cm-keyword">return</span>;
<span class="cm-keyword">let</span> <span class="cm-def">move</span> <span class="cm-operator">=</span> <span class="cm-def">moveEvent</span> <span class="cm-operator">=></span> {
<span class="cm-keyword">let</span> <span class="cm-def">newPos</span> <span class="cm-operator">=</span> <span class="cm-variable">pointerPosition</span>(<span class="cm-variable-2">moveEvent</span>.<span class="cm-property">touches</span>[<span class="cm-number">0</span>],
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">newPos</span>.<span class="cm-property">x</span> <span class="cm-operator">==</span> <span class="cm-variable-2">pos</span>.<span class="cm-property">x</span> <span class="cm-operator">&</span><span class="cm-operator">&</span> <span class="cm-variable-2">newPos</span>.<span class="cm-property">y</span> <span class="cm-operator">==</span> <span class="cm-variable-2">pos</span>.<span class="cm-property">y</span>) <span class="cm-keyword">return</span>;
<span class="cm-variable-2">pos</span> <span class="cm-operator">=</span> <span class="cm-variable-2">newPos</span>;
<span class="cm-variable-2">onMove</span>(<span class="cm-variable-2">newPos</span>);
};
<span class="cm-keyword">let</span> <span class="cm-def">end</span> <span class="cm-operator">=</span> () <span class="cm-operator">=></span> {
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">removeEventListener</span>(<span class="cm-string">"touchmove"</span>, <span class="cm-variable-2">move</span>);
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">removeEventListener</span>(<span class="cm-string">"touchend"</span>, <span class="cm-variable-2">end</span>);
};
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">addEventListener</span>(<span class="cm-string">"touchmove"</span>, <span class="cm-variable-2">move</span>);
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">addEventListener</span>(<span class="cm-string">"touchend"</span>, <span class="cm-variable-2">end</span>);
};</pre>
<p>برای رخدادهای لمسی، <code>clientX</code> و <code>clientY</code> مستقیما روی شیء رخداد در دسترس نیستند، اما می توانیم از مختصات شیء اولین لمس در خاصیت <code>touches</code> استفاده کنیم.</p>
<h2><a class="h_ident" id="h_jSwIBZxznw" href="#h_jSwIBZxznw" tabindex="-1" role="presentation"></a>برنامهی کاربردی</h2>
<p>برای اینکه بتوان برنامه را جز به جز ساخت، مؤلفهی اصلی را به عنوان یک پوسته حول یک بوم تصویر پیاده سازی می کنیم و مجموعهای پویا از ابزارها و کنترلها را به سازندهاش ارسال می کنیم.</p>
<p>کنترلها، همان عناصر موجود در رابط کاربری هستند که در زیر تصویر قرار می گیرند. آنها به شکل آرایهای از سازندههای مؤلفه در دسترس قرار می گیرند.</p>
<p><a class="p_ident" id="p_gUSP4nMke1" href="#p_gUSP4nMke1" tabindex="-1" role="presentation"></a>ابزارها کارهایی مثل ترسیم پیکسلها یا رنگکردن یک قسمت را انجام میدهند. برنامه مجموعهای از ابزارها را به صورت یک فیلد <code><select></code> نمایش می دهد. ابزاری که انتخاب شده مشخص می کند که تعامل اشارهگر با تصویر چه خواهد بود. مجموعهی ابزارهای موجود به صورت یک شیء فراهم میشوند که نامهای نشان داده شده در فیلد select را به توابعی که آن ابزار را پیاده سازی می کنند، نگاشت می کند. این توابع یک موقعیت تصویر ، یک وضعیت فعلی برنامه و نیز یک تابع <code>dispatch</code> را به عنوان آرگومان می گیرند. خروجی آنها ممکن است یک گردانندهی حرکت باشد که با تغییر مکان اشارهگر به یک پیکسل دیگر و در نتیجه تغییر وضعیت و موقعیت جدید فراخوانی می شود.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_bksTXX2fO6" href="#c_bksTXX2fO6" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">PixelEditor</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">state</span>, <span class="cm-def">config</span>) {
<span class="cm-keyword">let</span> {<span class="cm-def">tools</span>, <span class="cm-def">controls</span>, <span class="cm-def">dispatch</span>} <span class="cm-operator">=</span> <span class="cm-variable-2">config</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">state</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">canvas</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">PictureCanvas</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>, <span class="cm-def">pos</span> <span class="cm-operator">=></span> {
<span class="cm-keyword">let</span> <span class="cm-def">tool</span> <span class="cm-operator">=</span> <span class="cm-variable-2">tools</span>[<span class="cm-keyword">this</span>.<span class="cm-property">state</span>.<span class="cm-property">tool</span>];
<span class="cm-keyword">let</span> <span class="cm-def">onMove</span> <span class="cm-operator">=</span> <span class="cm-variable-2">tool</span>(<span class="cm-variable-2">pos</span>, <span class="cm-keyword">this</span>.<span class="cm-property">state</span>, <span class="cm-variable-2">dispatch</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">onMove</span>) <span class="cm-keyword">return</span> <span class="cm-def">pos</span> <span class="cm-operator">=></span> <span class="cm-variable-2">onMove</span>(<span class="cm-variable-2">pos</span>, <span class="cm-keyword">this</span>.<span class="cm-property">state</span>);
});
<span class="cm-keyword">this</span>.<span class="cm-property">controls</span> <span class="cm-operator">=</span> <span class="cm-variable-2">controls</span>.<span class="cm-property">map</span>(
<span class="cm-def">Control</span> <span class="cm-operator">=></span> <span class="cm-keyword">new</span> <span class="cm-variable-2">Control</span>(<span class="cm-variable-2">state</span>, <span class="cm-variable-2">config</span>));
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"div"</span>, {}, <span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>.<span class="cm-property">dom</span>, <span class="cm-variable">elt</span>(<span class="cm-string">"br"</span>),
<span class="cm-meta">...</span><span class="cm-keyword">this</span>.<span class="cm-property">controls</span>.<span class="cm-property">reduce</span>(
(<span class="cm-def">a</span>, <span class="cm-def">c</span>) <span class="cm-operator">=></span> <span class="cm-variable-2">a</span>.<span class="cm-property">concat</span>(<span class="cm-string">" "</span>, <span class="cm-variable-2">c</span>.<span class="cm-property">dom</span>), []));
}
<span class="cm-property">syncState</span>(<span class="cm-def">state</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">state</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>.<span class="cm-property">syncState</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>);
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">ctrl</span> <span class="cm-keyword">of</span> <span class="cm-keyword">this</span>.<span class="cm-property">controls</span>) <span class="cm-variable-2">ctrl</span>.<span class="cm-property">syncState</span>(<span class="cm-variable-2">state</span>);
}
}</pre>
<p>گردانندهی اشارهگری که به <code>PictureCanvas</code> داده می شود ، ابزاری که در حال حاضر انتخاب شده است را با آرگومانهای مناسب فراخوانی می کند و اگر پاسخ آن یک گردانندهی حرکت باشد، آن را به گونهای تغییر می دهد که وضعیت را نیز دریافت کند.</p>
<p>تمامی کنترلها در <code>this.controls</code> ساختار یافته و ذخیره می شوند در نتیجه می توان آنها را با تغییر وضعیت برنامه بهروز رسانی کرد. فراخوانی <code>reduce</code> باعث به وجود آمدن فاصلههایی بین عناصر کنترلی DOM می شود. این کار باعث میشود طوری نمایش داده نشوند که به نظر برسد همگی باهم فشرده شده اند.</p>
<p>اولین کنترل، منوی انتخاب ابزار است. این کنترل یک عنصر <code><select></code> با یک گزینه برای هر ابزار می سازد و یک گردانندهی رخداد برای <code>"change"</code> تنظیم می کند که وضعیت برنامه را با انتخاب هر ابزار تغییر می دهد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_ErM/S5/GMa" href="#c_ErM/S5/GMa" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">ToolSelect</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">state</span>, {<span class="cm-def">tools</span>, <span class="cm-def">dispatch</span>}) {
<span class="cm-keyword">this</span>.<span class="cm-property">select</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"select"</span>, {
<span class="cm-property">onchange</span>: () <span class="cm-operator">=></span> <span class="cm-variable-2">dispatch</span>({<span class="cm-property">tool</span>: <span class="cm-keyword">this</span>.<span class="cm-property">select</span>.<span class="cm-property">value</span>})
}, <span class="cm-meta">...</span><span class="cm-variable">Object</span>.<span class="cm-property">keys</span>(<span class="cm-variable-2">tools</span>).<span class="cm-property">map</span>(<span class="cm-def">name</span> <span class="cm-operator">=></span> <span class="cm-variable">elt</span>(<span class="cm-string">"option"</span>, {
<span class="cm-property">selected</span>: <span class="cm-variable-2">name</span> <span class="cm-operator">==</span> <span class="cm-variable-2">state</span>.<span class="cm-property">tool</span>
}, <span class="cm-variable-2">name</span>)));
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"label"</span>, <span class="cm-atom">null</span>, <span class="cm-string">"🖌 Tool: "</span>, <span class="cm-keyword">this</span>.<span class="cm-property">select</span>);
}
<span class="cm-property">syncState</span>(<span class="cm-def">state</span>) { <span class="cm-keyword">this</span>.<span class="cm-property">select</span>.<span class="cm-property">value</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">tool</span>; }
}</pre>
<p>با قرار دادن متن عنوان فیلد و خود فیلد درون یک عنصر <code><label></code> به مرورگر اعلام میکنیم که عنوان مورد نظر مربوط به این فیلد می باشد در نتیجه می توانید به عنوان مثال روی عنوان کلیک کنید تا فیلد فعال شود.</p>
<p><a class="p_ident" id="p_GU4T2nIKHw" href="#p_GU4T2nIKHw" tabindex="-1" role="presentation"></a>همچنین لازم است تا بتوانیم رنگ را تغییر دهیم – پس اجازه بدهید تا یک کنترل برای آن در نظر بگیریم. یک عنصر <code><input></code> که خصیصهی <code>type</code> آن <code>color</code> است به ما فیلد فرمی را می دهد که برای انتخاب رنگ طراحی شده است. مقدار این فیلد همیشه یک کد رنگ CSS به فرمت <bdo><code>"#RRGGBB"</code></bdo> می باشد (قرمز ، سبز و آبی. برای هر رنگ دو رقم). مرورگر یک رابط انتخابگر رنگ را به کاربر نشان می دهد تا بتواند با آن کار کند.</p>
<p>این کنترل فیلد مذکور را ایجاد می کند و طوری آن را می نویسد که با خاصیت <code>color</code> وضعیت برنامه همگام بماند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_CNKBIFujc0" href="#c_CNKBIFujc0" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">ColorSelect</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">state</span>, {<span class="cm-def">dispatch</span>}) {
<span class="cm-keyword">this</span>.<span class="cm-property">input</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"input"</span>, {
<span class="cm-property">type</span>: <span class="cm-string">"color"</span>,
<span class="cm-property">value</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">color</span>,
<span class="cm-property">onchange</span>: () <span class="cm-operator">=></span> <span class="cm-variable-2">dispatch</span>({<span class="cm-property">color</span>: <span class="cm-keyword">this</span>.<span class="cm-property">input</span>.<span class="cm-property">value</span>})
});
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"label"</span>, <span class="cm-atom">null</span>, <span class="cm-string">"🎨 Color: "</span>, <span class="cm-keyword">this</span>.<span class="cm-property">input</span>);
}
<span class="cm-property">syncState</span>(<span class="cm-def">state</span>) { <span class="cm-keyword">this</span>.<span class="cm-property">input</span>.<span class="cm-property">value</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">color</span>; }
}</pre>
<h2><a class="h_ident" id="h_Tgvra9FON1" href="#h_Tgvra9FON1" tabindex="-1" role="presentation"></a>ابزار ترسیم</h2>
<p>پیش از آنکه بتوانیم چیزی رسم کنیم، لازم است تا ابزارهایی که قرار است کارکرد موس یا رخدادهای لمسی روی بوم را کنترل کند پیاده سازی کنیم.</p>
<p>پایهای ترین ابزار، ابزار ترسیم است که هر پیکسلی که روی آن کلیک می شود یا لمس می شود را به رنگ انتخاب شده تغییر می دهد. این ابزار اکشنی را برای تغییر تصویر به تصویری که در آن پیکسل مورد اشاره به رنگ انتخاب شده تغییر یافته است، گسیل مینماید.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_CZoPfAkoSo" href="#c_CZoPfAkoSo" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">draw</span>(<span class="cm-def">pos</span>, <span class="cm-def">state</span>, <span class="cm-def">dispatch</span>) {
<span class="cm-keyword">function</span> <span class="cm-def">drawPixel</span>({<span class="cm-def">x</span>, <span class="cm-def">y</span>}, <span class="cm-def">state</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">drawn</span> <span class="cm-operator">=</span> {<span class="cm-property">x</span>, <span class="cm-property">y</span>, <span class="cm-property">color</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">color</span>};
<span class="cm-variable-2">dispatch</span>({<span class="cm-property">picture</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>.<span class="cm-property">draw</span>([<span class="cm-variable-2">drawn</span>])});
}
<span class="cm-variable-2">drawPixel</span>(<span class="cm-variable-2">pos</span>, <span class="cm-variable-2">state</span>);
<span class="cm-keyword">return</span> <span class="cm-variable-2">drawPixel</span>;
}</pre>
<p>تابع بلافاصله تابع <code>drawPixel</code> را فراخوانی می کند و بعد آن را بر می گرداند در نتیجه دوباره برای پیکسلهای لمسشدهی دیگر در هنگامی که کاربر موس یا انگشتش را حرکت می دهد، فراخوانی می شود.</p>
<p>برای ترسیم اشکال بزرگتر بهتر است امکان کشیدن چهارضلعی فراهم باشد. ابزار <code>rectangle</code> یک چهارضلعی بین نقطهی شروع ترسیم و نقطهای که موس متوقف می شود ترسیم می کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_cHtZqBzkqi" href="#c_cHtZqBzkqi" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">rectangle</span>(<span class="cm-def">start</span>, <span class="cm-def">state</span>, <span class="cm-def">dispatch</span>) {
<span class="cm-keyword">function</span> <span class="cm-def">drawRectangle</span>(<span class="cm-def">pos</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">min</span>(<span class="cm-variable-2">start</span>.<span class="cm-property">x</span>, <span class="cm-variable-2">pos</span>.<span class="cm-property">x</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">min</span>(<span class="cm-variable-2">start</span>.<span class="cm-property">y</span>, <span class="cm-variable-2">pos</span>.<span class="cm-property">y</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">max</span>(<span class="cm-variable-2">start</span>.<span class="cm-property">x</span>, <span class="cm-variable-2">pos</span>.<span class="cm-property">x</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">max</span>(<span class="cm-variable-2">start</span>.<span class="cm-property">y</span>, <span class="cm-variable-2">pos</span>.<span class="cm-property">y</span>);
<span class="cm-keyword">let</span> <span class="cm-def">drawn</span> <span class="cm-operator">=</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-variable-2">drawn</span>.<span class="cm-property">push</span>({<span class="cm-property">x</span>, <span class="cm-property">y</span>, <span class="cm-property">color</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">color</span>});
}
}
<span class="cm-variable-2">dispatch</span>({<span class="cm-property">picture</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>.<span class="cm-property">draw</span>(<span class="cm-variable-2">drawn</span>)});
}
<span class="cm-variable-2">drawRectangle</span>(<span class="cm-variable-2">start</span>);
<span class="cm-keyword">return</span> <span class="cm-variable-2">drawRectangle</span>;
}</pre>
<p><a class="p_ident" id="p_ti2pVqALSu" href="#p_ti2pVqALSu" tabindex="-1" role="presentation"></a>یک نکتهی جزئی در پیاده سازی این ابزار این است که در هنگام کشیدن (dragging)، چهارضلعی از روی وضعیت اصلی روی تصویر بازترسیم میشود. در این حالت، می توانید چهارضلعی را در هنگام رسم بزرگتر یا کوچکتر کنید بدون اینکه چهارضلعی هایی که این میان رسم می شوند روی تصویر نهایی باقی بمانند. این یکی از دلایلی است که اشیاء تصویری غیرقابل تغییر مفید واقع می شوند – بعدا دلیل دیگری نیز خواهیم دید.</p>
<p><a class="p_ident" id="p_+P8ipBjEed" href="#p_+P8ipBjEed" tabindex="-1" role="presentation"></a>پیاده سازی رنگ آمیزی ناحیههای همرنگ یا متصل (flood fill) مقداری پیچیدهتر است. این ابزار پیکسل مورد اشاره و همهی پیکسلهای همجوارش که رنگ مشابهی دارند را رنگ می کند. همجوار به این معناست که به صورت افقی یا عمودی همجوار باشد نه به صورت قطری. این تصویر مجموعهی پیکسلهای رنگ شده در زمان استفاده از این ابزار مشخص را نشان می دهد:</p><figure><img src="img/flood-grid.svg" alt="A pixel grid showing the area filled by a flood fill operation"></figure>
<p>به شکل جالبی، روشی که ما برای انجام این کار استفاده می کنیم شبیه به کدی است که برای مسیریابی در <a href="07_robot.html">فصل 7</a> نوشتیم. در حالی که آن کد به جستجو در طول یک گراف برای یافتن یک مسیر می پرداخت، این کد به جستجو در یک گرید برای پیدا کردن پیکسلهای متصل می پردازد. مشکل نگهداری و رصد یک مجموعه شاخه از مسیرهای ممکن، شبیه به آن مساله است.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_mkCcD637J8" href="#c_mkCcD637J8" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">around</span> <span class="cm-operator">=</span> [{<span class="cm-property">dx</span>: <span class="cm-operator">-</span><span class="cm-number">1</span>, <span class="cm-property">dy</span>: <span class="cm-number">0</span>}, {<span class="cm-property">dx</span>: <span class="cm-number">1</span>, <span class="cm-property">dy</span>: <span class="cm-number">0</span>},
{<span class="cm-property">dx</span>: <span class="cm-number">0</span>, <span class="cm-property">dy</span>: <span class="cm-operator">-</span><span class="cm-number">1</span>}, {<span class="cm-property">dx</span>: <span class="cm-number">0</span>, <span class="cm-property">dy</span>: <span class="cm-number">1</span>}];
<span class="cm-keyword">function</span> <span class="cm-def">fill</span>({<span class="cm-def">x</span>, <span class="cm-def">y</span>}, <span class="cm-def">state</span>, <span class="cm-def">dispatch</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">targetColor</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>.<span class="cm-property">pixel</span>(<span class="cm-variable-2">x</span>, <span class="cm-variable-2">y</span>);
<span class="cm-keyword">let</span> <span class="cm-def">drawn</span> <span class="cm-operator">=</span> [{<span class="cm-property">x</span>, <span class="cm-property">y</span>, <span class="cm-property">color</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">color</span>}];
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">done</span> <span class="cm-operator">=</span> <span class="cm-number">0</span>; <span class="cm-variable-2">done</span> <span class="cm-operator"><</span> <span class="cm-variable-2">drawn</span>.<span class="cm-property">length</span>; <span class="cm-variable-2">done</span><span class="cm-operator">++</span>) {
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> {<span class="cm-def">dx</span>, <span class="cm-def">dy</span>} <span class="cm-keyword">of</span> <span class="cm-variable">around</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">x</span> <span class="cm-operator">=</span> <span class="cm-variable-2">drawn</span>[<span class="cm-variable-2">done</span>].<span class="cm-property">x</span> <span class="cm-operator">+</span> <span class="cm-variable-2">dx</span>, <span class="cm-def">y</span> <span class="cm-operator">=</span> <span class="cm-variable-2">drawn</span>[<span class="cm-variable-2">done</span>].<span class="cm-property">y</span> <span class="cm-operator">+</span> <span class="cm-variable-2">dy</span>;
<span class="cm-keyword">if</span> (<span class="cm-variable-2">x</span> <span class="cm-operator">>=</span> <span class="cm-number">0</span> <span class="cm-operator">&</span><span class="cm-operator">&</span> <span class="cm-variable-2">x</span> <span class="cm-operator"><</span> <span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>.<span class="cm-property">width</span> <span class="cm-operator">&</span><span class="cm-operator">&</span>
<span class="cm-variable-2">y</span> <span class="cm-operator">>=</span> <span class="cm-number">0</span> <span class="cm-operator">&</span><span class="cm-operator">&</span> <span class="cm-variable-2">y</span> <span class="cm-operator"><</span> <span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>.<span class="cm-property">height</span> <span class="cm-operator">&</span><span class="cm-operator">&</span>
<span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>.<span class="cm-property">pixel</span>(<span class="cm-variable-2">x</span>, <span class="cm-variable-2">y</span>) <span class="cm-operator">==</span> <span class="cm-variable-2">targetColor</span> <span class="cm-operator">&</span><span class="cm-operator">&</span>
<span class="cm-operator">!</span><span class="cm-variable-2">drawn</span>.<span class="cm-property">some</span>(<span class="cm-def">p</span> <span class="cm-operator">=></span> <span class="cm-variable-2">p</span>.<span class="cm-property">x</span> <span class="cm-operator">==</span> <span class="cm-variable-2">x</span> <span class="cm-operator">&</span><span class="cm-operator">&</span> <span class="cm-variable-2">p</span>.<span class="cm-property">y</span> <span class="cm-operator">==</span> <span class="cm-variable-2">y</span>)) {
<span class="cm-variable-2">drawn</span>.<span class="cm-property">push</span>({<span class="cm-property">x</span>, <span class="cm-property">y</span>, <span class="cm-property">color</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">color</span>});
}
}
}
<span class="cm-variable-2">dispatch</span>({<span class="cm-property">picture</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>.<span class="cm-property">draw</span>(<span class="cm-variable-2">drawn</span>)});
}</pre>
<p>آرایهی پیکسلهای رسم شده ، به عنوان لیست کار این تابع استفاده میشود. برای هر پیکسل که بررسی می شود، بایستی ببینیم که آیا پیکسلهای همجوارش رنگ مشابهی دارند یا خیر و آیا قبل از این رنگآمیزی شده اند. با ورود پیکسلهای جدید، شمارندهی حلقه از طول آرایهی <code>drawn</code> عقب میافتد. تمامی پیکسلهای جلوتر از آن همچنان لازم است تا بررسی شوند. زمانی که شمارنده به طول آرایه می رسد ، هیچ پیکسل بررسی نشدهای نمی ماند و کار تابع تمام می شود.</p>
<p>آخرین ابزار، گزینشگر رنگ می باشد که به شما امکان انتخاب یک رنگ با استفاده از اشاره گر و استفاده از آن به عنوان رنگ فعلی ترسیم را می دهد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_JK2K2M0XJH" href="#c_JK2K2M0XJH" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">pick</span>(<span class="cm-def">pos</span>, <span class="cm-def">state</span>, <span class="cm-def">dispatch</span>) {
<span class="cm-variable-2">dispatch</span>({<span class="cm-property">color</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>.<span class="cm-property">pixel</span>(<span class="cm-variable-2">pos</span>.<span class="cm-property">x</span>, <span class="cm-variable-2">pos</span>.<span class="cm-property">y</span>)});
}</pre>
<p>اکنون می توانیم اپلیکیشنمان را آزمایش نماییم!</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_OAsV4NrCrn" href="#c_OAsV4NrCrn" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">div</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">state</span> <span class="cm-operator">=</span> {
<span class="cm-property">tool</span>: <span class="cm-string">"draw"</span>,
<span class="cm-property">color</span>: <span class="cm-string">"#000000"</span>,
<span class="cm-property">picture</span>: <span class="cm-variable">Picture</span>.<span class="cm-property">empty</span>(<span class="cm-number">60</span>, <span class="cm-number">30</span>, <span class="cm-string">"#f0f0f0"</span>)
};
<span class="cm-keyword">let</span> <span class="cm-def">app</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">PixelEditor</span>(<span class="cm-variable">state</span>, {
<span class="cm-property">tools</span>: {<span class="cm-property">draw</span>, <span class="cm-property">fill</span>, <span class="cm-property">rectangle</span>, <span class="cm-property">pick</span>},
<span class="cm-property">controls</span>: [<span class="cm-variable">ToolSelect</span>, <span class="cm-variable">ColorSelect</span>],
<span class="cm-property">dispatch</span>(<span class="cm-def">action</span>) {
<span class="cm-variable">state</span> <span class="cm-operator">=</span> <span class="cm-variable">updateState</span>(<span class="cm-variable">state</span>, <span class="cm-variable-2">action</span>);
<span class="cm-variable">app</span>.<span class="cm-property">syncState</span>(<span class="cm-variable">state</span>);
}
});
<span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"div"</span>).<span class="cm-property">appendChild</span>(<span class="cm-variable">app</span>.<span class="cm-property">dom</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_cXpv5q/Zyd" href="#h_cXpv5q/Zyd" tabindex="-1" role="presentation"></a>ذخیره سازی و بارگیری</h2>
<p>پس از اتمام ترسیم شاهکار هنریمان، می خواهیم آن را ذخیره کنیم. بایستی دکمهای اضافه کنیم که بتوان به وسیلهی آن تصویر فعلی را به شکل یک فایل عکسی بارگیری کرد. این کنترل دکمهی مذکور را ایجاد می کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_4+FAPgY7mH" href="#c_4+FAPgY7mH" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">SaveButton</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">state</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">picture</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"button"</span>, {
<span class="cm-property">onclick</span>: () <span class="cm-operator">=></span> <span class="cm-keyword">this</span>.<span class="cm-property">save</span>()
}, <span class="cm-string">"💾 Save"</span>);
}
<span class="cm-property">save</span>() {
<span class="cm-keyword">let</span> <span class="cm-def">canvas</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"canvas"</span>);
<span class="cm-variable">drawPicture</span>(<span class="cm-keyword">this</span>.<span class="cm-property">picture</span>, <span class="cm-variable-2">canvas</span>, <span class="cm-number">1</span>);
<span class="cm-keyword">let</span> <span class="cm-def">link</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"a"</span>, {
<span class="cm-property">href</span>: <span class="cm-variable-2">canvas</span>.<span class="cm-property">toDataURL</span>(),
<span class="cm-property">download</span>: <span class="cm-string">"pixelart.png"</span>
});
<span class="cm-variable">document</span>.<span class="cm-property">body</span>.<span class="cm-property">appendChild</span>(<span class="cm-variable-2">link</span>);
<span class="cm-variable-2">link</span>.<span class="cm-property">click</span>();
<span class="cm-variable-2">link</span>.<span class="cm-property">remove</span>();
}
<span class="cm-property">syncState</span>(<span class="cm-def">state</span>) { <span class="cm-keyword">this</span>.<span class="cm-property">picture</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>; }
}</pre>
<p>این مؤلفه تصویر فعلی را رصد کرده در نتیجه می تواند آن را ذخیره کند. برای ساخت یک فایل تصویری ، از عنصر <code><canvas></code> که تصویر را روی آن رسم می کنیم (در مقیاس یک پیکسل بر پیکسل) استفاده می کنیم.</p>
<p><a class="p_ident" id="p_R8UtG0ucfb" href="#p_R8UtG0ucfb" tabindex="-1" role="presentation"></a>متد <code>toDataURL</code> روی یک canvas یک URL ایجاد می کند که با <bdo><code>data:</code></bdo> شروع می شود. برخلاف URLهای <bdo><code>http:</code></bdo> و <bdo><code>https:</code></bdo> آدرس (URL)های از نوع data حاوی تمام منبع در خود URL می باشند. این URL ها معمولا خیلی بلند هستند اما به ما این امکان را می دهند تا پیوندهای واقعی به تصاویر دلخواه بسازیم، در دل خود مرورگر.</p>
<p><a class="p_ident" id="p_Di2bB3ekhc" href="#p_Di2bB3ekhc" tabindex="-1" role="presentation"></a>برای اینکه کاری کنیم تا مرورگر تصویر ایجاد شده را دانلود کند، یک عنصر لینک می سازیم که به این URL اشاره می کند و دارای یک خصیصهی <code>download</code> میباشد. این گونه لینکها، در زمان کلیک شدن، باعث می شوند که مرورگر یک پنجرهی محاورهای ذخیره فایل را نمایش دهند. ما آن لینک را به سند اضافه می کنیم و عمل کلیک کردن را روی آن شبیهسازی می کنیم و سپس آن را حذف می کنیم.</p>
<p>کارهای زیادی با تکنولوژی مرورگر ها می توان انجام داد اما گاهی اوقات برای انجام کاری لازم است که از روشهای نامانوس استفاده کنیم.</p>
<p>و بدتر هم میشود. لازم است بتوانیم یک فایل تصویری را نیز به درون برنامه بارگیری کنیم. برای این کار، دوباره یک مؤلفه دکمه می سازیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_6tKjwwpwls" href="#c_6tKjwwpwls" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">LoadButton</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">_</span>, {<span class="cm-def">dispatch</span>}) {
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"button"</span>, {
<span class="cm-property">onclick</span>: () <span class="cm-operator">=></span> <span class="cm-variable">startLoad</span>(<span class="cm-variable-2">dispatch</span>)
}, <span class="cm-string">"📁 Load"</span>);
}
<span class="cm-property">syncState</span>() {}
}
<span class="cm-keyword">function</span> <span class="cm-def">startLoad</span>(<span class="cm-def">dispatch</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">input</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"input"</span>, {
<span class="cm-property">type</span>: <span class="cm-string">"file"</span>,
<span class="cm-property">onchange</span>: () <span class="cm-operator">=></span> <span class="cm-variable">finishLoad</span>(<span class="cm-variable-2">input</span>.<span class="cm-property">files</span>[<span class="cm-number">0</span>], <span class="cm-variable-2">dispatch</span>)
});
<span class="cm-variable">document</span>.<span class="cm-property">body</span>.<span class="cm-property">appendChild</span>(<span class="cm-variable-2">input</span>);
<span class="cm-variable-2">input</span>.<span class="cm-property">click</span>();
<span class="cm-variable-2">input</span>.<span class="cm-property">remove</span>();
}</pre>
<p>برای دسترسی به یک فایل روی کامپیوتر کاربر، کاربر بایستی یک فایل را با استفاده از فیلد ورودی فایل انتخاب کند. اما من نمی خواهم که دکمهی بارگیری شبیه به فیلد ورودی فایل باشد؛ بنابراین بعد از فشردن دکمه، فیلد فایل را ایجاد می کنیم و سپس وانمود می کنیم که خودش کلیک را انجام داده است.</p>
<p><a class="p_ident" id="p_SjaIdtW+ZE" href="#p_SjaIdtW+ZE" tabindex="-1" role="presentation"></a>زمانی که کاربر یک فایل را انتخاب می کند، می توانیم از <code>FileReader</code> برای دسترسی به محتوای آن استفاده کنیم و دوباره به عنوان یک data URL. این URL را می توان برای ایجاد یک عنصر <code><img></code> استفاده کرد اما به دلیل اینکه دسترسی مستقیمی به پیکسلهای این تصویر نداریم، نمی توانیم یک شیء <code>Picture</code> از آن بسازیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_bRy4XNFu3R" href="#c_bRy4XNFu3R" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">finishLoad</span>(<span class="cm-def">file</span>, <span class="cm-def">dispatch</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">file</span> <span class="cm-operator">==</span> <span class="cm-atom">null</span>) <span class="cm-keyword">return</span>;
<span class="cm-keyword">let</span> <span class="cm-def">reader</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">FileReader</span>();
<span class="cm-variable-2">reader</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">image</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"img"</span>, {
<span class="cm-property">onload</span>: () <span class="cm-operator">=></span> <span class="cm-variable-2">dispatch</span>({
<span class="cm-property">picture</span>: <span class="cm-variable">pictureFromImage</span>(<span class="cm-variable-2">image</span>)
}),
<span class="cm-property">src</span>: <span class="cm-variable-2">reader</span>.<span class="cm-property">result</span>
});
});
<span class="cm-variable-2">reader</span>.<span class="cm-property">readAsDataURL</span>(<span class="cm-variable-2">file</span>);
}</pre>
<p><a class="p_ident" id="p_Fbyu6tSkzl" href="#p_Fbyu6tSkzl" tabindex="-1" role="presentation"></a>برای دسترسی به پیکسلها بایستی ابتدا یک تصویر روی عنصر <code><canvas></code> رسم کنیم. context این canvas متدی به نام <code>getImageData</code> دارد که به جاوااسکریپت این امکان را می دهد تا پیکسل های آن را بخواند. بنابراین با قرار گرفتن تصویر روی بوم، می توانیم به آن دسترسی داشته باشید و شی <code>Picture</code> را بسازیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_bHE23Qbgos" href="#c_bHE23Qbgos" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">pictureFromImage</span>(<span class="cm-def">image</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">width</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">min</span>(<span class="cm-number">100</span>, <span class="cm-variable-2">image</span>.<span class="cm-property">width</span>);
<span class="cm-keyword">let</span> <span class="cm-def">height</span> <span class="cm-operator">=</span> <span class="cm-variable">Math</span>.<span class="cm-property">min</span>(<span class="cm-number">100</span>, <span class="cm-variable-2">image</span>.<span class="cm-property">height</span>);
<span class="cm-keyword">let</span> <span class="cm-def">canvas</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"canvas"</span>, {<span class="cm-property">width</span>, <span class="cm-property">height</span>});
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable-2">canvas</span>.<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</span>);
<span class="cm-variable-2">cx</span>.<span class="cm-property">drawImage</span>(<span class="cm-variable-2">image</span>, <span class="cm-number">0</span>, <span class="cm-number">0</span>);
<span class="cm-keyword">let</span> <span class="cm-def">pixels</span> <span class="cm-operator">=</span> [];
<span class="cm-keyword">let</span> {<span class="cm-def">data</span>} <span class="cm-operator">=</span> <span class="cm-variable-2">cx</span>.<span class="cm-property">getImageData</span>(<span class="cm-number">0</span>, <span class="cm-number">0</span>, <span class="cm-variable-2">width</span>, <span class="cm-variable-2">height</span>);
<span class="cm-keyword">function</span> <span class="cm-def">hex</span>(<span class="cm-def">n</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable-2">n</span>.<span class="cm-property">toString</span>(<span class="cm-number">16</span>).<span class="cm-property">padStart</span>(<span class="cm-number">2</span>, <span class="cm-string">"0"</span>);
}
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">i</span> <span class="cm-operator">=</span> <span class="cm-number">0</span>; <span class="cm-variable-2">i</span> <span class="cm-operator"><</span> <span class="cm-variable-2">data</span>.<span class="cm-property">length</span>; <span class="cm-variable-2">i</span> <span class="cm-operator">+=</span> <span class="cm-number">4</span>) {
<span class="cm-keyword">let</span> [<span class="cm-def">r</span>, <span class="cm-def">g</span>, <span class="cm-def">b</span>] <span class="cm-operator">=</span> <span class="cm-variable-2">data</span>.<span class="cm-property">slice</span>(<span class="cm-variable-2">i</span>, <span class="cm-variable-2">i</span> <span class="cm-operator">+</span> <span class="cm-number">3</span>);
<span class="cm-variable-2">pixels</span>.<span class="cm-property">push</span>(<span class="cm-string">"#"</span> <span class="cm-operator">+</span> <span class="cm-variable-2">hex</span>(<span class="cm-variable-2">r</span>) <span class="cm-operator">+</span> <span class="cm-variable-2">hex</span>(<span class="cm-variable-2">g</span>) <span class="cm-operator">+</span> <span class="cm-variable-2">hex</span>(<span class="cm-variable-2">b</span>));
}
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Picture</span>(<span class="cm-variable-2">width</span>, <span class="cm-variable-2">height</span>, <span class="cm-variable-2">pixels</span>);
}</pre>
<p><a class="p_ident" id="p_t/23YReLHg" href="#p_t/23YReLHg" tabindex="-1" role="presentation"></a>ما اندازهی تصاویر را به 100 در 100 پیکسل محدود خواهیم کرد به این دلیل که هر تصویر بزرگتری روی صفحهنمایش ما خیلی بزرگ به نظر می رسد و ممکن است باعث کندی رابط کاربری شود.</p>
<p><a class="p_ident" id="p_O67gJLjmuD" href="#p_O67gJLjmuD" tabindex="-1" role="presentation"></a>خاصیت <code>data</code> مربوط به شیءای که با <code>getImageData</code> برمی گردد، آرایهای از مؤلفههای رنگ می باشد. برای هر پیکسل متعلق به چهارضلعی، که توسط آرگومانها مشخص شده است، دارای چهار مقدار میباد که نمایانگر اجزای قرمز، سبز، آبی و آلفای رنگ پیکسل میباشند که اعدادی بین 0 و 255 هستند. بخش alpha مربوط به شفافیت می باشد – زمانی که برابر صفر است، پیکسل کاملا شفاف می باشد و وقتی 255 است کامل مات می شود. برای مقصود ما، می توانیم از آن صرف نظر کنیم.</p>
<p><a class="p_ident" id="p_sr1m3Ckj6U" href="#p_sr1m3Ckj6U" tabindex="-1" role="presentation"></a>دو عدد هگزادسیمال برای هر مؤلفه که در مقدار رنگ ما آمده است، به شکلی دقیق متناظر با طیف 0 تا 255 است – دو رقم مبنای 16 می توانند <bdo>16<sup>2</sup> = 256</bdo> عدد متفاوت را نمایش دهند. به متد <code>toString</code> مربوط به اعداد می توان یک مبنا به عنوان آرگومان فرستاد، بنابراین <bdo><code>n.toString(16)</code></bdo> رشتهای تولید می کند که در مبنای 16 نشان داده می شود. بایستی مطمئن شویم که هر عدد دو رقم اشغال می کند در نتیجه تابع کمکی <code>hex</code> تابع <code>padStart</code> را برای افزودن صفرهای پیشین در صورت نیاز فراخوانی خواهد شد.</p>
<p>اکنون می توانیم بارگیری و ذخیره کنیم. یک امکان دیگر می ماند تا کار تمام شود.</p>
<h2><a class="h_ident" id="h_+EYMOBdnwC" href="#h_+EYMOBdnwC" tabindex="-1" role="presentation"></a>بازگشت در تاریخچه (undo)</h2>
<p>نیمی از روند عمل ویرایش شامل انجام اشتباهات کوچک و برطرف کردن آنها است. بنابراین یک ویژگی و کارکرد مهم در یک برنامهی ترسیم این است که بتوان در تاریخچه به عقب بازگشت.</p>
<p>برای اینکه بتوانیم تغییرات را خنثی کنیم، لازم است تا نسخهی قبلی از تصویر را جایی ذخیره کنیم. به دلیل اینکه این مقدار غیرقابل تغییر می باشد این کار آسان است. اما این کار مستلزم آن است که فیلد دیگری در وضعیت برنامه تعبیه شود.</p>
<p>ما آرایهای به نام <code>done</code> اضافه خواهیم کرد تا نسخههای قبلی تصویر را نگهداری کنیم. نگهداری این خاصیت نیازمند تابع بهروز رسانی وضعیت پیچیدهتری می باشد که تصاویر را به آرایه اضافه کند.</p>
<p>اما ما قصد نداریم هر تغییری را ذخیره کنیم. فقط تغییراتی که در بازهی زمانی خاصی رخ داده باشند. برای انجام این کار، نیاز به خاصیتی دومی هست، <code>doneAt،</code> که زمان آخرین ذخیرهی یک تصویر در تاریخچه را نگهداری می کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_xx2001jEVe" href="#c_xx2001jEVe" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">historyUpdateState</span>(<span class="cm-def">state</span>, <span class="cm-def">action</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">action</span>.<span class="cm-property">undo</span> <span class="cm-operator">==</span> <span class="cm-atom">true</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">state</span>.<span class="cm-property">done</span>.<span class="cm-property">length</span> <span class="cm-operator">==</span> <span class="cm-number">0</span>) <span class="cm-keyword">return</span> <span class="cm-variable-2">state</span>;
<span class="cm-keyword">return</span> <span class="cm-variable">Object</span>.<span class="cm-property">assign</span>({}, <span class="cm-variable-2">state</span>, {
<span class="cm-property">picture</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">done</span>[<span class="cm-number">0</span>],
<span class="cm-property">done</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">done</span>.<span class="cm-property">slice</span>(<span class="cm-number">1</span>),
<span class="cm-property">doneAt</span>: <span class="cm-number">0</span>
});
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">action</span>.<span class="cm-property">picture</span> <span class="cm-operator">&</span><span class="cm-operator">&</span>
<span class="cm-variable-2">state</span>.<span class="cm-property">doneAt</span> <span class="cm-operator"><</span> <span class="cm-variable">Date</span>.<span class="cm-property">now</span>() <span class="cm-operator">-</span> <span class="cm-number">1000</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable">Object</span>.<span class="cm-property">assign</span>({}, <span class="cm-variable-2">state</span>, <span class="cm-variable-2">action</span>, {
<span class="cm-property">done</span>: [<span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>, <span class="cm-meta">...</span><span class="cm-variable-2">state</span>.<span class="cm-property">done</span>],
<span class="cm-property">doneAt</span>: <span class="cm-variable">Date</span>.<span class="cm-property">now</span>()
});
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">return</span> <span class="cm-variable">Object</span>.<span class="cm-property">assign</span>({}, <span class="cm-variable-2">state</span>, <span class="cm-variable-2">action</span>);
}
}</pre>
<p>زمانی که اکشن مااز جنس خنثی کردن تغییر باشد، تابع، آخرین تصویر موجود در تاریخچه را برداشته و آن را به عنوان تصویر فعلی قرار می دهد. همچنین مقدار <code>doneAt</code> را برابر صفر قرار میدهد تا تغییر بعدی در تاریخچه ثبت شود و امکان عقبگرد برای آن در صورت نیاز فراهم باشد.</p>
<p><a class="p_ident" id="p_48u6iIP+dG" href="#p_48u6iIP+dG" tabindex="-1" role="presentation"></a>در غیر این صورت، اگر اکشن حاوی تصویر جدیدی باشد و آخرین باری که ما تصویری را ذخیره کرده ایم بیش از یک ثانیه عقب تر باشد ( 1000 میلی ثانیه) خاصیتهای <code>done</code> و <code>doneAt</code> بهروز می شوند تا تصویر قبلی را ذخیره می کنند.</p>
<p><a class="p_ident" id="p_XG+ib0k4KX" href="#p_XG+ib0k4KX" tabindex="-1" role="presentation"></a>مؤلفهی دکمهی خنثی (undo) کاری زیادی انجام نمی دهد. اکشنهای خنثی سازی را با بروز کلیک گسیل می دهد و زمانی که چیزی برای بازگشت یا خنثی سازی موجود نیست، خودش را غیرفعال می کند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_V5SwZIdQv8" href="#c_V5SwZIdQv8" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">UndoButton</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">state</span>, {<span class="cm-def">dispatch</span>}) {
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"button"</span>, {
<span class="cm-property">onclick</span>: () <span class="cm-operator">=></span> <span class="cm-variable-2">dispatch</span>({<span class="cm-property">undo</span>: <span class="cm-atom">true</span>}),
<span class="cm-property">disabled</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">done</span>.<span class="cm-property">length</span> <span class="cm-operator">==</span> <span class="cm-number">0</span>
}, <span class="cm-string">"⮪ Undo"</span>);
}
<span class="cm-property">syncState</span>(<span class="cm-def">state</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span>.<span class="cm-property">disabled</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">done</span>.<span class="cm-property">length</span> <span class="cm-operator">==</span> <span class="cm-number">0</span>;
}
}</pre>
<h2><a class="h_ident" id="h_xnB9hqgo0B" href="#h_xnB9hqgo0B" tabindex="-1" role="presentation"></a>اکنون زمان ترسیم است</h2>
<p><a class="p_ident" id="p_BbFFz7b7wk" href="#p_BbFFz7b7wk" tabindex="-1" role="presentation"></a>برای بالا آوردن برنامه، نیاز است تا یک وضعیت، مجموعهای از ابزار، مجموعهای از کنترلها و یک تابع گسیل (dispatch) را ایجاد کنیم. می توانیم آن ها را به سازندهی <code>PixelEditor</code> ارسال نماییم تا مؤلفهی اصلی را ایجاد نماید. به دلیل اینکه نیاز است تا چندین ویرایشگر در بخش تمرینها بسازیم، در اینجا چند متغیر تعریف می کنیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_PyNgZwUySd" href="#c_PyNgZwUySd" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">startState</span> <span class="cm-operator">=</span> {
<span class="cm-property">tool</span>: <span class="cm-string">"draw"</span>,
<span class="cm-property">color</span>: <span class="cm-string">"#000000"</span>,
<span class="cm-property">picture</span>: <span class="cm-variable">Picture</span>.<span class="cm-property">empty</span>(<span class="cm-number">60</span>, <span class="cm-number">30</span>, <span class="cm-string">"#f0f0f0"</span>),
<span class="cm-property">done</span>: [],
<span class="cm-property">doneAt</span>: <span class="cm-number">0</span>
};
<span class="cm-keyword">const</span> <span class="cm-def">baseTools</span> <span class="cm-operator">=</span> {<span class="cm-property">draw</span>, <span class="cm-property">fill</span>, <span class="cm-property">rectangle</span>, <span class="cm-property">pick</span>};
<span class="cm-keyword">const</span> <span class="cm-def">baseControls</span> <span class="cm-operator">=</span> [
<span class="cm-variable">ToolSelect</span>, <span class="cm-variable">ColorSelect</span>, <span class="cm-variable">SaveButton</span>, <span class="cm-variable">LoadButton</span>, <span class="cm-variable">UndoButton</span>
];
<span class="cm-keyword">function</span> <span class="cm-def">startPixelEditor</span>({<span class="cm-def">state</span> <span class="cm-operator">=</span> <span class="cm-variable">startState</span>,
<span class="cm-def">tools</span> <span class="cm-operator">=</span> <span class="cm-variable">baseTools</span>,
<span class="cm-def">controls</span> <span class="cm-operator">=</span> <span class="cm-variable">baseControls</span>}) {
<span class="cm-keyword">let</span> <span class="cm-def">app</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">PixelEditor</span>(<span class="cm-variable-2">state</span>, {
<span class="cm-property">tools</span>,
<span class="cm-property">controls</span>,
<span class="cm-property">dispatch</span>(<span class="cm-def">action</span>) {
<span class="cm-variable-2">state</span> <span class="cm-operator">=</span> <span class="cm-variable">historyUpdateState</span>(<span class="cm-variable-2">state</span>, <span class="cm-variable-2">action</span>);
<span class="cm-variable-2">app</span>.<span class="cm-property">syncState</span>(<span class="cm-variable-2">state</span>);
}
});
<span class="cm-keyword">return</span> <span class="cm-variable-2">app</span>.<span class="cm-property">dom</span>;
}</pre>
<p><a class="p_ident" id="p_XdMGqh6Y14" href="#p_XdMGqh6Y14" tabindex="-1" role="presentation"></a>زمانی که یک شیء یا یک آرایه را destruct می کنیم، می توانیم از = بعد از نام متغیر برای تعیین یک مقدار پیشفرض برای متغیر استفاده کنیم که در زمانی که خاصیت موجود نیست یا برابر <code>undefined</code> می باشد استفاده می شود. تابع <code>startPixelEditor</code> از این خاصیت استفاده می کند تا شیءای را با تعدادی خاصیت اختیاری به عنوان آرگومان قبول می کند. اگر خاصیت <code>tools</code> را فراهم نسازید به عنوان مثال <code>tools</code> به <code>baseTools</code> متناظر خواهد شد.</p>
<p>و این روشی است که ما به وسیلهی آن، یک ویرایشگر واقعی را روی صفحهی نمایش اجرا می کنیم</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_eYhWs5adxG" href="#c_eYhWs5adxG" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">div</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">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"div"</span>)
.<span class="cm-property">appendChild</span>(<span class="cm-variable">startPixelEditor</span>({}));
<span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p>می توانید کمی به ترسیم بپردازید. من منتظر میمانم.</p>
<h2><a class="h_ident" id="h_fZGagHbPYt" href="#h_fZGagHbPYt" tabindex="-1" role="presentation"></a>چرا این کار این قدر پرزحمت است</h2>
<p>تکنولوژی مرورگرها شگفتانگیز است.این تکنولوژی مجموعهای قدرتمند از بلاکها برای ساخت رابط، روشهای سبکدهی و تغییر سبک، و ابزارهایی برای اشکالزدایی و بررسی برنامههایتان را فراهم میسازد. نرمافزاری که برای مرورگر می نویسید تقریبا روی هر کامپیوتر یا گوشی یا تبلتی اجرا می شود.</p>
<p>در عین حال، تکنولوژی مرورگر مزخرف است. شما باید مجموعهای بزرگ از ترفندهای احمقانه و اطلاعات مبهم را یاد بگیرید تا به آن مسلط شوید و همچنین مدل برنامهنویسی پیشفرضی که فراهم می سازد بسیار مشکلزا است که بیشتر برنامه نویسان ترجیح می دهند تا آن را با لایههای متعددی از تجرید بپوشانند تا این که مستقیما با آن کار کنند.</p>
<p>اگرچه این شرایط قطعا در حال بهبود می باشد، اما بیشتر این تغییرات در قالب معرفی عناصر جدید برای پوشش کمبودها انجام می شود که خود به پیچیدگی آن افزوده است. یک ویژگی که توسط میلیونها وبسایت استفاده می شود را نمی توان جایگزین کرد. و حتی اگر این کار شدنی باشد، اینکه با چه جایگزین شود خود تصمیمی سخت است.</p>
<p>تکنولوژی هیچ وقت در خلاء قابل بررسی نیست – ما توسط ابزارهایمان و جامعه،اقتصاد و فاکتورهای تاریخی که آن ها را ایجاد کرده است محدود می شویم. ممکن است این واقعیت آزاردهنده باشد، اما به طول کلی پربارتر خواهد بود اگر تلاش کنیم تا فهم خوبی از نحوهی کارکرد واقعیتهای فنی کنونی داشته باشد- و اینکه چرا به شکل فعلی کار می کند- تا اینکه به جنگ آن برویم یا مقاومت پذیرش آن بخواهیم چیزی دیگری را درخواست کنیم.</p>
<p><a class="p_ident" id="p_HAhlwL5kw6" href="#p_HAhlwL5kw6" tabindex="-1" role="presentation"></a>تجریدهای جدید می توانند مفید باشند. مدل مؤلفه و روش جریان داده ای که من در این فصل استفاده کردم شکل خاصی از آنها بود. همانطور که ذکر شد، کتابخانههایی وجود دارند که باعث می شوند برنامهنویسی رابط کاربری بسیار دلپذیر تر بشود. در زمان نوشتن این کتاب ، <a href="https://reactjs.org/">React</a> و <a href="https://angular.io/">Angular</a> انتخابهای پرآوازهای هستند اما صنعت پرتحرکی از این چهارچوبها در حال فعالیت می باشد. اگر به برنامه نویسی اپلیکیشنهای وب علاقمند هستید، پیشنهاد می کنم چند تایی از آن ها را بررسی کنید تا از نحوهی کارکرد آن ها باخبر شوید و ببینید چه مزایایی با خود می آورند.</p>
<h2><a class="h_ident" id="h_ggOFdVwDCk" href="#h_ggOFdVwDCk" tabindex="-1" role="presentation"></a>تمرینها</h2>
<p>برنامهی ما هنوز جای بهتر شدن دارد. اجازه بدهید چند ویژگی جدید به عنوان تمرین به آن اضافه کنیم.</p>
<h3><a class="i_ident" id="i_shTeBMdxOj" href="#i_shTeBMdxOj" tabindex="-1" role="presentation"></a>میانبرهای صفحهکلید</h3>
<p><a class="p_ident" id="p_ntQj7OHCh9" href="#p_ntQj7OHCh9" tabindex="-1" role="presentation"></a>به برنامه میانبر های صفحه کلید اضافه کنید. اولین حرف هر ابزار برای انتخاب آن ابزار و <span class="keyname">control</span>-Z یا <span class="keyname">command</span>-Z برای انجام خنثی سازی.</p>
<p><a class="p_ident" id="p_UQGVovmP3W" href="#p_UQGVovmP3W" tabindex="-1" role="presentation"></a>این کار را با ایجاد تغییر در مؤلفهی <code>PixelEditor</code> انجام دهید. به خاصیت <code>tabIndex</code> متعلق به عنصر پوششی <code><div></code> ، مقدار 0 را اعمال کنید. در نتیجه می تواند توسط صفحهکلید فعال شود. توجه داشته باشید که خاصیت مذکور که به خصیصهی <code>tabindex</code> متناظر است <code>tabIndex</code> خوانده می شود که حرف اول آن بزرگ است و تابع <code>elt</code> ما نامهای خاصیت را قبول می کند. گردانندههای رخدادهای صفحهکلید را مستقیما روی آن عنصر ثبت کنید. یعنی باید کلیک و لمس touch یا tab روی برنامه انجام شود قبل از اینکه بتوانید با آن توسط صفحه کلید تعامل کنید.</p>
<p><a class="p_ident" id="p_GSX3kwk1gc" href="#p_GSX3kwk1gc" tabindex="-1" role="presentation"></a>به خاطر داشته باشید که رخدادهای صفحهکلید دارای خاصیتهای <code>ctrlKey</code> و <code>metaKey</code> (برای کلید <span class="keyname">command</span> در مک) می باشند که می توانید از آن ها برای اینکه ببینید آیا این کلیدها پایین نگهداشته شده اند استفاده کنید.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_9RBHLjfr9C" href="#c_9RBHLjfr9C" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">div</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-comment">// The original PixelEditor class. Extend the constructor.</span>
<span class="cm-keyword">class</span> <span class="cm-def">PixelEditor</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">state</span>, <span class="cm-def">config</span>) {
<span class="cm-keyword">let</span> {<span class="cm-def">tools</span>, <span class="cm-def">controls</span>, <span class="cm-def">dispatch</span>} <span class="cm-operator">=</span> <span class="cm-variable-2">config</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">state</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">canvas</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">PictureCanvas</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>, <span class="cm-def">pos</span> <span class="cm-operator">=></span> {
<span class="cm-keyword">let</span> <span class="cm-def">tool</span> <span class="cm-operator">=</span> <span class="cm-variable-2">tools</span>[<span class="cm-keyword">this</span>.<span class="cm-property">state</span>.<span class="cm-property">tool</span>];
<span class="cm-keyword">let</span> <span class="cm-def">onMove</span> <span class="cm-operator">=</span> <span class="cm-variable-2">tool</span>(<span class="cm-variable-2">pos</span>, <span class="cm-keyword">this</span>.<span class="cm-property">state</span>, <span class="cm-variable-2">dispatch</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">onMove</span>) {
<span class="cm-keyword">return</span> <span class="cm-def">pos</span> <span class="cm-operator">=></span> <span class="cm-variable-2">onMove</span>(<span class="cm-variable-2">pos</span>, <span class="cm-keyword">this</span>.<span class="cm-property">state</span>, <span class="cm-variable-2">dispatch</span>);
}
});
<span class="cm-keyword">this</span>.<span class="cm-property">controls</span> <span class="cm-operator">=</span> <span class="cm-variable-2">controls</span>.<span class="cm-property">map</span>(
<span class="cm-def">Control</span> <span class="cm-operator">=></span> <span class="cm-keyword">new</span> <span class="cm-variable-2">Control</span>(<span class="cm-variable-2">state</span>, <span class="cm-variable-2">config</span>));
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"div"</span>, {}, <span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>.<span class="cm-property">dom</span>, <span class="cm-variable">elt</span>(<span class="cm-string">"br"</span>),
<span class="cm-meta">...</span><span class="cm-keyword">this</span>.<span class="cm-property">controls</span>.<span class="cm-property">reduce</span>(
(<span class="cm-def">a</span>, <span class="cm-def">c</span>) <span class="cm-operator">=></span> <span class="cm-variable-2">a</span>.<span class="cm-property">concat</span>(<span class="cm-string">" "</span>, <span class="cm-variable-2">c</span>.<span class="cm-property">dom</span>), []));
}
<span class="cm-property">syncState</span>(<span class="cm-def">state</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">state</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">canvas</span>.<span class="cm-property">syncState</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>);
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">ctrl</span> <span class="cm-keyword">of</span> <span class="cm-keyword">this</span>.<span class="cm-property">controls</span>) <span class="cm-variable-2">ctrl</span>.<span class="cm-property">syncState</span>(<span class="cm-variable-2">state</span>);
}
}
<span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"div"</span>)
.<span class="cm-property">appendChild</span>(<span class="cm-variable">startPixelEditor</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_vET994+4Pt" href="#p_vET994+4Pt" tabindex="-1" role="presentation"></a>خاصیت <code>key</code> متعلق به رخدادهای کلیدهای حروف همان حروف به شکل کوچک خواهند بود، اگر کلید <span class="keyname">shift</span> فشرده شده نباشد. در اینجا ما به رخدادهای کلیدهایی که <span class="keyname">shift</span> دارند توجهای نمیکنیم.</p>
<p>یک گردانندهی <code>"keydown"</code> می تواند شیء رخدادش را برای تطبیق با میانبرها بررسی کند. میتوانید لیست حروف اول را از شیء <code>tools</code> به دست بیاورید تا نیاز نباشد آنها را از ابتدا بنویسید.</p>
<p>زمانی که یک رخداد کلید با یک میانبر مطابقت دارد، <code>preventDefault</code> را روی آن فراخوانی کنید و اکشن مناسب با آن را گسیل دهید.</p>
</div></div>
<h3><a class="i_ident" id="i_a35s7yITYX" href="#i_a35s7yITYX" tabindex="-1" role="presentation"></a>ترسیم بهینه</h3>
<p><a class="p_ident" id="p_qNTHO2y5/J" href="#p_qNTHO2y5/J" tabindex="-1" role="presentation"></a>در طول ترسیم بیشترین کاری که برنامهی ما انجام می دهد توسط <code>drawPicture</code> صورت می گیرد. ایجاد یک وضعیت جدید و بهروز رسانی ما بقی قسمتهای DOM کار زیاد پرهزینهای محسوب نمی شود، اما بازنقاشی تمامی پیکسلها روی canvas نسبتا کار می برد.</p>
<p><a class="p_ident" id="p_vHZbU+6AuP" href="#p_vHZbU+6AuP" tabindex="-1" role="presentation"></a>راهی بیابید که متد <code>syncState</code> مربوط به PictureCanvas را بتوان با باز ترسیم تنها پیکسلهایی که واقعا تغییر نمودهاند سریع تر کرد.</p>
<p>به یاد داشته باشید که <code>drawPicture</code> توسط دکمهی ذخیرهسازی نیز استفاده شده است بنابراین اگر آن را تغییر دهید مطمئن شوید که تغییرات موجب از کار افتادن آن نمی شود. می توان نسخهی جدیدی از آن را با نامی دیگر ساخت.</p>
<p>همچنین توجه داشته باشید که تغییر اندازهی یک عنصر <code><canvas></code>، توسط تغییر خاصیتهای <code>width</code> و <code>height</code> آن، باعث می شود که از صفحه پاک شود و به طور کامل به صورت شفاف در بیاید.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_WYxUTPFclW" href="#c_WYxUTPFclW" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">div</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-comment">// Change this method</span>
<span class="cm-variable">PictureCanvas</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">picture</span>) {
<span class="cm-keyword">if</span> (<span class="cm-keyword">this</span>.<span class="cm-property">picture</span> <span class="cm-operator">==</span> <span class="cm-variable-2">picture</span>) <span class="cm-keyword">return</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">picture</span> <span class="cm-operator">=</span> <span class="cm-variable-2">picture</span>;
<span class="cm-variable">drawPicture</span>(<span class="cm-keyword">this</span>.<span class="cm-property">picture</span>, <span class="cm-keyword">this</span>.<span class="cm-property">dom</span>, <span class="cm-variable">scale</span>);
};
<span class="cm-comment">// You may want to use or change this as well</span>
<span class="cm-keyword">function</span> <span class="cm-def">drawPicture</span>(<span class="cm-def">picture</span>, <span class="cm-def">canvas</span>, <span class="cm-def">scale</span>) {
<span class="cm-variable-2">canvas</span>.<span class="cm-property">width</span> <span class="cm-operator">=</span> <span class="cm-variable-2">picture</span>.<span class="cm-property">width</span> <span class="cm-operator">*</span> <span class="cm-variable-2">scale</span>;
<span class="cm-variable-2">canvas</span>.<span class="cm-property">height</span> <span class="cm-operator">=</span> <span class="cm-variable-2">picture</span>.<span class="cm-property">height</span> <span class="cm-operator">*</span> <span class="cm-variable-2">scale</span>;
<span class="cm-keyword">let</span> <span class="cm-def">cx</span> <span class="cm-operator">=</span> <span class="cm-variable-2">canvas</span>.<span class="cm-property">getContext</span>(<span class="cm-string">"2d"</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">0</span>; <span class="cm-variable-2">y</span> <span class="cm-operator"><</span> <span class="cm-variable-2">picture</span>.<span class="cm-property">height</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-number">0</span>; <span class="cm-variable-2">x</span> <span class="cm-operator"><</span> <span class="cm-variable-2">picture</span>.<span class="cm-property">width</span>; <span class="cm-variable-2">x</span><span class="cm-operator">++</span>) {
<span class="cm-variable-2">cx</span>.<span class="cm-property">fillStyle</span> <span class="cm-operator">=</span> <span class="cm-variable-2">picture</span>.<span class="cm-property">pixel</span>(<span class="cm-variable-2">x</span>, <span class="cm-variable-2">y</span>);
<span class="cm-variable-2">cx</span>.<span class="cm-property">fillRect</span>(<span class="cm-variable-2">x</span> <span class="cm-operator">*</span> <span class="cm-variable-2">scale</span>, <span class="cm-variable-2">y</span> <span class="cm-operator">*</span> <span class="cm-variable-2">scale</span>, <span class="cm-variable-2">scale</span>, <span class="cm-variable-2">scale</span>);
}
}
}
<span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"div"</span>)
.<span class="cm-property">appendChild</span>(<span class="cm-variable">startPixelEditor</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_mnm+YR4CZ+" href="#p_mnm+YR4CZ+" tabindex="-1" role="presentation"></a>این تمرین مثال خوبی است که نشان می دهد چگونه ساختارهای دادهی غیرقابل تغییر می توانند باعث سریع تر شدن کد شوند. به دلیل اینکه ما به هر دوی تصاویر جدید و قدیم دسترسی داریم، می توانیم آنها را مقایسه کرده و فقط پیکسلهایی را بازترسیم کنیم که رنگشان تغییر یافته است و از 99 درصد از کار ترسیم اضافی بپرهیزیم.</p>
<p><a class="p_ident" id="p_1dTNB2FqVC" href="#p_1dTNB2FqVC" tabindex="-1" role="presentation"></a>شما همچنین می توانید تابع جدیدی <code>updatePicture</code> بنویسید یا به <code>drawPicture</code> آرگومان جدیدی اضافه کنید که میتواند undefined یا برابر تصویر قبل باشد. برای هر پیکسل، تابع بررسی میکند آیا تصویر دادهشدهی قبلی در این موقعیت رنگ مشابهی دارد که در این صورت از آن رد می شود.</p>
<p><a class="p_ident" id="p_Y6QhRlx+CS" href="#p_Y6QhRlx+CS" tabindex="-1" role="presentation"></a>با توجه به اینکه canvas با تغییر اندازه، پاک می شود، باید از دستکاری <code>width</code> و <code>height</code> آن در زمانی که تصویر قدیمی و جدید اندازهی مشابهی دارند، خودداری کنید. در صورت نبود اندازهی برابر، که در هنگام بارگیری تصویر جدید رخ می دهد، می توانید متغیرهایی که تصویر قبلی را نگهداری می کردند را پس از تغییر اندازهی canvas برابر null قرار دهید زیرا نباید هیچ پیکسلی پس از تغییر اندازهی canvas از قلم بیفتد.</p>
</div></div>
<h3><a class="i_ident" id="i_OA3lZSOtuW" href="#i_OA3lZSOtuW" tabindex="-1" role="presentation"></a>دوایر</h3>
<p>ابزاری به نام <code>circle</code> تعریف کنید که با کشیدن آن روی بوم بتوان دایرهی توپر رسم نمود. مرکز دایره در قسمتی قرار بگیرد که شروع رسم یا لمس قرار می گیرد و شعاع آن با حرکت موس معلوم می شود.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_MYYVHp5bsE" href="#c_MYYVHp5bsE" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">div</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">function</span> <span class="cm-def">circle</span>(<span class="cm-def">pos</span>, <span class="cm-def">state</span>, <span class="cm-def">dispatch</span>) {
<span class="cm-comment">// Your code here</span>
}
<span class="cm-keyword">let</span> <span class="cm-def">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">startPixelEditor</span>({
<span class="cm-property">tools</span>: <span class="cm-variable">Object</span>.<span class="cm-property">assign</span>({}, <span class="cm-variable">baseTools</span>, {<span class="cm-property">circle</span>})
});
<span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"div"</span>).<span class="cm-property">appendChild</span>(<span class="cm-variable">dom</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>rectangle</code> الهام گیری کنید. شبیه آن ابزار، در هنگام حرکت اشارهگر موس، می خواهید به ترسیم روی تصویر آغازین ادامه دهید نه تصویر فعلی.</p>
<p><a class="p_ident" id="p_c97Fk0rcGV" href="#p_c97Fk0rcGV" tabindex="-1" role="presentation"></a>برای دستیابی به پیکسلهایی که بایستی رنگ شوند، می توانید از قضیهی فیثاغورس استفاده کنید. ابتدا فاصلهی بین موقعیت اشارهگر فعلی و موقعیت آغازین را با محاسبهی ریشهی دوم (<code>Math.sqrt</code>) مجموع مربعهای (<code>Math.pow(x,2)</code>) تفاوت مختصات x و مربع تفاوت مختصات y، به دست بیاورید. سپس سراغ مربع پیکسلهای اطراف نقطهی آغازین بروید با این شرط که ضلعشان حداقل دوبرابر شعاع باشد، و آنهایی که در محدودهی شعاع قرار میگیرند را رنگ کنید، با استفاده از قضیهی فیثاغورس، فاصلهی آنها را از مرکز به دست میآید.</p>
<p>حواستان باشد که پیکسلهایی که بیرون از محدودهی تصویر هستند را رنگ نکنید.</p>
</div></div>
<h3><a class="i_ident" id="i_yNshKJasqJ" href="#i_yNshKJasqJ" tabindex="-1" role="presentation"></a>خطوط صحیح</h3>
<p>این تمرین از دو تمرین قبلی پیشرفته تر است و لازم است تا راه حلی برای مسئلهای نه چندان ساده طراحی کنید. مطمئن شوید که زمان و صبر کافی برای حل آن قبل از کار کردن روی تمرین، در اختیار دارید و اگر در ابتدای کار موفق به حل آن نشدید نا امید نشوید.</p>
<p>در بیشتر مرورگرها، زمانی که ابزار <code>draw</code> را انتخاب می کنید و سریع روی تصویر به کشیدن می پردازید، خط بسته تولید نمی شود. در عوض آنچه رخ میدهد کشیده شدن نقاطی مقطع است که دلیل آن این است که رخدادهای <code>"mousemove"</code> یا <code>"touchmove"</code> به سرعت و پی در پی ایجاد نمی شوند که همهی پیکسلها را پوشش دهند.</p>
<p>ابزار <code>draw</code> را بهبود دهید تا بتواند یک خط کامل را رسم نماید. این به این معنا است که شما باید تابع گردانندهی حرکت را تغییر دهید تا موقعیت قبلی را به خاطر داشته باشد و آن را به موقعیت فعلی متصل کند.</p>
<p>برای انجام این کار، به دلیل اینکه پیکسلها می توانند با فاصلهی دلخواهی واقع شوند، لازم است تا یک تابع عمومی برای ترسیم خط بنویسید.</p>
<p>یک خط بین دو نقطه توسط زنجیرهای از پیکسلهای متصل رسم می شود که تا جای ممکن مستقیم هستند و از نقطهی شروع به نقطهی پایان قرار می گیرند. پیکسلهایی که به صورت قطری مجاور هم هستند به عنوان پیکسلهای متصل محسوب می شوند. بنابراین یک خط مورب می تواند شبیبه به تصویر سمت چپ باشد نه تصویر سمت راست.</p><figure><img src="img/line-grid.svg" alt="Two pixelated lines, one light, skipping across pixels diagonally, and one heavy, with all pixels connected horizontally or vertically"></figure>
<p>درنتیجه، اگر کدی داشته باشیم که یک خط بین دو نقطهی دلخواه رسم کند، ممکن است همچنین ادامه دهیم و از آن برای تعریف یک ابزار <code>line</code> نیز بهره ببریم که برای ترسیم خط مستقیم بکار می رود.</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_lYipUdu4TJ" href="#c_lYipUdu4TJ" tabindex="-1" role="presentation"></a><span class="cm-tag cm-bracket"><</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">div</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-comment">// The old draw tool. Rewrite this.</span>
<span class="cm-keyword">function</span> <span class="cm-def">draw</span>(<span class="cm-def">pos</span>, <span class="cm-def">state</span>, <span class="cm-def">dispatch</span>) {
<span class="cm-keyword">function</span> <span class="cm-def">drawPixel</span>({<span class="cm-def">x</span>, <span class="cm-def">y</span>}, <span class="cm-def">state</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">drawn</span> <span class="cm-operator">=</span> {<span class="cm-property">x</span>, <span class="cm-property">y</span>, <span class="cm-property">color</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">color</span>};
<span class="cm-variable-2">dispatch</span>({<span class="cm-property">picture</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">picture</span>.<span class="cm-property">draw</span>([<span class="cm-variable-2">drawn</span>])});
}
<span class="cm-variable-2">drawPixel</span>(<span class="cm-variable-2">pos</span>, <span class="cm-variable-2">state</span>);
<span class="cm-keyword">return</span> <span class="cm-variable-2">drawPixel</span>;
}
<span class="cm-keyword">function</span> <span class="cm-def">line</span>(<span class="cm-def">pos</span>, <span class="cm-def">state</span>, <span class="cm-def">dispatch</span>) {
<span class="cm-comment">// Your code here</span>
}
<span class="cm-keyword">let</span> <span class="cm-def">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">startPixelEditor</span>({
<span class="cm-property">tools</span>: {<span class="cm-property">draw</span>, <span class="cm-property">line</span>, <span class="cm-property">fill</span>, <span class="cm-property">rectangle</span>, <span class="cm-property">pick</span>}
});
<span class="cm-variable">document</span>.<span class="cm-property">querySelector</span>(<span class="cm-string">"div"</span>).<span class="cm-property">appendChild</span>(<span class="cm-variable">dom</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_ULBiqgvFj9" href="#p_ULBiqgvFj9" tabindex="-1" role="presentation"></a>موضوع مهم دربارهی مشکل ترسیم یک خط پیکسلی این است که در واقع ما با چهار مسئله مشابه اما کمی متفاوت روبرو هستیم. رسم یک خط افقی از چپ به راست آسان است - مختصات x را پیمایش می کنید و در هر گام یک پیکسل را رنگ می کنید. اگر خط شیب کمی داشته باشد (کمتر از ۴۵ درجه یا ¼π رادیان )، می توانید مختصات y را در طول زاویه درونیابی کنید. هنوز به یک پیکسل برای هر موقعیت x نیاز دارید چراکه مختصات y آنها توسط شیب تعیین می شود.</p>
<p><a class="p_ident" id="p_thnnfs5ntI" href="#p_thnnfs5ntI" tabindex="-1" role="presentation"></a>اما به محض اینکه شییب شما بیشتر از 45 درجه شود، باید روش برخورد با مختصات را عوض کنید. اکنون به یک پیکسل برای هر موقعیت y نیاز دارید چراکه خط بیشتر به بالا حرکت میکند تا به چپ. و در نتیجه، وقتی از شیب 135 درجه عبور می کنید، باید به پیمایش مختصات x برگردید اما از راست به چپ.</p>
<p><a class="p_ident" id="p_HsCfDi9xUv" href="#p_HsCfDi9xUv" tabindex="-1" role="presentation"></a>در واقع نیازی نیست که چهار حلقه بنویسید. با توجه به اینکه رسم یک خط از نقطهی A به B همانند رسم خط از B به A میباشد، میتوانید موقعیتهای شروع و پایان را برای خطوطی که از راست به چپ میروند جابجا کنید و مانند چپ به راست آنها را در نظر بگیرید.</p>
<p>بنابراین به دو حلقهی متفاوت نیاز دارید. اولین چیزی که تابع رسم خط شما باید انجام دهد این است که بررسی کند که تفاوت مختصات x بزرگتر از تفاوت مختصات y باشد. اگر این طور بود، این خط، خطی افقی شکل است و اگر نبود، عموی شکل می باشد.</p>
<p>اطمینان حاصل کنید که قدر مطلق تفاوت مقادیر x و y را مقایسه می کنید که می توان این کار را با <bdo><code>Math.abs</code></bdo> انجام داد.</p>
<p>به محض اینکه بدانید حول کدام محور پیمایش خواهید داشت، می توانید بررسی کنید که نقطهی شروع دارای مختصات بالاتری در طول آن محور نسبت به نقطهی پایان باشد و در صورت نیاز آنها را جابجا کنید. یک روش مختصر برای جابجایی مقادیر دو متغیر در جاوااسکریپت استفاده از انتساب و تخریب به شکل زیر است:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_/XtfsVpMBL" href="#c_/XtfsVpMBL" tabindex="-1" role="presentation"></a>[<span class="cm-variable">start</span>, <span class="cm-variable">end</span>] <span class="cm-operator">=</span> [<span class="cm-variable">end</span>, <span class="cm-variable">start</span>];</pre>
<p>اکنون می توانید شیب خط را محاسبه نمایید، که خود مقداری که مختصات روی دیگر محور تغیر می کند را برای هر گامی که روی محور اصلی برمیدارید، تعیین می نماید. با استفاده از آن، می توانید از یک حلقه برای محور اصلی استفاده کنید و در حین آن موقعیت متناظر روی محور دیگر را نیز رصد کنید، و می توانید پیکسلها را در هر گام حلقه رسم کنید. مطمئن شوید که مختصات محور غیراصلی را رند نمایید چراکه احتمالا اعشاری باشند و متد <code>draw</code>به مختصات اعشاری پاسخ خوبی نمی دهد.</p>
</div></div><nav><a href="18_http.html" title="previous chapter">◀</a> <a href="index.html" title="cover">◆</a> <a href="20_node.html" title="next chapter">▶</a></nav>
</article>