forked from deianvasilev/diveintohtml5
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathcanvas.html
934 lines (711 loc) · 54.7 KB
/
canvas.html
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
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
<!DOCTYPE html>
<meta charset=utf-8>
<title>Canvas - Dive Into HTML5</title>
<!--[if lt IE 9]><script src=j/excanvas.min.js></script><![endif]-->
<link rel=alternate type=application/atom+xml href=https://github.com/diveintomark/diveintohtml5/commits/master.atom>
<link rel=stylesheet href=screen.css>
<style>
body{counter-reset:h1 4}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=prefetch href=index.html>
<p>Navigace: <a href=index.html>Domů</a> <span class=u>‣</span> <a href=table-of-contents.html#canvas>Dive Into <abbr>HTML5</abbr></a> <span class=u>‣</span>
<h1>Říkejme tomu (plocha na) kreslení</h1>
<p id=toc>
<p class=a>❧
<h2 id=divingin>Jdeme na to</h2>
<p class=f><img src=i/aoc-h.png alt=H width=107 height=105>TML 5 definuje <a href=http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html>element <canvas></a> jako „bitmapové kreslící plátno závislé na rozlišení, které může být za běhu využíváno pro vykreslování grafů, herní grafiky a ostatních vizuálních prvků“. <dfn>Canvas</dfn> je obdélník, umístěný na stránce, ve kterém můžete prostřednictvím JavaScriptu vykreslovat cokoliv se vám zlíbí.</p>
<table class=bc>
<caption>Základní podpora elementu <canvas></caption>
<thead>
<tr><th title="Internet Explorer">IE<th title="Mozilla Firefox">Firefox<th title="Apple Safari">Safari<th title="Google Chrome">Chrome<th>Opera<th>iPhone<th>Android
<tbody>
<tr><td>7.0+<sup>*</sup><td>3.0+<td>3.0+<td>3.0+<td>10.0+<td>1.0+<td>1.0+
<tfoot>
<tr>
<td colspan=7 style="text-align:left">* Internet Explorer 7 a 8 vyžadují <a href=http://code.google.com/p/explorercanvas/>explorercanvas</a> knihovny třetích stran. Internet Explorer 9 podporuje <code><canvas></code> nativně.
</table>
<p class=clear>Jak vlastně takový canvas vypadá? Nijak. Vážně. Element <code><canvas></code> nemá obsah a ani vlastní border.</p>
<canvas width=300 height=225 class=clear style="float:left"></canvas>
<p class="legend right" style="margin-top:4em"><span class=arrow> ↜</span> Neviditelný canvas
<p class=clear>Kód vypadá přibližně takhle:
<pre><code><canvas width="300" height="225"></canvas></code></pre>
<p>Pojďme přidat tečkovaný okraj, ať vidíme s čím se potýkáme.</p>
<canvas width=300 height=225 class=clear style="border:1px dotted;float:left"></canvas>
<p class="legend right" style="margin-top:4em"><span class=arrow> ↜</span> Canvas s okrajem
<p class=clear>Na jedné stránce můžete mít více elementů <code><canvas></code>. Každý canvas se zobrazí v <abbr>DOMu</abbr> a každý canvas si udržuje svůj vlastní stav. Přiřadíte-li elementu canvas atribut <code>id</code>, můžete k němu přistupovat stejně jako ke kterémukoliv jinému elementu.
<p>Pojďme kód rozšířit o atribut <code>id</code>:
<pre><code><canvas id="a" width="300" height="225"></canvas></code></pre>
<p>Odteď můžete v DOMu element <code><canvas></code> snadno najít<abbr></abbr>.
<pre><code>var a_canvas = document.getElementById("a");</code></pre>
<p class=a>❧
<h2 id=shapes>Základní tvary</h2>
<table class=bc>
<thead>
<tr><th title="Internet Explorer">IE<th title="Mozilla Firefox">Firefox<th title="Apple Safari">Safari<th title="Google Chrome">Chrome<th>Opera<th>iPhone<th>Android
<tbody>
<tr><td>7.0+<sup>*</sup><td>3.0+<td>3.0+<td>3.0+<td>10.0+<td>1.0+<td>1.0+
<tfoot>
<tr>
<td colspan=7 style="text-align:left">* Internet Explorer 7 a 8 vyžadují <a href=http://code.google.com/p/explorercanvas/>explorercanvas</a> knihovny třetích stran. Internet Explorer 9 podporuje <code><canvas></code> shapes nativně.
</table>
<p>Každé plátno je na začátku prázdné. A to je nuda! Pojďme něco nakreslit.</p>
<canvas id=b width=300 height=225 style="border:1px dotted;float:left" onclick="draw_b();return false"></canvas>
<p class="legend right" style="margin-top:4em"><span class=arrow> ⇜</span> <a href="#" onclick="draw_b();return false">Klikněte pro kreslení na toto „plátno“</a></p>
<p class=clear>Událost <code>onclick</code> spustí tuto funkci:
<pre><code>function draw_b() {
var b_canvas = document.getElementById("b");
var b_context = b_canvas.getContext("2d");
b_context.fillRect(50, 25, 150, 100);
}</code></pre>
<p>První řádek funkce není nic zvláštního, zkrátka jen najde element <code><canvas></code> v DOMu<abbr></abbr>.
<p class="legend left" style="margin-top:2em">A potom je tu tohle <span class=arrow>⇝</span> </p>
<pre><code>function draw_b() {
var b_canvas = document.getElementById("b");
<mark> var b_context = b_canvas.getContext("2d");</mark>
b_context.fillRect(50, 25, 150, 100);
}</code></pre>
<p class=ss><img src=i/openclipart.org_media_files_johnny_automatic_4145.png width=312 height=300 alt="muž kreslící před zrcadlem">
<p>Každé plátno má svoje <dfn>kreslicí prostředí</dfn> (drawing context), kde se odehrává všechna zábava. Jakmile v DOMu najdete element <code><canvas></code> (pomocí <code>document.getElementById()</code> nebo jiným způsobem), zavoláte jeho metodu <code>getContext()</code>. Metodě <code>getContext()</code> <strong>musíte</strong> předat řetězec <code>"2d"</code>.
<blockquote class=note>
<p><span>☞</span>Otázka: Existuje i 3D canvas?<br>
Odpověď: Zatím ne. Někteří výrobci prohlížečů experimetovali s vlastními <abbr>API</abbr> pro 3D canvas, ale žádné zatím nebylo standardizováno. Specifikace <abbr>HTML5</abbr> zmiňuje, že „budoucí verze této specifikace pravděpodobně budou definovat i 3D prostředí“.
</blockquote>
<p>Takže, máme element <code><canvas></code> a máme jeho kreslicí prostředí. V tomto prostředí jsou definovány všechny vlastnosti a metody kreslení. Kreslení obdélníků se věnuje celá skupina vlastností a metod:
<ul>
<li>Vlastnost <code>fillStyle</code> (styl výplně) může být CSS barva, vzorek nebo přechod. (Více o přechodech později.) Implicitně je <code>fillStyle</code> nastavena jako černá, ale můžete ji nastavit jakkoliv chcete. Každé kreslicí prostředí si pamatuje své vlastnosti, dokud stránku nezavřete nebo je sami nezresetujete.
<li><code>fillRect(x, y, šířka, výška)</code> nakreslí obdélník s aktuálním stylem výplně.
<li>Vlastnost <code>strokeStyle</code> (styl orámování) je podobná vlastnosti <code>fillStyle</code> — může být CSS barvou, vzorkem nebo přechodem.
<li><code>strokeRect(x, y, šířka, výška)</code> nakreslí obdélník s aktuálním stylem orámování. Vlastnost <code>strokeRect</code> nevyplňuje střed, nakreslí pouze okraje.
<li><code>clearRect(x, y, šířka, výška)</code> odstraní pixely v definovaném obdélníku.
</ul>
<div class="pf clear" id=reset>
<h4>Zeptejte se profesora Značky</h4>
<div class=inner>
<blockquote class=note>
<p><span>☞</span>Otázka: Můžu canvas „zresetovat“?<br>
Odpověď: Ano. Přenastavení rozměrů elementu <code><canvas></code> vymaže všechen jeho obsah a vynuluje všechny vlastnosti kreslicího prostředí na základní hodnoty. Rozměry nemusíte ani <em>změnit</em>, stačí je nastavit na jejich současné hodnoty, například takto:
<pre><code>var b_canvas = document.getElementById("b");
<mark>b_canvas.width = b_canvas.width;</mark></code></pre>
</blockquote>
</div>
</div>
<p>Pojďme zpět k ukázce kódu z předchozího příkladu…
<p class="legend left" style="margin-top:2em">Nakresli obdélník<span class=arrow>⇝ </span></p>
<pre><code>var b_canvas = document.getElementById("b");
var b_context = b_canvas.getContext("2d");
<mark>b_context.fillRect(50, 25, 150, 100);</mark></code></pre>
<p class=clear>Zavolání metody <code>fillRect()</code> nakreslí obdélník s nastaveným stylem výplně (tj. černou, dokud ji nenastavíte jinak). Obdélník je ohraničen horním levým rohem (souřadnice 50, 25), jeho šířkou (150) a jeho výškou (100). Pojďme si představit systém souřadnic, abychom lépe pochopili o co jde.
<p class=a>❧
<h2 id=coordinates>Souřadnice plátna</h2>
<p>Canvas je vlastně dvojrozměrná mřížka. Souřadnice (0, 0) jsou levý horní roh plátna. Čím je hodnota souřadnice X vyšší, tím více se po plátně posouváme směrem k pravému rohu. Na ose Y se zvýšením hodnoty přibližujeme ke spodnímu okraji plátna.
<p class="legend top" style="width:500px;text-align:center">Diagram souřadnic plátna <span class=arrow>↷</span><br></p>
<canvas id=c width=500 height=375></canvas>
<p>Tento diagram souřadnic byl nakreslen pomocí elementu <code><canvas></code>. Skládá se ze:
<ul>
<li>soustavy světle šedých vertikálních čar,
<li>soustavy světle šedých horizontálních čar,
<li>dvou černých horizotnálních čar,
<li>dvou malých diagonál, které vytváří šipku,
<li>dvou černých vertikálních čar,
<li>dvou malých diagonálních čar, které vytváří druhou šipku,
<li>písmene „x“,
<li>písmene „y“,
<li>textu „(0, 0)“ poblíž levého horního rohu,
<li>textu „(500, 375)“ u pravého spodního rohu,
<li>tečky v pravém horním rohu a druhé v levém spodním rohu.</ul>
<p>Nejprve potřebujeme nadefinovat samotný <code><canvas></code> element. Element <code><canvas></code> definuje <code>šířka</code> a <code>výška</code>, a <code>id</code> abychom jej mohli později nalézt.
<pre><code><canvas id="c" width="500" height="375"></canvas></code></pre>
<p>Potom potřebujeme skript, kterým najdeme element <code><canvas></code> v DOMu a dostaneme se k jeho kreslicímu prostředí.
<pre><code>var c_canvas = document.getElementById("c");
var context = c_canvas.getContext("2d");</code></pre>
<p>Nyní můžeme začít kreslit čáry.
<p class=a>❧
<h2 id=paths>Cesty</h2>
<table class=bc>
<thead>
<tr><th title="Internet Explorer">IE<th title="Mozilla Firefox">Firefox<th title="Apple Safari">Safari<th title="Google Chrome">Chrome<th>Opera<th>iPhone<th>Android
<tbody>
<tr><td>7.0+<sup>*</sup><td>3.0+<td>3.0+<td>3.0+<td>10.0+<td>1.0+<td>1.0+
<tfoot>
<tr>
<td colspan=7 style="text-align:left">* Internet Explorer 7 a 8 vyžadují <a href=http://code.google.com/p/explorercanvas/>explorercanvas</a> knihovny třetích stran. Internet Explorer 9 podporuje cesty v <code><canvas></code> nativně.</table>
<p style="float:left;margin:1.75em 1.75em 1.75em 0"><img src=i/openclipart.org_media_files_johnny_automatic_7563.png alt="pískomil sedící na židli s psacím brkem a inkoustem" width=167 height=347>
<p>Představte si, že kreslíte obrázek inkoustem. Určitě se do toho nepustíte hned po hlavě, protože můžete inkoustem udělat nevratné chyby. Místo toho načrtnete čáry a křivky tužkou a jakmile s nimi budete spokojeni, obtáhnete svoji skicu inkoustem.
<p>Každé plátno má cestu<dfn></dfn>. Definovat cestu je jako kreslit tužkou. Můžete kreslit co chcete, ale nebude to součástí finálního díla, dokud nevezmete pero a neobtáhnete cestu inkoustem.
<p>K nakreslení rovných čar tužkou můžete použít následující dvě metody:
<ol style="list-style-position:inside">
<li><code>moveTo(x, y)</code> přesune tužku do specifikovaného počátečního bodu.
<li><code>lineTo(x, y)</code> nakreslí čáru do specifikovaného bodu.
</ol>
<p>Čím více budete <code>moveTo()</code> a <code>lineTo()</code> volat, tím větší cesta bude. Toto jsou metody pro „práci s tužkou“ — můžete je volat, jak často chcete, ale na plátně neuvidíte nic, dokud nezavoláte některou z metod pro „práci s inkoustem“.
<p>Začněme s nakreslením šedé mřížky.
<pre style="float:left"><code>for (var x = 0.5; x < 500; x += 10) {
context.moveTo(x, 0);
<mark>context.lineTo(x, 375);</mark>
}</code></pre>
<p class="legend right" style="margin-top:4em"><span class=arrow> ⇜</span> Nakreslí vertikální čáry</p>
<pre style="clear:left;float:left"><code>for (var y = 0.5; y < 375; y += 10) {
context.moveTo(0, y);
<mark>context.lineTo(500, y);</mark>
}</code></pre>
<p class="legend right" style="margin-top:4em"><span class=arrow> ⇜</span> Nakreslí horizontální čáry</p>
<p class=clear>To jsou ještě stále metody „tužky“. Na plátně ještě není nic nakresleno. Potřebujeme metody pro „práci s inkoustem“ abychom čáru navždy obtáhli.
<pre><code>context.strokeStyle = "#eee";
<mark>context.stroke();</mark></code></pre>
<p><code>stroke()</code> je jedna z „inkoustových“ metod. Vezme komplexní cestu, kterou jste definovali se všemi těmi <code>moveTo()</code> a <code>lineTo()</code> voláními, a vykreslí ji na plátno. Barvu čar nastavuje <code>strokeStyle</code>. Toto je výsledek:</p>
<canvas id=c2 width=500 height=375></canvas>
<div class="pf clear" id=pixel-madness>
<h4>Zeptejte se profesora Značky</h4>
<div class=inner>
<blockquote class=note>
<p><span>☞</span>Otázka: Proč si začal souřadnice <var>x</var> i <var>y</var> v bodu <code>0,5</code>? Proč ne <code>0</code>?<br>
Odpověď: Představte si každý pixel jako velký čtverec. Celá čísla souřadnic (0, 1, 2…) jsou rohy těchto čtverců. Pokud mezi celočíselnými souřadnicemi nakreslíte čáru o tloušťce jedné jednotky, přesáhne pixel po stranách, a výsledná čára bude dva pixely široká. Abyste nakreslili pouze pixel širokou linku, musíte změnit souřadnice o 0,5 kolmo ke směru čáry.
<p>Pokud například zkusíte nakreslit čáru z <code>(1, 0)</code>do <code>(1, 3)</code>, prohlížeč vykreslí čáru pokrývající 0,5 obrazového pixelu na každé straně, kde se <code>x=1</code>. Obrazovka nemůže zobrazit polovinu pixelu, proto čáru rozšíří na celé dva pixely:
<p><img src=i/canvas-half-pixels-1.jpg alt="Čára z (1,0) do (1,3) je nakreslena 2 pixely široká" width=406 height=314>
<p>Pokud ale zkusíte nakreslit čáru z <code>(1.5, 0)</code> do <code>(1.5, 3)</code>, prohlížeč vykreslí čáru pokrývající 0,5 obrazového pixelu na každé straně, kde <code>x=1,5</code>, čímž vznikne právě jeden pixel široká čára:
<p><img src=i/canvas-half-pixels-2.jpg alt="Čára z (1.5,0) do (1.5,3) je nakreslena 1 pixel široká" width=404 height=323>
<p><em>Díky Jason Johnsonovi za poskytnutí těchto diagramů.</em></blockquote>
</div>
</div>
<p>Pojďme nakreslit horizontální šipku. Všechny čáry a křivky v cestě jsou vykresleny stejnou barvou (nebo přechodem — ano, už brzy se k nim dostaneme). Šipku chceme nakreslit jinou barvou — černou namísto šedé — takže musíme začít novou cestu.
<p class="legend top" style="margin-left:2em">Nová cesta <span class=arrow>↷</span><br></p>
<pre><code><mark>context.beginPath();</mark>
context.moveTo(0, 40);
context.lineTo(240, 40);
context.moveTo(260, 40);
context.lineTo(500, 40);
context.moveTo(495, 35);
context.lineTo(500, 40);
context.lineTo(495, 45);</code></pre>
<p>Vertikální šipka vypadá skoro stejně. Vzhledem k tomu, že má horizontální i vertikální šipka stejnou barvu, <strong>nemusíme</strong> vytvářet další cestu. Obě šipky budou součástí jedné cesty.
<pre style="float:left"><code>context.moveTo(60, 0);
context.lineTo(60, 153);
context.moveTo(60, 173);
context.lineTo(60, 375);
context.moveTo(65, 370);
context.lineTo(60, 375);
context.lineTo(55, 370);</code></pre>
<p class="legend right" style="margin-top:4em"><span class=arrow> ↜</span> Bez nové cesty</p>
<p class=clear>Říkal jsem, že tyto šipky budou černé, ale <code>strokeStyle</code> je stále nastaven na šedou (<code>fillStyle</code> a <code>strokeStyle</code> nejsou po vytvoření nové cesty vynulovány). To je v pořádku, protože jsme zatím použili jen metody „tužky“. Než obraz vykreslíme doopravdy, „inkoustem“, potřebujeme změnit <code>strokeStyle</code> na černou. V opačném případě se tyto šipky vykreslí šedou, takže je skoro neuvidíme! Následující řádky změní barvu na černou a vykreslí cesty na plátno:
<pre><code>context.strokeStyle = "#000";
context.stroke();</code></pre>
<p>Toto je výsledek:</p>
<canvas id=c3 width=500 height=375></canvas>
<p class=a>❧
<h2 id=text>Text</h2>
<table class=bc>
<thead>
<tr><th title="Internet Explorer">IE<th title="Mozilla Firefox">Firefox<th title="Apple Safari">Safari<th title="Google Chrome">Chrome<th>Opera<th>iPhone<th>Android
<tbody>
<tr><td>7.0+<sup>*</sup><td>3.0+<sup>†</sup><td>3.0+<td>3.0+<td>10.0+<td>1.0+<td>1.0+
<tfoot>
<tr>
<td colspan=7 style="text-align:left">* Internet Explorer 7 a 8 vyžadují <a href=http://code.google.com/p/explorercanvas/>explorercanvas</a> knihovny třetích stran. Internet Explorer 9 podporuje canvas text nativně.
<tr>
<td colspan=7 style="text-align:left">† Mozilla Firefox 3.0 pro kompatibilitu vyžaduje záplaty.
</table>
<p>Kromě <a href=#paths>kreslení čar</a> můžete na plátno vykreslovat i text. Narozdíl od textu na této stránce není k dispozici box model, nemůžeme tedy využívat známých CSS technik pro rozvržení stránky: žádný floats, margins, padding nebo word wrapping. (Možná si myslíte, že je to dobře!) Můžete nastavit pár atributů písma, poté zvolíte bod na plátně a text vykreslíte.
<p>V rámci <a href=#shapes>kreslicího prostředí</a> jsou k dispozici následující atributy fontu:
<ul>
<li><code>font</code> může být cokoliv, co byste vložili do pravidla <code>font</code> v <abbr>CSS:</abbr> font style, font variant, font weight, font size, line height a font family.
<li><code>textAlign</code> ovlivňuje zarovnání textu. Je velmi podobný (ale ne identický) <abbr>CSS</abbr> pravidlu <code>text-align</code>. Možné hodnoty jsou <code>start</code>, <code>end</code>, <code>left</code>, <code>right</code> a <code>center</code>.
<li><code>textBaseline</code> určuje kde je text vykreslen, relativně k startovnímu bodu. Možné hodnoty jsou <code>top</code>, <code>hanging</code>, <code>middle</code>, <code>alphabetic</code>, <code>ideographic</code> nebo <code>bottom</code>.
</ul>
<p><code>textBaseline</code> je ošidný, protože text je ošidný (na plátno můžete vykreslit kterýkoliv Unicode znak… a Unicode je ošidný). <abbr>HTML5</abbr> specifikace <a href=http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-textbaseline>vysvětluje rozdíly účaří</a>:
<blockquote cite=http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-textbaseline>
<p>Výška čtverčíku je přibližně shodná s výškou glyfů, the hanging baseline is where some glyphs like <span class=u>आ</span> are anchored, the middle is half-way between the top of the em square and the bottom of the em square, the alphabetic baseline is where characters like <span class=u>Á</span>, <span class=u>ÿ</span>, <span class=u>f</span>, and <span class=u>Ω</span> are anchored, the ideographic baseline is where glyphs like <span class=u>私</span> and <span class=u>達</span> are anchored, and the bottom of the em square is roughly at the bottom of the glyphs in a font. The top and bottom of the bounding box can be far from these baselines, due to glyphs extending far outside the em square.</p>
<p><img src=i/baselines.png alt="diagram of different values of the textBaseline property" width=680 height=227>
</blockquote>
<p>Pro jednoduché abecedy, jako je například anglická, můžete pro vlastnost <code>textBaseline</code> s klidem užívat hodnoty <code>top</code>, <code>middle</code> nebo <code>bottom</code>.
<p>Pojďme nakreslit nějaký text! Text vykreslený „na plátno“ dědí velikost a styl písma definovaný pro element <code><canvas></code> samotný, ale toto nastavení můžete snadno obejít, nastavíte-li vlastnost <code>font</code> v kreslicím prostředí.
<pre style="float:left"><code><mark>context.font = "bold 12px sans-serif";</mark>
context.fillText("x", 248, 43);
context.fillText("y", 58, 165);</code></pre>
<p class="legend right"><span class=arrow> ↜</span> Změna stylu písma</p>
<p class=clear>The <code>fillText()</code> method draws the actual text.</p>
<pre style="float:left"><code>context.font = "bold 12px sans-serif";
<mark>context.fillText("x", 248, 43);</mark>
context.fillText("y", 58, 165);</code></pre>
<p class="legend right" style="margin-top:2.6em"><span class=arrow> ⇜</span> Kreslení textu</p>
<div class="pf clear" id=relative-font-size>
<h4>Zeptejte se profesora Značky</h4>
<div class=inner>
<blockquote class=note>
<p><span>☞</span>Otázka: Můžu při kreslení na plátno používat relativní velikost fontu?<br>
Odpověď: Ano. Samotný element <code><canvas></code>, stejně jako kterýkoliv jiný HTML element na vaší stránce, vypočítal velikost písma na základě CSS pravidel na vaší stránce. Pokud nastavíte vlastnost <code>context.font</code> v relativních jednotkách, například <code>1.5em</code> nebo <code>150%</code>, prohlížeč ji vynásobí vypočítanou velikostí písma elementu <code><canvas></code> samotného.
</blockquote>
</div>
</div>
<p>Řekněme, že chci aby text v levém horním rohu začínal na <code>y=5</code>. Jenže já jsem líný — nechci měřit výšku textu a počítat velikost účaří. Místo toho můžu nastavit <code>textBaseline</code> na<code> top</code> a nastavit souřadnice levého horního rohu ohraničení textu.
<pre><code>context.textBaseline = "top";
context.fillText("( 0 , 0 )", <mark>8, 5</mark>);</code></pre>
<p>Pojďme na text v pravém spodním rohu. Řekněme, že chci, aby byl pravý horní roh textu na souřadnici <code>(492,370)</code> — jen pár pixelů od pravého spodního rohu plátna — ale nechci měřit výšku ani šířku textu. Můžu nastavit <code>textAlign</code> na <code>right</code> a <code>textBaseline</code> na <code>bottom</code> a potom zavolat <code>fillText()</code> se souřadnicemi pravého spodního rohu ohraničení textu.
<pre><code>context.textAlign = "right";
context.textBaseline = "bottom";
context.fillText("( 500 , 375 )", <mark>492, 370</mark>);</code></pre>
<p>A toto je výsledek:</p>
<canvas id=c4 width=500 height=375></canvas>
<p>Hopla! Zapomněli jsme na tečky v rozích. Podíváme se až později, jak je nakreslit. Pro teď budu trochu podvádět a <a href=#shapes>nakreslím je jako obdélníky</a>.
<pre style="float:left"><code>context.fillRect(0, 0, 3, 3);
context.fillRect(497, 372, 3, 3);</code></pre>
<p class="legend right"><span class=arrow> ⇜</span> Nakresli dvě „tečky“</p>
<p>A to je vše! Tady je finální produkt:</p>
<canvas id=c5 width=500 height=375 class=clear></canvas>
<p class=a>❧
<h2 id=gradients>Přechody</h2>
<table class=bc>
<thead>
<tr><th><th title="Internet Explorer">IE<th title="Mozilla Firefox">Firefox<th title="Apple Safari">Safari<th title="Google Chrome">Chrome<th>Opera<th>iPhone<th>Android
<tbody>
<tr>
<tr>
<th>lineární přechody<td>7.0+<sup>*</sup><td>3.0+<td>3.0+<td>3.0+<td>10.0+<td>1.0+<td>1.0+
<tr>
<th>radiální přechody<td>9.0+<td>3.0+<td>3.0+<td>3.0+<td>10.0+<td>1.0+<td>1.0+
<tfoot>
<tr>
<td colspan=8 style="text-align:left">* Internet Explorer 7 and 8 vyžadují <a href=http://code.google.com/p/explorercanvas/>explorercanvas</a> knihovnu třetích stran. Internet Explorer 9 podporuje přechody v elementu <code><canvas></code> nativně.
</table>
<p>Z předchozích odstavců této kapitoly jsme se naučili, jak vytvořit <a href=#shapes>obdélník s jednobarevnou výplní</a> a <a href=#paths>jednobarevnou čáru</a>. Jenže tvary ani čáry nemusí být pouze jednobarevné, můžete dělat spoustu kouzel s přechody. Podívejte se na příklad.</p>
<canvas id=d width=300 height=225></canvas>
<p>Zdrojový kód vypadá stejně, jako u kteréhokoliv jiného plátna.
<pre><code><canvas id="d" width="300" height="225"></canvas></code></pre>
<p>Nejprve musíme najít element <code><canvas></code> a jeho kreslicí prostředí.
<pre><code>var d_canvas = document.getElementById("d");
var context = d_canvas.getContext("2d");</code></pre>
<p>Jakmile máme k dispozici kreslicí prostředí, můžeme začít pracovat s přechody. Přechod chápejte jako plynulou změnu mezi dvěma nebo více barvami. Kreslicí prostředí podporuje dva typy přechodů:
<ol>
<li><code>createLinearGradient(x0, y0, x1, y1)</code> vykreslený podél úsečky z bodu (x0, y0) do bodu (x1, y1).
<li><code>createRadialGradient(x0, y0, r0, x1, y1, r1)</code> vykreslený podél kuželu mezi dvěma kruhy. První tři parametry reprezentují začátek kruhu se středem v bodu (x0, y0) a poloměrem r0. Poslední tři parametry reprezentují konec kruhu se středem v bodu (x1, y1) a průměrem r1.
</ol>
<p>Pojďme vyzkoušet lineární přechod. Přechody mohou být jakkoliv velké; já ho udělám 300 pixelů široký, stejně jako je plátno.
<p class="legend top" style="margin-left:2.5em">Vytvoř objekt přechodu <span class=arrow>↷</span><br></p>
<pre><code>var my_gradient = <mark>context.createLinearGradient(0, 0, 300, 0);</mark></code></pre>
<p>Protože jsou hodnoty <code>y</code> (druhý a čtvrtý parametr) obě nulové, rozprostře se přechod rovnoměrně zleva do prava.
<p>Jakmile máme pro přechod připravený objekt, můžeme definovat jeho barvy. Přechod má dva nebo více barevných milníků. K přidání milníku stačí zadat jeho pozici kdekoliv podél přechodu mezi 0 a 1.
<p>Pojďme vytvořit přechod z černé do bílé.
<pre><code>my_gradient.addColorStop(0, "black");
my_gradient.addColorStop(1, "white");</code></pre>
<p>Definicí přechodu na plátno zatím nic nenakreslíme. Jde zatím jen o objekt zastrčený někde v paměti. Abychom přechod nakreslili, nastavíme <code>fillStyle</code> na přechod a nakreslíme tvar, stejně jako u obdélníků nebo čar.
<p class="legend top">Výplní bude přechod <span class=arrow>↷</span><br></p>
<pre><code><mark>context.fillStyle = my_gradient;</mark>
context.fillRect(0, 0, 300, 225);</code></pre>
<p>A toho je výsledek:</p>
<canvas id=d2 width=300 height=225></canvas>
<p>Řekněme, že budete potřebovat přechod, který půjde shora dolů. Jakmile pro přechod vytvoříte objekt, ponechte hodnoty pro osu <code>x</code> (první a třetí parametr) konstantní, a nastavte hodnoty osy <code>y</code> (druhý a čtvrtý parametr) v rozmezí 0 až výška plátna.
<p class="legend top" style="margin-left:6.5em">Hodnoty x jsou stejné, y se liší<span class=arrow>↷</span><br></p>
<pre><code>var my_gradient = context.createLinearGradient(<mark>0, 0, 0, 225</mark>);
my_gradient.addColorStop(0, "black");
my_gradient.addColorStop(1, "white");
context.fillStyle = my_gradient;
context.fillRect(0, 0, 300, 225);</code></pre>
<p>A toto je výsledek:</p>
<canvas id=d3 width=300 height=225></canvas>
<p>Můžete klidně vytvořit i diagonální přechod.
<p class="legend top" style="margin-left:8.5em">Hodnoty x i y jsou různé <span class=arrow>↷</span><br></p>
<pre><code>var my_gradient = context.createLinearGradient(<mark>0, 0, 300, 225</mark>);
my_gradient.addColorStop(0, "black");
my_gradient.addColorStop(1, "white");
context.fillStyle = my_gradient;
context.fillRect(0, 0, 300, 225);</code></pre>
<p>A toto je výsledek:</p>
<canvas id=d4 width=300 height=225></canvas>
<p class=a>❧
<h2 id=images>Obrázky</h2>
<table class=bc>
<thead>
<tr><th title="Internet Explorer">IE<th title="Mozilla Firefox">Firefox<th title="Apple Safari">Safari<th title="Google Chrome">Chrome<th>Opera<th>iPhone<th>Android
<tbody>
<tr><td>7.0+<sup>*</sup><td>3.0+<td>3.0+<td>3.0+<td>10.0+<td>1.0+<td>1.0+
<tfoot>
<tr>
<td colspan=7 style="text-align:left">* Internet Explorer 7 a 8 vyžadují <a href=http://code.google.com/p/explorercanvas/>explorercanvas</a> knihovny třetích stran. Internet Explorer 9 podporuje obrázky v elementu <code><canvas></code> nativně.
</table>
<p>Tady máme kočku:
<p style="float:left"><img src=i/openclipart.org_media_files_johnny_automatic_1360.png alt="sleeping cat" width=177 height=113 id=cat>
<p class="legend right" style="margin-top:4em"><span class=arrow> ⇜</span> Element <img></p>
<p class=clear>A tady máme stejnou kočku, ale nakreslenou na plátně:
<div style="float:right">
<p class="legend left" style="margin-top:2em">Element <canvas> <span class=arrow>⇝ </span></p>
<canvas id=e width=177 height=113></canvas>
</div>
<p class=clear>Pro kreslení obrázků na plátno máme v kreslicím prostředí metodu <code>drawImage()</code>. Této metodě můžete předat tři, pět nebo devět argumentů.
<ul>
<li><code>drawImage(image, dx, dy)</code> vezme obrázek a vykreslí ho na plátno. Levý horní roh obrázku bude na souřadnicích <code>(dx, dy)</code>. Pokud tedy budete chtít vykreslit obrázek v levém horním rohu plátna, jednoduše zadáte souřadníce <code>(0, 0)</code>.
<li><code>drawImage(image, dx, dy, dw, dh)</code> vezme obrázek a vykreslí ho ze souřadnic <code>(dx, dy)</code> v takovém měřítku, aby odpovídal šířce <code>dw</code> a výšce <code>dh</code>.
<li><code>drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)</code> vezme obrázek, vyřízne z něj oblast <code>(sx, sy, sw, sh)</code>, změní jeho rozměry na <code>(dw, dh)</code> a vykreslí ho na plátno ze souřadnic <code>(dx, dy)</code>.
</ul>
<p>Specifikace <abbr>HTML5</abbr> <a href=http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#images>vysvětluje parametry <code>drawImage()</code></a>:
<blockquote cite=http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#images>
<p>Zdrojová oblast je obdélník (uvnitř zdrojového obrázku) jehož rohy jsou čtyři body <code>(sx, sy)</code>, <code>(sx+sw, sy)</code>, <code>(sx+sw, sy+sh)</code>, <code>(sx, sy+sh)</code>.
<p>Cílová oblast je obdélník (uvnitř plátna) jehož rohy jsou čtyři body <code>(dx, dy)</code>, <code>(dx+dw, dy)</code>, <code>(dx+dw, dy+dh)</code>, <code>(dx, dy+dh)</code>.
<p><img src=i/drawImage.png alt="diagram of drawImage parameters" width=327 height=330>
</blockquote>
<p>Abychom mohli vykreslit obrázek na plátno, potřebujeme nejprve obrázek samotný. Obrázkem může být existující<code> <img></code> element nebo můžete JavaScriptem vytvořit objekt <code>Image()</code>. V obou případech si musíte být jistí, že se obrázek správně nahrál, než se jej pokusíte vykreslit na plátno.
<p>Využijete-li existujícího <code><img></code> elementu, můžete jej na plátno v klidu vykreslit v průběhu události <code>window.onload</code>.
<p class="legend top" style="margin-left:6.5em"><span class=arrow>↶</span> využití elementu <img><br></p>
<pre><code><img <mark>id="cat"</mark> src="images/cat.png" alt="spící kočka" width="177" height="113">
<canvas id="e" width="177" height="113"></canvas>
<script>
<mark>window.onload</mark> = function() {
var canvas = document.getElementById("e");
var context = canvas.getContext("2d");
var cat = document.getElementById("cat");
<mark>context.drawImage(cat, 0, 0);</mark>
};
</script></code></pre>
<p>Pokud vytváříte obrázek výhradně JavaScriptem, můžete jej na plátno bezpečně vykreslit v průběhu události <code>Image.onload</code>.
<p class="legend top" style="margin-left:3.5em">využití objektu Image() <span class=arrow>↷</span><br></p>
<pre><code><canvas id="e" width="177" height="113"></canvas>
<script>
var canvas = document.getElementById("e");
var context = canvas.getContext("2d");
var cat = <mark>new Image()</mark>;
cat.src = "images/cat.png";
<mark>cat.onload</mark> = function() {
context.drawImage(cat, 0, 0);
};
</script></code></pre>
<p>Třetí a čtvrtý parametr metody <code>drawImage()</code> jsou nepovinné a měníte jimi rozměry obrázku. Toto je stejný obrázek, pouze s poloviční šířkou a výškou, opakovaně vykreslený na různých souřadnicích jednoho plátna.</p>
<canvas id=multicat width=500 height=375></canvas>
<p>Tady je skript tvořící efekt „multikočky“:
<pre style="float:left"><code>cat.onload = function() {
for (var x = 0, y = 0;
x < 500 && y < 375;
x += 50, y += 37) {
context.drawImage(cat, x, y, <mark>88, 56</mark>);
}
};
</code></pre>
<p class="legend right" style="margin-top:6em"><span class=arrow> ⇜</span> změna rozměrů obrázku</p>
<p class=clear>Tato snaha vyvolává legitimní otázku: proč byste měli chtít vykreslovat obrázek na plátno? V čem je extra komplexní nastavení obrázků na plátně lepší než element <code><img></code> a pár <abbr>CSS</abbr> pravidel? I efekt „multikočky“ je nahraditelný deseti překrývajícími se <code><img></code> elementy.
<p>Jednoduchá odpověď je, že z nějakého důvodu možná budete chtít <a href=#text>vykreslit text na plátno</a>. Například <a href=#coordinates>diagram souřadnic plátna</a> obsahuje text, čáry a tvary; text na plátně byla pouze součást. Složitější diagram může snadno využít <code>drawImage()</code> například k vykreslení ikon nebo jiných grafických prvků.
<p class=a>❧
<h2 id=ie>A co Internet Explorer?</h2>
<p> Internet Explorer nepodporuje canvas <abbr>API</abbr> do verze 9.0. (IE9 <a href=http://msdn.microsoft.com/en-us/ie/ff468705.aspx#_HTML5_canvas>plně podporuje canvas <abbr>API</abbr></a>.) Nicméně tyto starší verze Internet Exploreru <em>podporují</em> proprietární technologii zvanou <abbr>VML</abbr>, která dokáže skoro totéž, co <code><canvas></code> element. A proto se zrodil <code>excanvas.js</code>.
<p><a href=http://code.google.com/p/explorercanvas/>Explorercanvas</a> (<code>excanvas.js</code>) je JavaScriptová open source knihovna, licencovaná pro Apache, která implementuje canvas <abbr>API</abbr> do Internet Exploreru. Abyste ji mohli využít, stačí vložit následující skript do hlavičky vaší stránky.
<pre><code><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dive Into HTML5</title>
<mark><!--[if lt IE 9]>
<script src="excanvas.js"></script>
<![endif]--></mark>
</head>
<body>
...
</body>
</html>
</code></pre>
<p><a href="http://msdn.microsoft.com/en-us/library/ms537512(VS.85).aspx">Podmíněné komentáře</a> <code><!--[if lt IE 9]></code> a <code><![endif]--></code> Internet Explorer interpretuje jako: „pokud je vaším prohlížečem Internet Explorer ve verzi nižší než 9.0, pak proveď tento kód“. Ostatní prohlížeče tuto pasáž berou jako obyčejný <abbr>HTML</abbr> komentář. Ve výsledku si Internet Explorer 7 a 8 stáhne a spustí <code>excanvas.js</code>, zatímco všechny ostatní prohlížeče budou skript ignorovat (nebudou ho stahovat ani spouštět, zkrátka nic). Stránky se tak v prohlížečích s nativní podporou canvas <abbr>API</abbr> načtou bez sebemenšího zpomalení.
<p>Jakmile zahrnete <code>excanvas.js</code> v elementu <code><head></code> vaší stránky, nemusíte si už s Internet Explorerem dělat starost. Zkrátka v klidu zahrňte element <code><canvas></code> do vašeho kódu. Dodržte instrukce pro práci s kreslicím prostředím elementu <code><canvas></code>, popsané výše v této kapitole, a můžete kreslit tvary, texty a vzorky dle libosti.
<p>No dobře… ne tak úplně. Je tam pár omezení:
<ol>
<li><a href=#gradients>Přechody</a> mohou být pouze lineární. <a href=https://developer.mozilla.org/En/Canvas_tutorial/Applying_styles_and_colors#A_createRadialGradient_example>Kruhové přechody</a> nejsou podporovány.
<li>Vzorek se musí opakovat v obou směrech.
<li><a href=http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#clipping-region>Oblasti ořezu</a> nejsou podporovány.
<li>Nejednotné <a href=http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-scale>škálování</a> mění nesprávně rozměry okrajů.
<li>Je to pomalé. To asi není velké překvapení vzhledem k tomu, že JavaScriptový parser Internet Exploreru zkrátka je pomalejší, než tomu je u ostatních prohlížečů. Začnete-li kreslit složitější tvary skrz JavaScriptovou knihovnu, která překládá příkazy naprosto rozdílné technologii, nedivte se „kde to vázne“. Když nakreslíte pár čar nebo transformujete obrázek, nezaznamenáte žádný zásadní pokles výkonu. Určitě si ale všimnete, jakmile zkusíte pracovat s animacemi nebo dalšími skopičinami, které element <code><canvas></code> nabízí.
</ol>
<p>Při používání <code>excanvas.js</code> je ještě jeden zádrhel, na který jsem narazil při vytváření příkladů v této kapitole. ExplorerCanvas automaticky inicializuje prostředí vlastního rádoby-plátna kdykoliv vložíte <code>excanvas.js</code> skript do vaší <abbr>HTML</abbr> stránky. Jenže to neznamená, že ho Internet Explorer zvládne okamžitě používat. V některých případech můžete narazit na situaci, kdy je rádoby-plátno <em>skoro</em>, ale ještě ne úplně, připravené. Hlavním příznakem tohoto stavu je stížnost „<samp>objekt nepodporuje tuto vlastnost nebo metodu</samp>“, kterou Internet Explorer vrátí kdykoliv zkusíte jakkoliv pracovats elementem <code><canvas></code>, potažmo kreslicím prostředím.
<p>Nejjednodušší řešení je odložit všechny akce související s plátnem, dokud „nezahouká“událost <code>onload</code>. Možná to chvilku potrvá — pokud mají vaše stránky hodně obrázků nebo videa, jejich zpracování oddálí událost <code>onload</code> — ale to dá ExplorerCanvasu potřebný čas na jeho kouzla.
<p class=a>❧
<h2 id=halma>Kompletní, živý příklad</h2>
<p>Halma je stovky let stará desková hra a vznikla spousta jejích variací. V tomto příkladu jsem vytvořil solitérní verzi Halmy s devíti figurkami na hracím plánu s 9 × 9 poli. Na začátku hry figurky vytváři čtverec 3 × 3 v levém spodním rohu hrací desky. Cílem hry je přesunout všechny figurky v co nejméně tazích tak, aby zformovaly čtverec 3 × 3 v pravém horním rohu.
<p>Ve hře Halma jsou dva typy povolených tahů:
<ul>
<li>Přesunete figurku do kteréhokoliv volného přilehlého pole. „Volným“ polem rozumíme kterékoliv pole, na kterém není jiná figurka. „Přilehlá“ pole je sousední pole na sever, jih, východ, západ, severovýchod, severozápad, jihovýchod nebo jihozápad hned vedle pole, ve kterém se figurka právě nachází. (Hrací plán nepokračuje za své okraje. Pokud je figurka v poli na levém okraji plánu, nemůže být přesunuta na západ, severozápad nebo jihozápad. Pokud je figurka v poli u spodního okraje, nemůže být přesunuta na jih, jihovýchod nebo jihozápad.)
<li>Přeskočíte figurku na přilehlém poli a to i opakovaně. To znamená, že když přeskočíte přes figurku na přilehlém poli a hned přeskočíte <em>další</em> figurku na poli sousedícím k vaší nové pozici, jde pouze o jeden tah. Ve skutečnosti může být považován za jeden tah jakýkoliv počet těchto přeskočení. (Vzhledem k tomu, že je cílem minimalizovat celkový počet tahů, snažíme se rozmístit figurky tak, abychom ostatními figurkami mohli provést co nejdelší sekvence skoků v co nejméně tazích).</ul>
<p>Tak takhle se Halma hraje. Můžete si ji <a href=examples/canvas-halma.html>zahrát na samostatné stránce</a> pokud byste se v ní chtěli šťourat se svými vývojářskými udělátky.</p>
<canvas id=halmacanvas></canvas>
<p style="margin-top:0;font-style:normal" class="legend">Počet tahů: <span id=halmamovecount>0</span>
<p>Jak to funguje? Jsem rád, že se ptáte. Nebudu tu ukazovat <em>celý</em> kód. (Můžete se na něj podívat na <a href=examples/halma.js>diveintohtml5.org/examples/halma.js</a>.) Vlastně přeskočím většinu hracího kódu, ale rád bych vypíchnul pár částí kódu, které obsluhují kliknutí myší v oblasti plátna a samotné „kreslení na plátno“.
<p>Zatímco se stránka načítá, inicializujeme hru nastavením rozměru elementu <code><canvas></code> a uchováme referenci k jeho kreslicímu prostředí.
<pre><code>gCanvasElement.width = kPixelWidth;
gCanvasElement.height = kPixelHeight;
gDrawingContext = gCanvasElement.getContext("2d");</code></pre>
<p>Pak uděláme něco, co jste dosud neviděli: navěsíme na element <code><canvas></code> odchytávání událostí, abychom mohli zpracovávat kliknutí myší.
<pre><code>gCanvasElement.<mark>addEventListener</mark>(<mark>"click"</mark>, halmaOnClick, false);</code></pre>
<p>Funkce <code>halmaOnClick()</code> se zavolá kdykoliv uživatel klikne někde uvnitř plátna. Jejím argumentem je objekt <code>MouseEvent</code>, který obsahuje informaci kam uživatel kliknul.
<pre><code>function halmaOnClick(e) {
var cell = <mark>getCursorPosition(e)</mark>;
// the rest of this is just gameplay logic
for (var i = 0; i < gNumPieces; i++) {
if ((gPieces[i].row == cell.row) &&
(gPieces[i].column == cell.column)) {
clickOnPiece(i);
return;
}
}
clickOnEmptyCell(cell);
}</code></pre>
<p>V dalším kroku díky objektu <code>MouseEvent</code> zjistíme, na které pole hrací desky uživatel právě kliknul. Hrací deska Halmy zabere plochu celého plátna, takže každé kliknutí směřuje na <em>některé</em> z polí. Jen musíme zjistit na které. To je ošidné, protože každý prohlížeč zpracovává události myši rozdílně.
<pre><code>function getCursorPosition(e) {
var x;
var y;
if (e.pageX != undefined && e.pageY != undefined) {
x = e.pageX;
y = e.pageY;
}
else {
x = e.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop +
document.documentElement.scrollTop;
}</code></pre>
<p>V tuto chvíli máme souřadnice <code>x</code> a <code>y</code>, relativní vůči dokumentu (což je celá <abbr>HTML</abbr> stránka). To je nám zatím celkem k ničemu, protože potřebujeme souřadnice relativní vůči plátnu.
<pre><code> x -= gCanvasElement.offsetLeft;
y -= gCanvasElement.offsetTop;</code></pre>
<p>Nyní máme souřadnice <code>x</code> a <code>y</code>, které jsou <a href=#coordinates>relativní vůči plátnu</a>. To znamená, že pokud se <code>x</code> i <code>y</code> rovná nule, víme, že uživatel kliknul na pixel nejbližší levému hornímu rohu plátna.
<p>Díky tomu jsme schopni spočítat na které pole uživatel kliknul a podle toho se zachovat.
<pre><code> var cell = new Cell(Math.floor(y/kPieceHeight),
Math.floor(x/kPieceWidth));
return cell;
}</code></pre>
<p>Páni! Události myši nejsou snadné. Ve svých aplikacích, založených na elementu <code><canvas></code>, můžete využít stejnou logiku (vlastně dokonce i přímo tento kód). Pamatujte: kliknutí → souřadnice relativní k dokumentu → souřadnice relativní k elementu <code><canvas></code> → specifický kód aplikace.
<p>Fajn, pojďme se podívat na základní kreslicí rutiny. Protože je grafika hry velmi jednoduchá, rozhodl jsem se vymazat a překreslit celou hrací desku kdykoliv ve hře dojde k nějaké změně. Toto není nezbytně nutné. Kreslicí prostředí plátna zachová vše, co jste na ně předtím nakreslili. Dokonce i když uživatel odroluje plátno mimo viewport prohlížeče nebo klikne-li na jiný tab a později se vrátí zpět. Pokud pomocí elementu <code><canvas></code> vytváříte aplikaci se složitější grafikou (jako například hry), můžete optimalizovat výkon sledováním oblastí, které jsou na plátně „špinavé“ a jen ty překreslovat. Ale to už je trochu mimo záběr této knihy.
<pre><code>gDrawingContext.clearRect(0, 0, kPixelWidth, kPixelHeight);</code></pre>
<p>Postup při kreslení hrací desky vypadá povědomě. Ostatně, je podobný kreslení <a href=#coordinates>diagramu souřadnic plátna</a> popsaném dříve v této kapitole.
<pre><code>gDrawingContext.<mark>beginPath()</mark>;
/* vertical lines */
for (var x = 0; x <= kPixelWidth; x += kPieceWidth) {
gDrawingContext.<mark>moveTo</mark>(0.5 + x, 0);
gDrawingContext.<mark>lineTo</mark>(0.5 + x, kPixelHeight);
}
/* horizontal lines */
for (var y = 0; y <= kPixelHeight; y += kPieceHeight) {
gDrawingContext.<mark>moveTo</mark>(0, 0.5 + y);
gDrawingContext.<mark>lineTo</mark>(kPixelWidth, 0.5 + y);
}
/* draw it! */
gDrawingContext.<mark>strokeStyle</mark> = "#ccc";
gDrawingContext.<mark>stroke()</mark>;</code></pre>
<p>Skutečná zábava začíná, jakmile začneme kreslit každou z figurek. Figurka je kruh, tedy něco, co jsme zatím nekreslili. Mimoto, pokud uživatel zvolí figurku s úmyslem přesunu na jiné pole, chceme ji vybarvit.
<p>Argument <code>p</code> reprezentuje figurku, která má vlasnost <code>row</code> (řada) a <code>column</code> (sloupec), které popisují současnou polohu figurky na hrací desce. Využijeme herních konstant k překladu <code>(column, row)</code> na souřadnice <code>(x, y)</code> relativní k plátnu, poté nakreslíme kruh, který (je-li figurka vybraná) vyplníme barvou.
<pre><code>function drawPiece(p, selected) {
var column = p.column;
var row = p.row;
var <mark>x</mark> = (column * kPieceWidth) + (kPieceWidth/2);
var <mark>y</mark> = (row * kPieceHeight) + (kPieceHeight/2);
var radius = (kPieceWidth/2) - (kPieceWidth/10);</code></pre>
<p>To je všechno z logiky specifické pro tuto hru. Nyní máme souřadníce <code>(x, y)</code>, relativní vůči plátnu, pro střed kruhu, který chceme kreslit. V canvas <abbr>API</abbr> metoda <code>circle()</code> není, ale můžeme využít metodu <code>arc()</code> (česky „oblouk“). Vždyť přeci, co jiného je kruh, než oblouk kolem dokola? Vzpomínáte na základy geometrie? Metoda <code>arc()</code> potřebuje středový bod <code>(x, y)</code>, poloměr, počáteční a konečný úhel (v radiánech) a příznak směru (<code>false</code> znamená „po směru hodinových ručiček“, <code>true</code> naopak). Pro výpočet radiánů můžeme využít JavaScriptového modulu <code>Math</code>.
<pre><code>gDrawingContext.beginPath();
gDrawingContext.<mark>arc</mark>(x, y, radius, 0, <mark>Math.PI * 2</mark>, false);
gDrawingContext.closePath();</code></pre>
<p>Počkat! Zatím se nic nevykreslilo. Stejně jako metody <code>moveTo()</code> a <code>lineTo</code>, i <code>arc()</code> nejprve <a href=#paths>„kreslí tužkou“</a>. Abychom nakreslili kruh, potřebujeme ještě nastavit <code>strokeStyle</code> a zavolat <code>stroke()</code>, který skicu kruhu „obtáhne inkoustem“.
<pre><code>gDrawingContext.<mark>strokeStyle</mark> = "#000";
gDrawingContext.<mark>stroke()</mark>;</code></pre>
<p>A co když je figurka vybraná? Můžeme použít stejnou cestu, kterou jsme už vytvořili pro obrys figurky, a tu vyplnit barvou.
<pre><code>if (selected) {
gDrawingContext.<mark>fillStyle</mark> = "#000";
gDrawingContext.<mark>fill()</mark>;
}</code></pre>
<p>A to je… no, skoro všechno. Zbytek programu je logika specifická pro tuto hru — rozhodování mezi platnými a nepovolenými tahy, sledování počtu tahů, detekce ukončení hry. S devití kruhy, pár čarami a jednou <code>onclick</code> událostí jsme vytvořili celou hru v elementu <code><canvas></code>. Huzzah!
<p class=a>❧
<h2 id=further-reading>Další čtení</h2>
<ul>
<li><a href=https://developer.mozilla.org/en/Canvas_tutorial>Canvas tutorial</a> na Mozilla Developer Center
<li><a href=http://dev.opera.com/articles/view/html-5-canvas-the-basics/><abbr>HTML5</abbr> <code>canvas</code> — the basics</a> od Mihai Sucan
<li><a href=http://www.canvasdemos.com/>CanvasDemos.com</a>: dema, nástroje a tutoriály k <abbr>HTML</abbr> elementu <code>canvas</code>
<li><a href=http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html>Specifikace pro element <code>canvas</code></a> v pracovním návrhu standardu <abbr>HTML5</abbr>
<li><a href=http://msdn.microsoft.com/en-us/ie/ff468705.aspx#_HTML5_canvas>Internet Explorer 9 Guide for Developers: HTML5 <code>canvas</code> element</a>
</ul>
<p class=a>❧
<p>Dočetli jste kapitolu “Říkejme tomu (plocha na) kreslení.” Pokud chcete pokračovat, přejděte na <a href=table-of-contents.html>celý obsah knihy</a>.
<div class=pf>
<h4>Víte že?</h4>
<div class=moneybags>
<blockquote><p>O’Reilly spolu s Google Press nabízí anglickou verzi této knihy v řadě formátů včetně tištěné pdoby, ePub, Mobi, and <abbr>DRM</abbr>-free <abbr>PDF</abbr>. Placená verze se nazývá „HTML5: Up & Running,“ a můžete ji mít ihned.
<p>Pokud se vám tahle kapitola líbila a chcete autora anglického originálu podpořit, <a href="http://www.amazon.com/HTML5-Up-Running-Mark-Pilgrim/dp/0596806027?ie=UTF8&tag=diveintomark-20&creativeASIN=0596806027">kupte si „HTML5: Up & Running“ skrze tento affiliate odkaz</a> nebo <a href=http://oreilly.com/catalog/9780596806033>elektronickou verzi přímo od O’Reilly</a>. Vy dostanete knihu, já peníze. V současnosti nepřijímám přímé dary.
</blockquote>
</div>
</div>
<p class=c>Copyright MMIX–MMXI <a href=about.html>Mark Pilgrim</a>, Czech translation <a href="http://sigy.cz/">Jiří Sekera</a>
<form action=http://www.google.com/cse><div><input type=hidden name=cx value=013556084273090989037:f1s-wnbz68q><input type=hidden name=ie value=UTF-8><input type=search name=q size=25 placeholder="powered by Google™"> <input type=submit name=sa value=Hledat></div></form>
<script src=j/jquery.js></script>
<script src=j/canvastext-fx3.js></script>
<script src=j/dih5.js></script>
<script src=examples/halma.js></script>
<script>
function draw_b() {
try {
var b_canvas = document.getElementById("b");
var b_context = b_canvas.getContext("2d");
b_context.fillRect(50, 25, 150, 100);
} catch(err) {}
}
function reset_b() {
try {
var b_canvas = document.getElementById("b");
b_canvas.width = b_canvas.width;
} catch(err) {}
}
function draw_grid(ctx) {
try {
/* vertical lines */
for (var x = 0.5; x < 500; x += 10) {
ctx.moveTo(x, 0);
ctx.lineTo(x, 375);
}
/* horizontal lines */
for (var y = 0.5; y < 375; y += 10) {
ctx.moveTo(0, y);
ctx.lineTo(500, y);
}
/* draw it! */
ctx.strokeStyle = "#eee";
ctx.stroke();
} catch(err) {}
}
function draw_arrows(ctx) {
try {
/* x-axis */
ctx.beginPath();
ctx.moveTo(0, 40);
ctx.lineTo(240, 40);
ctx.moveTo(260, 40);
ctx.lineTo(500, 40);
ctx.moveTo(495, 35);
ctx.lineTo(500, 40);
ctx.lineTo(495, 45);
/* y-axis */
ctx.moveTo(60, 0);
ctx.lineTo(60, 153);
ctx.moveTo(60, 173);
ctx.lineTo(60, 375);
ctx.moveTo(65, 370);
ctx.lineTo(60, 375);
ctx.lineTo(55, 370);
/* draw it! */
ctx.strokeStyle = "#000";
ctx.stroke();
} catch(err) {}
}
function draw_labels(ctx) {
try {
ctx.font = "bold 12px sans-serif";
ctx.fillText("x", 248, 43);
ctx.fillText("y", 58, 165);
} catch(err) {}
try {
ctx.textBaseline = "top";
ctx.fillText("( 0 , 0 )", 8, 5);
} catch(err) {}
try {
ctx.textAlign = "right";
ctx.textBaseline = "bottom";
ctx.fillText("( 500 , 375 )", 492, 370);
} catch(err) {}
}
function draw_dots(ctx) {
try {
ctx.fillRect(0, 0, 3, 3);
ctx.fillRect(497, 372, 3, 3);
} catch(err) {}
}
function draw_gradients() {
try {
var d = document.getElementById("d");
var context = d.getContext("2d");
var my_gradient = context.createLinearGradient(0, 0, 300, 0);
my_gradient.addColorStop(0, "black");
my_gradient.addColorStop(1, "white");
context.fillStyle = my_gradient;
context.fillRect(0, 0, 300, 225);
} catch(err) {}
try {
var d2 = document.getElementById("d2");
var context = d2.getContext("2d");
var my_gradient = context.createLinearGradient(0, 0, 300, 0);
my_gradient.addColorStop(0, "black");
my_gradient.addColorStop(1, "white");
context.fillStyle = my_gradient;
context.fillRect(0, 0, 300, 225);
} catch(err) {}
try {
var d3 = document.getElementById("d3");
var context = d3.getContext("2d");
var my_gradient = context.createLinearGradient(0, 0, 0, 225);
my_gradient.addColorStop(0, "black");
my_gradient.addColorStop(1, "white");
context.fillStyle = my_gradient;
context.fillRect(0, 0, 300, 225);
} catch(err) {}
try {
var d4 = document.getElementById("d4");
var context = d4.getContext("2d");
var my_gradient = context.createLinearGradient(0, 0, 300, 225);
my_gradient.addColorStop(0, "black");
my_gradient.addColorStop(1, "white");
context.fillStyle = my_gradient;
context.fillRect(0, 0, 300, 225);
} catch(err) {}
}
function draw_images(imagesReady) {
var cat_canvas = document.getElementById("e");
var cat_context = cat_canvas.getContext("2d");
var cat_canvas2 = document.getElementById("multicat");
var cat_context2 = cat_canvas2.getContext("2d");
var cat_image = document.getElementById("cat");
var _draw = function() {
cat_context.drawImage(cat_image, 0, 0);
for (var x = 0, y = 0; x < 500 && y < 375; x += 50, y += 37) {
cat_context2.drawImage(cat_image, x, y, 88, 56);
}
};
if (!!imagesReady) {
_draw();
} else {
window.onload = _draw;
}
}
function draw(imagesReady) {
var c = document.getElementById("c");
var ctx = c.getContext("2d");
draw_grid(ctx);
draw_arrows(ctx);
draw_labels(ctx);
draw_dots(ctx);
var c2 = document.getElementById("c2");
ctx = c2.getContext("2d");
draw_grid(ctx);
var c3 = document.getElementById("c3");
ctx = c3.getContext("2d");
draw_grid(ctx);
draw_arrows(ctx);
var c4 = document.getElementById("c4");
ctx = c4.getContext("2d");
draw_grid(ctx);
draw_arrows(ctx);
draw_labels(ctx);
var c5 = document.getElementById("c5");
var ctx = c5.getContext("2d");
draw_grid(ctx);
draw_arrows(ctx);
draw_labels(ctx);
draw_dots(ctx);
draw_gradients();
draw_images(imagesReady);
initGame(document.getElementById("halmacanvas"), document.getElementById("halmamovecount"));
}
$(function() {
if (!(!/*@cc_on!@*/0)) {
window.attachEvent('onload', draw);
} else {
draw(false);
}
});
</script>
<script type="text/javascript"><!--
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-25728182-1']);
_gaq.push(['_setDomainName', '.html5.cz']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
// --></script>