En toen was er <canvas>
De laatste tijd lijkt het alleen nog maar te gaan over HTML 5. Apple is op ramkoers en maakt het Adobe de laatste tijd moeilijk. Tegelijkertijd zet Adobe de tegenaanval in door Flash 10.1 op Android toestellen uit te brengen. Microsoft heeft aangekondigd dat het alles zal ondersteunen van HTML 5 en CSS 3. Kortom: roerige tijden voor de web-ontwikkelaar.
Canvas?
Maar wat is er nou eigenlijk zo spannend aan HTML 5 waar Apple zo hoog op inzet? Persoonlijk vind ik het <canvas> element één van de spannendste dingen aan HTML 5. Je kunt het op een aantal vlakken vergelijken met Flash maar dan volledig open en je hebt er geen plugin voor nodig. Je gebruikt gewoon javascript en laadt niet (zoals bij Flash het geval is) een apart object in, dat vervolgens in je browser afspeelt.
In de praktijk:
Het canvas element is als het ware een “gereserveerd” gebied binnen een webpagina waarop je naar hartelust kan “tekenen”. Het is enigzins te vergelijken met SVG maar dan (vind ik persoonlijk) een stuk begrijpelijker en uitgebreider.
Hoe gaat dat in praktijk in zijn werk? Als je een beetje een fatsoenlijke browser gebruikt (Opera, Safari, Chrome of Firefox) kun je vrij snel aan de slag:
<canvas width="320" height="200" id="shapes"></canvas>Het canvas element kun je gewoon met CSS positioneren zoals je dat gewend bent van ieder “gewoon” element.
Context:
Door met de drawing API aan de slag te gaan kun je lijnen, veelhoeken, cirkels, teksten e.d. op de canvas tekenen. Eerste stap is daarbij de “context” aan te spreken:
var canvas = document.getElementById('shapes'); var context = canvas.getContext('2d');
De “context” is als het ware de vertaalslag naar de canvas (het stukje scherm dat je heb gereserveerd in je pagina) toe. Aan de context geef je de commando’s die deze vervolgens vertaald naar het scherm.
In het bovenstaande voorbeeld vraag ik de “2d” context op. In de toekomst wordt er ook een “3d” variant verwacht (het laat zich al raden hoe deze zich onderscheidt van de 2d versie).
Drawing API:
Hoe nu verder? Te beginnen met een eenvoudig voorbeeld:
context.fillStyle = "rgba(255, 0, 0, 1)"; context.fillRect(0, 0, 25, 25);
Zoals je ziet stel ik eerst de kleurstijl in (fillStyle), en vervolgens geef ik het commando om een “opgevuld” vierkant te tekenen van 25 bij 25 pixels op positie x 0 en y 0. Het resultaat: een rood vierkant.
Een voorbeeld van het tekenen van een dynamisch ingeladen afbeelding:
var image = new Image(); image.src = "afbeelding.png"; image.onload = function() { context.rotate(45 * Math.PI / 180); context.drawImage(image.imageData, 0, 0); };
In het bovenstaande voorbeeld laad ik eerst de afbeelding in. Zodra deze is ingeladen (onLoad) teken ik de afbeelding 45 graden gekanteld op de canvas.
Bij deze twee bovenstaande voorbeelden valt goed op hoe procedureel eigenlijk de teken-instructies zijn. Je geeft eerst een aantal instellingen op (vulkleur, lijnkleur, positie, rotatie etc.) en vervolgens geef je de teken instructie op (lijn, cirkel, vierkant etc.). Het is goed te vergelijken met turtle draw (http://en.wikipedia.org/wiki/Turtle_graphics).
De W3C heeft een goed en leesbaar (en dat is helaas weleens anders bij de W3C) document opgesteld waarin alle functionaliteiten van de drawing API goed worden belicht: http://dev.w3.org/html5/canvas-api/canvas-2d-api.html.
Animatie:
Het tekenen is dus niet bijzonder complex (wel erg verbose). Maar het wordt pas complexer als we animatie aan het geheel willen toevoegen.
Door de procedurele opzet gaat dat niet eenvoudig. Je moet zelf bij elke animatie de canvas “leegmaken” en vervolgens de nieuwe situatie tekenen. Een simpel voorbeeld:
var canvas = document.getElementById('shapes'); var context = canvas.getContext('2d'); var rotation = 0; setInterval(100, function() { context.clearRect(0, 0, 320, 200); context.rotate(rotation * Math.PI / 180); context.fillStyle = "rgba(255, 0, 0, 1)"; context.fillRect(0, 0, 25, 25); rotation++; if(rotation > 359) rotation = 0; });
In het bovenstaande voorbeeld laat ik een vierkant elke 100ms steeds een graad draaien. Met “clearRect” ruim ik alle getekende data in de canvas op. Met “rotate” geef ik aan, dat de daarop volgende teken-instructies geroteerd moet worden getekend. Als ik “clearRect” niet elke 100ms zou aanroepen, zou het vierkant 360 maal over elkaar heen worden getekend.
Hier is ook meteen een van de grote verschillen te zien met Flash. Waar Flash’s drawing API voornamelijk met parent-child containers werkt die allemaal hun eigen afbeelding vasthouden, moet het bij de canvas allemaal zelf geregeld worden. Het is een beetje terug in de tijd. Je kunt dus veel maar je moet het wel allemaal zelf (willen) doen.
canvas_library:
Om het leven van de webontwikkelaar een stukje eenvoudiger te maken ben ik momenteel bezig met canvas_library http://github.com/dkln/canvas_library Het doel is om een library te schrijven waarmee je eenvoudig graphics, animaties en interactiviteit kan gebruiken in de canvas zonder alles zelf te hoeven schrijven. Iedereen is uitgenodigd om mee te ontwikkelen! (het is tenslotte open source)
Toekomst van Flash:
Zal met de mogelijkheden van HTML 5 en canvas (wat het meest te vergelijken is met de functionaliteiten van Flash) gevolgen hebben voor Flash?
Flash zal ongetwijfeld aan marktaandeel moeten gaan inleveren. Ik denk niet dat Flash helemaal zal verdwijnen. Bedenk dat er nog steeds een grote groep designers/flash ontwikkelaars is die niet zo snel het veel technischere HTML 5 zal gaan gebruiken. Wel denk ik dat een technische groep mensen die nu snel naar Flash greep “omdat het niet anders kan” straks juist sneller naar HTML 5 zullen grijpen.
Daarnaast: Flash kent nog steeds een aantal unieke features (betere performance, brede video ondersteuning, volledig crossplatform) die HTML 5 (voorlopig) niet heeft.
Kortom: ik denk dat Flash niet “dood” zal gaan zoals zoveel mensen misschien hopen. Flash hoeft ook niet te verdwijnen maar het platform kan wel meer openheid en innovatie gebruiken naar mijn mening. Flash zal dus naast HTML blijven bestaan. Zij het voor andere doeleinden.
Tot slot:
Even weer terug naar Canvas: Bij een aantal rijst waarschijnlijk de vraag: waarom zou ik canvas überhaupt gebruiken? De stijlregel geldt eigenlijk ook voor Flash: canvas kun je inzetten om delen van je pagina net wat meer interactiviteit, design en sjeu te kunnen geven dat je niet kunt bereiken met HTML elementen en CSS regels.
Ookal is canvas in zijn basis eenvoudig, met een beetje creativiteit en inspanningsvermogen kun je er veel bereiken. Het wachten is nu nog op reus Microsoft om IE9 volledig HTML 5 compliant te maken zodat ook de grote groep gebruikers kan gaan genieten van het open web.
Canvas voorbeelden:

kleine note: die docs zijn opgesteld door de WhatWG, niet w3c
rikkert June 28, 2010 11:04
@rikkert thanks, goed opgemerkt
Diederick Lawson June 28, 2010 12:45
In de Mozilla Developer Center is ook een mooie tutorial te vinden, zie https://developer.mozilla.org/en/canvas_tutorial
Lennaert July 1, 2010 0:04
DL,
kan het zijn dat in plaats van:
context.drawImage(image.imageData, 0, 0);
het moet zijn:
context.drawImage(image, 0, 0);
Daarna draai schrijf je dat je het image draait, maar je draait het canvas. En om een onhandig punt. (0,0) Hoe draai je een plaatje om zijn midden. Zodat je een functie kune maken
zetPlaatje(image, x, y, angle)
Simon October 14, 2010 22:08
Hallo Simon,
Het klopt dat je inderdaad het canvas draait. Om iets in een geroteerde positie te tekenen, draai je als het ware je tekenvel en vervolgens teken je alsof het recht ligt.
Om te kunnen draaien vanuit het middelpunt van het plaatje om een willekeurig punt op het canvas zul je een paar transformaties moeten toepassen:
1. translatie zodat 0,0 van het coordinatenstelsel op het punt ligt waarom je heen wilt draaien
2. rotatie van het coordinatenstelsel in de richting waarin je wilt tekenen
3. translatie van het coordinatenstelsel zodat je niet om de linkerboven van de afbeelding, maar om het midden van de afbeelding draait.
Je functie zetPlaatje wordt dan:
function zetPlaatje(image, x, y, angle) {
context.save(); // huidige state opslaan op de stack
context.translate(x,y); // stap 1
context.rotate(angle * Math.PI / 180); // stap 2
context.translate(-image.width / 2, -image.height / 2); // stap 3
context.drawImage(image, 0, 0); // tekenen plaatje
context.restore(); // transformaties ongedaan maken
}
Met context.save en context.restore voorkom je dat de transformaties gevolgen hebben voor vervolgtekenopdrachten.
Lennaert November 2, 2010 23:50
Hey Simon, ik lees nu net pas je bericht (doordat ik een notificatie van Lennaert’s reactie kreeg)… oeps!
Wat Lennaert zegt klopt helemaal. Het save() en restore() proces geldt eigenlijk voor ALLE transformatie commando’s. Dus voor scale, translate, transform, rotate, strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor en globalCompositeOperation
Zie: https://developer.mozilla.org/en/Canvas_tutorial/Transformations
Zoals ik al eerder in het blogartikeltje schreef is canvas dus heel erg procedureel (turtledrawing)
DL November 3, 2010 0:19