Het artikel bespreekt de technische afweging tussen het bouwen van webapps met standaard HTML (DOM) versus het gebruik van het Canvas-element. Terwijl de meeste applicaties baat hebben bij de native browserfunctionaliteiten van de DOM, zoals toegankelijkheid en tekstverwerking, is Canvas essentieel voor complexe, prestatiegevoelige tools zoals Google Sheets of Miro.
De belangrijkste voordelen van Canvas zijn:
- Snelheid: Minder overhead omdat de browser geen complexe elementbomen hoeft te parsen.
- Controle: Volledige grip op rendering bij functies als zoomen en pannen.
- Consistentie: Dezelfde visuele weergave op alle apparaten.
Implementatie-adviezen: De auteur adviseert het gebruik van een centrale renderer, het inzetten van meerdere canvas-lagen voor interactieve elementen (layering), en het correct beheren van de devicePixelRatio voor scherpe beelden. Daarnaast is het cruciaal om eigen systemen te bouwen voor coördinatenvertaling en hit-testing via bounding boxes.
Conclusie: Canvas is geen algemene vervanging voor HTML, maar de juiste keuze zodra een interface meer functioneert als een visuele 'scène' dan als een traditioneel document.
Waarom je je webapp in Canvas zou willen bouwen in plaats van HTML
Er zijn natuurlijk veel antwoorden op deze vraag, maar één aspect dat ik bijzonder interessant vind, is hun gebruik van het Canvas-element voor functionaliteiten die gewoonlijk in HTML worden geïmplementeerd.
Het document in Google Docs is een Canvas. Dat geldt ook voor de spreadsheet in Google Sheets of de webversie van Excel. Niet verrassend is dat Canva een Canvas is, maar dat geldt ook voor het bord in Miro. Onze eigen planninginterface in Hivekit is eveneens een Canvas.
De scheduler van Hivekit kan worden ingezoomd (wat de weergegeven tijdsspanne verandert), kan in de x- en y-richting worden gepand en heeft veel interactieve aspecten die beheerd moesten worden. Ik heb geholpen bij de bouw ervan, en in dit artikel wil ik uitleggen waarom we kozen voor Canvas boven DOM-elementen, wat we onderweg hebben geleerd, wanneer Canvas volgens mij een goede (of slechte) keuze is voor webapps, en waarom grote bedrijven Canvas gebruiken voor prestatiegevoelige applicaties.
Wat was Canvas eigenlijk?
Canvas bestaat inmiddels al meer dan 20 jaar. Het biedt een lege ruimte binnen een HTML-document waarop getekend kan worden. Hiervoor gebruik je een JavaScript API met methoden op hoog niveau, zoals fillRect() om een rechthoek te vullen, en methoden op lager niveau, zoals getImageData() om toegang te krijgen tot de ruwe RGBA-waarden van pixels.
Welke aanpak je ook kiest, het resultaat is in feite een statische afbeelding. Voor webontwikkelaars voelt dat wat vreemd. Zij zijn immers gewend aan een complex Document Object Model (DOM), HTML die wordt geparsed in elementbomen, click-handlers en event bubbling, dynamische rendering en reflows — zaken die allemaal voor je worden beheerd en perfect zijn afgestemd op het apparaat van de gebruiker.
Bij Canvas is dit alles verdwenen.
Waarom zou je Canvas gebruiken?
Er zijn sommige dingen die alleen met Canvas kunnen, zoals pixelmanipulatie van afbeeldingen. Maar waarom zou je kiezen voor Canvas om een webapp te bouwen die ook in HTML gemaakt had kunnen worden? Hier zijn enkele redenen:
- Snelheid: Het parsen van HTML, het creëren van een DOM, het toepassen van CSS-stijlen en het afhandelen van de talloze functies rondom gebruikersinteractie kost tijd. Als je webapp complex wordt, moet de browser zwaar tillen. Een "domme" teken-API betekent minder werk, en minder werk betekent meer snelheid.
- Controle: Als je een whiteboard-app bouwt met een oneindige werkruimte, een raster met talloze rijen, of een planningstool met een zoombare interface, moet je de rendering sowieso zelf in handen nemen. Je kunt dit (op een bepaalde manier) in HTML doen — bijvoorbeeld via "virtual scrolling", waarbij je de inhoud van rasterrijen vervangt in plaats van de native scrolling van de browser te gebruiken, of door slim elementen toe te voegen aan en te verwijderen uit de DOM tijdens het zoomen en pannen. Maar op dat punt kun je beter de volledige rendering zelf beheren.
- Consistentie: Met Canvas lever je op alle apparaten exact wat je specificeert. Vroeger was dit vaker een probleem omdat browser-implementaties verschilden, maar zelfs nu kunnen responsive designs, CSS-gradiënten en transitie-effecten er per besturingssysteem of scherm anders uitzien. Met Canvas krijg je hetzelfde resultaat, voor beter of slechter.
- Portabiliteit: Canvas wordt gebruikt om output van andere visuele frameworks te renderen. Flutter Web en bepaalde WebAssembly-implementaties sturen hun screen buffers naar canvas. Dit werkt ook andersom: tools zoals Ejecta en NativeScript wrappen C++ teken-API's in Canvas-aanroepen, waardoor je graphics op andere systemen kunt weergeven.
Waarom zou je Canvas niet gebruiken?
Er zijn veel meer redenen om Canvas niet te gebruiken dan om het wel te doen. Voor de meeste webapps ben je veel beter af met goede oude DOM-elementen. Neem bijvoorbeeld het eenvoudige <input type="text"> element. Hiermee krijg je scherpe rendering op elke resolutie, ondersteuning voor de tab-toets, focus, selectie, muisinteracties en navigatie via pijltjestoetsen, internationalisering voor tekst van rechts naar links en Aziatische samengestelde tekens, toegankelijkheid voor schermlezers... de lijst is lang.
Browsers bieden veel functionaliteit direct uit de doos, en er zijn talloze geweldige frameworks die het makkelijk maken om op een georganiseerde en gestandaardiseerde manier te werken binnen teams.
Wanneer is Canvas de betere keuze?
Er is een specifieke set use-cases waarbij Canvas de betere keuze kan zijn:
- Veel absoluut gepositioneerde elementen: Wanneer je werkt met onregelmatige vormen of complexe render-volgorde/z-index vereisten. Of je nu een vision board app bouwt of een 2D platformer; als je app buiten de gebruikelijke HTML-layout flow valt, kan Canvas het leven makkelijker maken.
- Specifieke renderingbehoeften: Als je app kan zoomen, pannen, gebruikmaakt van camera transforms, clipping, tiling, level-of-detail rendering of virtualisatie, dan maakt Canvas het eenvoudig om ervoor te zorgen dat je alleen rendert wat echt nodig is.
- Aanwezigheid van een sterk intern model: Als je applicatie al een sterk concept heeft van state, geometrie, focus en interactie, en je alleen een manier nodig hebt om dit te visualiseren, dan is Canvas makkelijker dan HTML.
Belangrijke aandachtspunten bij implementatie
Als je besluit voor een Canvas-implementatie te kiezen, zijn dit de belangrijkste zaken om op te letten:
Beheer wanneer er gerenderd wordt
Het patroon dat voor ons het beste werkte, is één centrale renderer die andere klassen aanroept die specifieke aspecten renderen (bijv. backgroundRenderer, rowRenderer, taskRenderer). Elke klasse kan scheduleRender aanroepen op de renderer. Dit plant een enkele render-pass voor het volgende animatieframe. Let op dat we het volledige Canvas bij elk frame wissen en alles vanaf nul opnieuw renderen. Hoewel dit verspillend is, heeft het bij ons nooit tot problemen geleid; complexere implementaties zouden alleen specifieke regio's kunnen herrenderen.
class Renderer {
constructor(canvas) {
this.renderScheduled = false;
this.canvas = canvas;
this.context = canvas.getContext('2d');
this.backgroundRenderer = new BackgroundRenderer(this);
this.rowRenderer = new RowRenderer(this);
}
scheduleRender() {
if (this.renderScheduled) return;
this.renderScheduled = true;
requestAnimationFrame(this.render.bind(this));
}
render() {
this.renderScheduled = false;
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Render de individuele lagen in volgorde
this.backgroundRenderer.render(this.context);
this.rowRenderer.render(this.context);
}
}
Gebruik meerdere Canvas-elementen in lagen (Layering)
Voor onze planninginterface blijft het basisplan vrij statisch terwijl de gebruiker met de muis interacteert, maar zijn er veel highlight- en hover-effecten. Hiervoor gebruiken we een tweede Canvas-element met dezelfde afmetingen als het origineel. Deze InteractionRenderer ververst veel frequenter dan het eigenlijke Canvas, maar rendert slechts enkele bounding frames, wat veel lichter is.
Houd stijlen gescheiden
Het scheiden van CSS en HTML maakt het leven makkelijker; doe hetzelfde met Canvas. Gebruik een apart bestand voor stijlen, bijvoorbeeld:
gapDiagonalLineSpacing: 10,
textColor: '#ecf0f5',
textColorSecondary: '#4d6585',
fontStyle: "13px 'Lato', sans-serif",
fontStyleBold: "bold 13px 'Lato', sans-serif"
Beheer apparaatresolutie en pixeldichtheid
Om scherpe resultaten te krijgen, moet je ervoor zorgen dat je Canvas-element is geschaald naar de pixel ratio van het apparaat. Zet vervolgens (tegenintuïtief) de scale van de context om de scale van het element te compenseren. Zo krijg je een scherp resultaat zonder dat je code constant rekening hoeft te houden met pixel-densiteit.
getPixelScale() {
return Math.max(window.devicePixelRatio, 1);
}
scaleCanvas(canvas, ctx) {
const pixelScale = this.getPixelScale();
canvas.width = canvas.offsetWidth * pixelScale;
canvas.height = canvas.offsetHeight * pixelScale;
ctx.scale(pixelScale, pixelScale);
}
Gebruik centrale functies voor coördinatenvertaling
Als je een app met een oneindige werkruimte bouwt, hebben elementen waarschijnlijk X- en Y-coördinaten. Bij een spreadsheet heb je rij- en kolomindices. Deze verschillen van de werkelijke pixelcoördinaten op basis van resolutie, zoom, pan-positie en andere factoren. Het maakt het leven veel makkelijker om eenvoudige functies te hebben zoals getXForColumn(colIndex) of getPositionForDomainCoordinates(x,y).
Onderhoud een simpel box-model
Wanneer een gebruiker over elementen hovert of klikt, wil je snel weten waarmee ze interacteren. Bouw hiervoor vooraf een index van bounding boxes in de schermruimte op. Sla voor elke box de x1, x2, y1 en y2 coördinaten op, samen met de z-index en een identifier. Bij zeer grote hoeveelheden bounding boxes kun je indices op basis van x/y coördinaten maken voor snellere lookups, of zelfs een R-Tree overwegen voor ruimtelijke indexering.
Beheer de levenscyclus van Event Handlers
Gebruik één globale listener voor muis- en toetsenbordevenementen en een eenvoudige manier om callbacks te registreren en te deregistreren voor specifieke gebeurtenissen en elementen.
Conclusie: moet je Canvas gebruiken?
Canvas is geen snellere vervanging voor HTML. Het is een renderingtool op lager niveau die je meer controle geeft, maar je daarmee ook verantwoordelijk maakt voor veel werk dat de browser normaal gesproken voor je doet.
Voor de meeste webapps blijft de DOM de betere keuze. Het biedt gratis toegankelijkheid, responsive layouts, tekstselectie, input-afhandeling en talloze andere functies. Maar als de kern van je applicatie een grote, ruimtelijke werkruimte is met complexe positionering, zoomen, pannen of duizenden visuele elementen, dan kan Canvas beter passen.
Dat was onze ervaring. Het in eigen hand nemen van de rendering pipeline maakte de planninginterface begrijpelijker en zorgde voor voorspelbare prestaties op uiteenlopende apparaten. Het betekende echter ook dat we onze eigen systemen moesten bouwen voor interactie, hit-testing, schaling en rendering — werk dat niet onderschat mag worden.
Kies dus niet voor Canvas simpelweg omdat het "snel" klinkt. Kies ervoor wanneer je interface niet langer aanvoelt als een document, maar meer begint te functioneren als een scène.
Waarom je je webapp in Canvas zou willen bouwen in plaats van HTML
Er zijn natuurlijk veel antwoorden op deze vraag, maar één aspect dat ik bijzonder interessant vind, is hun gebruik van het Canvas-element voor functionaliteiten die gewoonlijk in HTML worden geïmplementeerd.
Het document in Google Docs is een Canvas. Dat geldt ook voor de spreadsheet in Google Sheets of de webversie van Excel. Niet verrassend is dat Canva een Canvas is, maar dat geldt ook voor het bord in Miro. Onze eigen planninginterface in Hivekit is eveneens een Canvas.
De scheduler van Hivekit kan worden ingezoomd (wat de weergegeven tijdsspanne verandert), kan in de x- en y-richting worden gepand en heeft veel interactieve aspecten die beheerd moesten worden. Ik heb geholpen bij de bouw ervan, en in dit artikel wil ik uitleggen waarom we kozen voor Canvas boven DOM-elementen, wat we onderweg hebben geleerd, wanneer Canvas volgens mij een goede (of slechte) keuze is voor webapps, en waarom grote bedrijven Canvas gebruiken voor prestatiegevoelige applicaties.
Wat was Canvas eigenlijk?
Canvas bestaat inmiddels al meer dan 20 jaar. Het biedt een lege ruimte binnen een HTML-document waarop getekend kan worden. Hiervoor gebruik je een JavaScript API met methoden op hoog niveau, zoals fillRect() om een rechthoek te vullen, en methoden op lager niveau, zoals getImageData() om toegang te krijgen tot de ruwe RGBA-waarden van pixels.
Welke aanpak je ook kiest, het resultaat is in feite een statische afbeelding. Voor webontwikkelaars voelt dat wat vreemd. Zij zijn immers gewend aan een complex Document Object Model (DOM), HTML die wordt geparsed in elementbomen, click-handlers en event bubbling, dynamische rendering en reflows — zaken die allemaal voor je worden beheerd en perfect zijn afgestemd op het apparaat van de gebruiker.
Bij Canvas is dit alles verdwenen.
Waarom zou je Canvas gebruiken?
Er zijn sommige dingen die alleen met Canvas kunnen, zoals pixelmanipulatie van afbeeldingen. Maar waarom zou je kiezen voor Canvas om een webapp te bouwen die ook in HTML gemaakt had kunnen worden? Hier zijn enkele redenen:
- Snelheid: Het parsen van HTML, het creëren van een DOM, het toepassen van CSS-stijlen en het afhandelen van de talloze functies rondom gebruikersinteractie kost tijd. Als je webapp complex wordt, moet de browser zwaar tillen. Een "domme" teken-API betekent minder werk, en minder werk betekent meer snelheid.
- Controle: Als je een whiteboard-app bouwt met een oneindige werkruimte, een raster met talloze rijen, of een planningstool met een zoombare interface, moet je de rendering sowieso zelf in handen nemen. Je kunt dit (op een bepaalde manier) in HTML doen — bijvoorbeeld via "virtual scrolling", waarbij je de inhoud van rasterrijen vervangt in plaats van de native scrolling van de browser te gebruiken, of door slim elementen toe te voegen aan en te verwijderen uit de DOM tijdens het zoomen en pannen. Maar op dat punt kun je beter de volledige rendering zelf beheren.
- Consistentie: Met Canvas lever je op alle apparaten exact wat je specificeert. Vroeger was dit vaker een probleem omdat browser-implementaties verschilden, maar zelfs nu kunnen responsive designs, CSS-gradiënten en transitie-effecten er per besturingssysteem of scherm anders uitzien. Met Canvas krijg je hetzelfde resultaat, voor beter of slechter.
- Portabiliteit: Canvas wordt gebruikt om output van andere visuele frameworks te renderen. Flutter Web en bepaalde WebAssembly-implementaties sturen hun screen buffers naar canvas. Dit werkt ook andersom: tools zoals Ejecta en NativeScript wrappen C++ teken-API's in Canvas-aanroepen, waardoor je graphics op andere systemen kunt weergeven.
Waarom zou je Canvas niet gebruiken?
Er zijn veel meer redenen om Canvas niet te gebruiken dan om het wel te doen. Voor de meeste webapps ben je veel beter af met goede oude DOM-elementen. Neem bijvoorbeeld het eenvoudige <input type="text"> element. Hiermee krijg je scherpe rendering op elke resolutie, ondersteuning voor de tab-toets, focus, selectie, muisinteracties en navigatie via pijltjestoetsen, internationalisering voor tekst van rechts naar links en Aziatische samengestelde tekens, toegankelijkheid voor schermlezers... de lijst is lang.
Browsers bieden veel functionaliteit direct uit de doos, en er zijn talloze geweldige frameworks die het makkelijk maken om op een georganiseerde en gestandaardiseerde manier te werken binnen teams.
Wanneer is Canvas de betere keuze?
Er is een specifieke set use-cases waarbij Canvas de betere keuze kan zijn:
- Veel absoluut gepositioneerde elementen: Wanneer je werkt met onregelmatige vormen of complexe render-volgorde/z-index vereisten. Of je nu een vision board app bouwt of een 2D platformer; als je app buiten de gebruikelijke HTML-layout flow valt, kan Canvas het leven makkelijker maken.
- Specifieke renderingbehoeften: Als je app kan zoomen, pannen, gebruikmaakt van camera transforms, clipping, tiling, level-of-detail rendering of virtualisatie, dan maakt Canvas het eenvoudig om ervoor te zorgen dat je alleen rendert wat echt nodig is.
- Aanwezigheid van een sterk intern model: Als je applicatie al een sterk concept heeft van state, geometrie, focus en interactie, en je alleen een manier nodig hebt om dit te visualiseren, dan is Canvas makkelijker dan HTML.
Belangrijke aandachtspunten bij implementatie
Als je besluit voor een Canvas-implementatie te kiezen, zijn dit de belangrijkste zaken om op te letten:
Beheer wanneer er gerenderd wordt
Het patroon dat voor ons het beste werkte, is één centrale renderer die andere klassen aanroept die specifieke aspecten renderen (bijv. backgroundRenderer, rowRenderer, taskRenderer). Elke klasse kan scheduleRender aanroepen op de renderer. Dit plant een enkele render-pass voor het volgende animatieframe. Let op dat we het volledige Canvas bij elk frame wissen en alles vanaf nul opnieuw renderen. Hoewel dit verspillend is, heeft het bij ons nooit tot problemen geleid; complexere implementaties zouden alleen specifieke regio's kunnen herrenderen.
class Renderer {
constructor(canvas) {
this.renderScheduled = false;
this.canvas = canvas;
this.context = canvas.getContext('2d');
this.backgroundRenderer = new BackgroundRenderer(this);
this.rowRenderer = new RowRenderer(this);
}
scheduleRender() {
if (this.renderScheduled) return;
this.renderScheduled = true;
requestAnimationFrame(this.render.bind(this));
}
render() {
this.renderScheduled = false;
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Render de individuele lagen in volgorde
this.backgroundRenderer.render(this.context);
this.rowRenderer.render(this.context);
}
}
Gebruik meerdere Canvas-elementen in lagen (Layering)
Voor onze planninginterface blijft het basisplan vrij statisch terwijl de gebruiker met de muis interacteert, maar zijn er veel highlight- en hover-effecten. Hiervoor gebruiken we een tweede Canvas-element met dezelfde afmetingen als het origineel. Deze InteractionRenderer ververst veel frequenter dan het eigenlijke Canvas, maar rendert slechts enkele bounding frames, wat veel lichter is.
Houd stijlen gescheiden
Het scheiden van CSS en HTML maakt het leven makkelijker; doe hetzelfde met Canvas. Gebruik een apart bestand voor stijlen, bijvoorbeeld:
gapDiagonalLineSpacing: 10,
textColor: '#ecf0f5',
textColorSecondary: '#4d6585',
fontStyle: "13px 'Lato', sans-serif",
fontStyleBold: "bold 13px 'Lato', sans-serif"
Beheer apparaatresolutie en pixeldichtheid
Om scherpe resultaten te krijgen, moet je ervoor zorgen dat je Canvas-element is geschaald naar de pixel ratio van het apparaat. Zet vervolgens (tegenintuïtief) de scale van de context om de scale van het element te compenseren. Zo krijg je een scherp resultaat zonder dat je code constant rekening hoeft te houden met pixel-densiteit.
getPixelScale() {
return Math.max(window.devicePixelRatio, 1);
}
scaleCanvas(canvas, ctx) {
const pixelScale = this.getPixelScale();
canvas.width = canvas.offsetWidth * pixelScale;
canvas.height = canvas.offsetHeight * pixelScale;
ctx.scale(pixelScale, pixelScale);
}
Gebruik centrale functies voor coördinatenvertaling
Als je een app met een oneindige werkruimte bouwt, hebben elementen waarschijnlijk X- en Y-coördinaten. Bij een spreadsheet heb je rij- en kolomindices. Deze verschillen van de werkelijke pixelcoördinaten op basis van resolutie, zoom, pan-positie en andere factoren. Het maakt het leven veel makkelijker om eenvoudige functies te hebben zoals getXForColumn(colIndex) of getPositionForDomainCoordinates(x,y).
Onderhoud een simpel box-model
Wanneer een gebruiker over elementen hovert of klikt, wil je snel weten waarmee ze interacteren. Bouw hiervoor vooraf een index van bounding boxes in de schermruimte op. Sla voor elke box de x1, x2, y1 en y2 coördinaten op, samen met de z-index en een identifier. Bij zeer grote hoeveelheden bounding boxes kun je indices op basis van x/y coördinaten maken voor snellere lookups, of zelfs een R-Tree overwegen voor ruimtelijke indexering.
Beheer de levenscyclus van Event Handlers
Gebruik één globale listener voor muis- en toetsenbordevenementen en een eenvoudige manier om callbacks te registreren en te deregistreren voor specifieke gebeurtenissen en elementen.
Conclusie: moet je Canvas gebruiken?
Canvas is geen snellere vervanging voor HTML. Het is een renderingtool op lager niveau die je meer controle geeft, maar je daarmee ook verantwoordelijk maakt voor veel werk dat de browser normaal gesproken voor je doet.
Voor de meeste webapps blijft de DOM de betere keuze. Het biedt gratis toegankelijkheid, responsive layouts, tekstselectie, input-afhandeling en talloze andere functies. Maar als de kern van je applicatie een grote, ruimtelijke werkruimte is met complexe positionering, zoomen, pannen of duizenden visuele elementen, dan kan Canvas beter passen.
Dat was onze ervaring. Het in eigen hand nemen van de rendering pipeline maakte de planninginterface begrijpelijker en zorgde voor voorspelbare prestaties op uiteenlopende apparaten. Het betekende echter ook dat we onze eigen systemen moesten bouwen voor interactie, hit-testing, schaling en rendering — werk dat niet onderschat mag worden.
Kies dus niet voor Canvas simpelweg omdat het "snel" klinkt. Kies ervoor wanneer je interface niet langer aanvoelt als een document, maar meer begint te functioneren als een scène.