Introduzione a WebGL, Parte 2 L'elemento Canvas per il nostro primo Shader

Nel precedente articolo, abbiamo scritto i nostri primi vertex e frammenti shader. Dopo aver scritto il codice lato GPU, è ora di imparare come scrivere quello lato CPU. In questo tutorial e nel prossimo, ti mostrerò come incorporare gli shader nella tua applicazione WebGL. Iniziamo da zero, utilizzando solo JavaScript e senza librerie di terze parti. In questa parte, tratteremo il codice specifico della tela. Nel prossimo, tratteremo quello specifico di WebGL.

Nota che questi articoli:

  • Supponiamo che tu abbia familiarità con gli shader GLSL. In caso contrario, si prega di leggere il primo articolo.
  • non sono intesi per insegnarti HTML, CSS o JavaScript. Cercherò di spiegare i concetti difficili mentre li incontriamo, ma dovrai cercare ulteriori informazioni su di loro sul web. MDN (Mozilla Developer Network) è un posto eccellente per farlo.

Iniziamo già!

Che cos'è WebGL?

WebGL 1.0 è un'API di grafica 3D di basso livello per il web, esposta attraverso l'elemento Canvas HTML5. È un'API basata sullo shader molto simile all'API OpenGL ES 2.0. WebGL 2.0 è lo stesso, ma è basato su OpenGL ES 3.0. WebGL 2.0 non è interamente retrocompatibile con WebGL 1.0, ma la maggior parte delle applicazioni WebGL 1.0 prive di errori che non usano estensioni dovrebbero funzionare su WebGL 2.0 senza problemi.

Al momento di scrivere questo articolo, le implementazioni di WebGL 2.0 sono ancora sperimentali nei pochi browser che lo implementano. Inoltre, non sono abilitati per impostazione predefinita. Pertanto, il codice che scriveremo in questa serie è indirizzato a WebGL 1.0.

Dai un'occhiata al seguente esempio (ricordati di cambiare scheda e di dare qualche sguardo al codice):

Questo è il codice che scriveremo. Sì, in realtà ci vogliono poco più di cento linee di JavaScript per implementare qualcosa di così semplice. Ma non preoccuparti, ci prenderemo il nostro tempo per spiegarli in modo che alla fine abbiano tutti un senso. Copriremo il codice relativo alla tela in questo tutorial e continueremo con il codice specifico per WebGL nel prossimo.

La tela

Per prima cosa, dobbiamo creare una tela in cui mostreremo le nostre cose renderizzate. 

Questa piccola piazza carina è la nostra tela! Passare al HTML guarda e vediamo come ce l'abbiamo fatta.

Questo per dire al browser che non vogliamo che la nostra pagina sia ingrandibile sui dispositivi mobili.

E questo è il nostro elemento di tela. Se non assegnassimo quote al nostro canvas, sarebbe stato predefinito a 300 * 150 pixel (pixel CSS). Ora passa al CSS guarda per vedere come lo abbiamo stilizzato.

tela ...

Questo è un selettore CSS. Questo particolare significa che le seguenti regole verranno applicate a tutti gli elementi canvas nel nostro documento. 

sfondo: # 0f0;

Infine, la regola da applicare agli elementi canvas. Lo sfondo è impostato su verde chiaro (# 0F0).

Nota: nell'editor sopra, il testo CSS viene allegato automaticamente al documento. Quando crei i tuoi file, devi collegarti al file CSS nel tuo file HTML in questo modo:

Preferibilmente, mettilo nel capo etichetta.

Ora che la tela è pronta, è tempo di disegnare qualcosa! Sfortunatamente, anche se il quadro appare bello e tutto, abbiamo ancora una lunga strada da percorrere prima di poter disegnare qualsiasi cosa usando WebGL. Quindi rottami WebGL! Per questo tutorial, faremo un semplice disegno 2D per spiegare alcuni concetti prima di passare a WebGL. Lascia che il nostro disegno sia una linea diagonale.

Contesto di rendering

L'HTML è lo stesso dell'ultimo esempio, eccetto per questa linea:   

in cui abbiamo dato un id all'elemento canvas in modo che possiamo recuperarlo facilmente in JavaScript. Il CSS è esattamente lo stesso e una nuova scheda JavaScript è stata aggiunta per eseguire il disegno.

Passare al JS linguetta,

window.addEventListener ('load', function () ...);

Nell'esempio precedente, il codice JavaScript che abbiamo scritto deve essere associato al capo del documento, vale a dire che viene eseguito prima che la pagina finisca il caricamento. Ma se è così, non saremo in grado di disegnare sulla tela, che deve ancora essere creata. Ecco perché rimandiamo il nostro codice fino a dopo il caricamento della pagina. Per fare questo, usiamo window.addEventListener, specificando caricare come l'evento che vogliamo ascoltare e il nostro codice come una funzione che viene eseguita quando l'evento viene attivato.

Andare avanti:

var canvas = document.getElementById ("canvas");

Ricorda l'id che abbiamo assegnato alla tela in precedenza nell'HTML? Qui è dove diventa utile. Nella riga precedente recuperiamo l'elemento canvas dal documento usando il suo id come riferimento. D'ora in poi, le cose diventeranno più interessanti,

context = canvas.getContext ('2d');

Per poter eseguire qualsiasi disegno sulla tela, dobbiamo prima acquisire un contesto di disegno. Un contesto in questo senso è un oggetto helper che espone l'API di disegno richiesta e la lega all'elemento canvas. Ciò significa che qualsiasi utilizzo successivo dell'API che utilizza questo contesto verrà eseguito sull'oggetto canvas in questione.

In questo caso particolare, abbiamo richiesto a 2d contesto di disegno (CanvasRenderingContext2D) che ci consente di utilizzare funzioni di disegno 2D arbitrarie. Potremmo aver richiesto un WebGL, un webgl2 o a bitmaprenderer contesti invece, ognuno dei quali avrebbe esposto un diverso insieme di funzioni.

Una tela ha sempre impostato la sua modalità di contesto nessuna inizialmente. Quindi, chiamando getContext, la sua modalità cambia in modo permanente. Non importa quante volte chiami getContext su una tela, non cambierà la sua modalità dopo che è stata inizialmente impostata. chiamata getContext di nuovo per la stessa API restituirà lo stesso oggetto di contesto restituito al primo utilizzo. chiamata getContext per un ritorno API diverso nullo.

Sfortunatamente, le cose possono andare storte. In alcuni casi particolari, getContext potrebbe non essere in grado di creare un contesto e genererebbe invece un'eccezione. Mentre questo è piuttosto raro al giorno d'oggi, è possibile con 2d contesti. Quindi, invece di andare in crash se questo accade, abbiamo incapsulato il nostro codice in a prova a prendere bloccare:

prova context = canvas.getContext ('2d');  catch (exception) alert ("Umm ... scusa, non contesti 2d per te!" + exception.message); ritorno ; 

In questo modo, se viene lanciata un'eccezione, possiamo prenderla e visualizzare un messaggio di errore, quindi procedere con grazia per colpire la nostra testa contro il muro. O magari visualizzare un'immagine statica di una linea diagonale. Mentre potremmo farlo, sfida l'obiettivo di questo tutorial!

Supponendo di aver acquisito con successo un contesto, tutto ciò che resta da fare è tracciare la linea:

context.beginPath ();

Il 2d il contesto ricorda l'ultimo percorso che hai costruito. Disegnare un percorso non lo scarta automaticamente dalla memoria del contesto. BeginPath dice al contesto di dimenticare tutti i percorsi precedenti e ricominciare da capo. Quindi sì, in questo caso, avremmo potuto omettere questa linea del tutto e avrebbe funzionato perfettamente, dal momento che non c'erano percorsi precedenti da cui iniziare.

contesto.moveTo (0, 0);

Un percorso può essere costituito da più percorsi secondari. moveTo avvia un nuovo sotto-percorso alle coordinate richieste.

context.lineTo (30, 30);

Crea un segmento di linea dall'ultimo punto del sottotracciato a (30, 30). Questo significa una linea diagonale dall'angolo superiore sinistro della tela (0, 0) all'angolo in basso a destra (30, 30).

context.stroke ();

Creare un percorso è una cosa; disegnarlo è un altro. ictus dice al contesto di disegnare tutti i sotto-percorsi nella sua memoria.

BeginPath, moveTo, lineTo, e ictus sono disponibili solo perché abbiamo richiesto un 2d contesto. Se, ad esempio, abbiamo richiesto a WebGL contesto, queste funzioni non sarebbero state disponibili.

Nota: nell'editor sopra, il codice JavaScript viene allegato automaticamente al documento. Quando crei i tuoi file, devi collegarti al file JavaScript nel tuo file HTML in questo modo:

Dovresti metterlo in capo etichetta.

Questo conclude il nostro tutorial sul disegno di linee! Ma in qualche modo, non sono soddisfatto di questa piccola tela. Possiamo fare più grandi di questo!

Dimensionamento della tela

Aggiungeremo alcune regole al nostro CSS per fare in modo che la tela riempia l'intera pagina. Il nuovo codice CSS assomiglierà a questo:

html, body height: 100%;  body margin: 0;  canvas display: block; larghezza: 100%; altezza: 100%; sfondo: # 888; 

Smontiamolo:

html, body height: 100%; 

Il html e corpo gli elementi sono trattati come elementi di blocco; consumano l'intera larghezza disponibile. Tuttavia, si espandono verticalmente quanto basta per avvolgere il loro contenuto. In altre parole, le loro altezze dipendono dalle altezze dei loro figli. L'impostazione di un'altezza dei loro figli ad una percentuale della loro altezza causerà un ciclo di dipendenza. Quindi, a meno che non assegniamo esplicitamente valori alle loro altezze, non saremmo in grado di impostare le altezze relative ai bambini.

Dal momento che vogliamo che la tela riempia l'intera pagina (imposta la sua altezza al 100% del suo genitore), impostiamo le loro altezze al 100% (dell'altezza della pagina).

body margin: 0; 

I browser hanno fogli di stile di base che danno uno stile predefinito a qualsiasi documento che renderizzano. Si chiama il fogli di stile user-agent. Gli stili in questi fogli dipendono dal browser in questione. A volte possono anche essere regolati dall'utente.

Il corpo l'elemento di solito ha un margine predefinito nei fogli di stile utente-agente. Vogliamo che la tela riempia l'intera pagina, quindi ne impostiamo i margini 0.

canvas display: block;

A differenza degli elementi di blocco, gli elementi in linea sono elementi che possono essere trattati come testo su una linea normale. Possono avere elementi prima o dopo di essi sulla stessa riga e hanno uno spazio vuoto al di sotto di essi le cui dimensioni dipendono dal tipo di carattere e dalla dimensione del carattere in uso. Non vogliamo alcuno spazio vuoto sotto la nostra tela, quindi impostiamo semplicemente la sua modalità di visualizzazione bloccare.

larghezza: 100%; altezza: 100%;

Come previsto, impostiamo le dimensioni del canvas su 100% della larghezza e altezza della pagina.

sfondo: # 888;

L'abbiamo già spiegato prima, no??!

Guarda il risultato dei nostri cambiamenti ...

...

...

No, non abbiamo fatto niente di sbagliato! Questo è un comportamento totalmente normale. Ricorda le dimensioni che abbiamo dato alla tela nel HTML etichetta?

Ora siamo andati e abbiamo dato alla tela altre dimensioni nel CSS:

canvas ... width: 100%; altezza: 100%; ... 

Si scopre che le dimensioni che impostiamo nel tag HTML controllano il dimensioni intrinseche della tela. La tela è più o meno un contenitore bitmap. Le dimensioni bitmap sono indipendenti su come verrà visualizzato il canvas nella sua posizione finale e le dimensioni nella pagina. Ciò che definisce questi sono i dimensioni estrinseche, quelli che abbiamo impostato nel CSS.

Come possiamo vedere, la nostra piccola bitmap 30 * 30 è stata allungata per riempire l'intero canvas. Questo è controllato dal CSS oggetto-fit proprietà, che per impostazione predefinita riempire. Ci sono altre modalità che, per esempio, sono clip anziché scala, ma da allora riempire non ci arriverò (in realtà può essere utile), lasceremo stare. Se stai pianificando di supportare Internet Explorer o Edge, non puoi comunque fare nulla al riguardo. Al momento di scrivere questo articolo, non supportano oggetto-fit affatto.

Tuttavia, tieni presente che il modo in cui il browser ridimensiona il contenuto è ancora oggetto di discussione. La proprietà CSS immagine rendering è stato proposto di gestirlo, ma è ancora sperimentale (se supportato a tutti) e non impone determinati algoritmi di ridimensionamento. Non solo, il browser può scegliere di trascurarlo del tutto dal momento che è solo un suggerimento. Ciò significa che, per il momento, diversi browser utilizzeranno algoritmi di ridimensionamento diversi per ridimensionare la bitmap. Alcuni di questi hanno artefatti davvero terribili, quindi non scalare troppo.

Se stiamo disegnando usando a 2d contesto o altri tipi di contesti (come WebGL), la tela si comporta quasi allo stesso modo. Se vogliamo che la nostra piccola bitmap riempia l'intero canvas e non ci piaccia lo stretching, dovremmo controllare le modifiche alle dimensioni della tela e regolare di conseguenza le dimensioni della bitmap. Facciamolo ora,

Osservando le modifiche che abbiamo apportato, abbiamo aggiunto queste due righe a JavaScript:

canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight;

Sì, quando si usa 2d contesti, l'impostazione delle dimensioni della bitmap interna per le dimensioni della tela è così facile! La tela larghezza e altezza sono monitorati, e quando qualcuno di loro è scritto (anche se è lo stesso valore):

  • La bitmap corrente viene distrutta.
  • Ne viene creato uno nuovo con le nuove dimensioni.
  • La nuova bitmap viene inizializzata con il valore predefinito (nero trasparente).
  • Qualsiasi contesto associato viene ripristinato allo stato iniziale e viene reinizializzato con le quote dello spazio delle coordinate appena specificate.

Si noti che, per impostare entrambi i larghezza e altezza, i passaggi precedenti sono eseguiti due volte! Una volta quando si cambia larghezza e l'altro quando cambia altezza. No, non c'è altro modo per farlo, non che io sappia.

Abbiamo anche esteso la nostra linea corta per diventare la nuova diagonale,

context.lineTo (canvas.width, canvas.height);

invece di: 

context.lineTo (30, 30);

Dal momento che non usiamo più le dimensioni originali 30 * 30, non sono più necessarie nel codice HTML:

Potremmo averli lasciati inizializzati su valori molto piccoli (come 1 * 1) per risparmiare l'overhead della creazione di una bitmap usando le dimensioni predefinite relativamente grandi (300 * 150), inizializzandola, eliminandola e creando una nuova con il dimensione corretta che abbiamo impostato in JavaScript.

...

a pensarci bene, facciamolo!

Nessuno dovrebbe mai notare la differenza, ma non posso sopportare la colpa!

Pixel CSS vs. Pixel fisico

Mi sarebbe piaciuto dirlo, ma non lo è! offsetWidth e offsetHeight sono specificati in pixel CSS. 

Ecco il trucco. I pixel CSS sono non pixel fisici. Sono pixel indipendenti dalla densità. A seconda della densità dei pixel fisici del dispositivo (e del browser), un pixel CSS può corrispondere a uno o più pixel fisici.

Mettiamola palesemente, se hai uno smartphone da 5 pollici Full-HD, allora offsetWidth*offsetHeight sarebbe 640 * 360 anziché 1920 * 1080. Certo, riempie lo schermo, ma poiché le dimensioni interne sono impostate su 640 * 360, il risultato è una bitmap allungata che non sfrutta appieno l'alta risoluzione del dispositivo. Per risolvere questo problema, teniamo conto del devicePixelRatio:

var pixelRatio = window.devicePixelRatio? window.devicePixelRatio: 1.0; canvas.width = pixelRatio * canvas.offsetWidth; canvas.height = pixelRatio * canvas.offsetHeight;

devicePixelRatio è il rapporto tra il pixel CSS e il pixel fisico. In altre parole, quanti pixel fisici rappresenta un singolo pixel CSS.

var pixelRatio = window.devicePixelRatio? window.devicePixelRatio: 1.0;

window.devicePixelRatio è ben supportato nella maggior parte dei browser moderni, ma nel caso in cui non è definito, torniamo al valore predefinito di 1.0.

canvas.width = pixelRatio * canvas.offsetWidth; canvas.height = pixelRatio * canvas.offsetHeight;

Moltiplicando le dimensioni CSS con il rapporto pixel, torniamo alle dimensioni fisiche. Ora la nostra bitmap interna ha esattamente le stesse dimensioni del canvas e non si verificherà alcun allungamento.

Se tuo devicePixelRatio è 1 quindi non ci sarà alcuna differenza. Tuttavia, per qualsiasi altro valore, la differenza è significativa.

Rispondere alle modifiche alle dimensioni

Non è tutto ciò che è necessario per gestire il dimensionamento del canvas. Poiché abbiamo specificato le dimensioni CSS relative alle dimensioni della pagina, le modifiche alle dimensioni della pagina hanno effetto su di noi. Se siamo in esecuzione su un browser desktop, l'utente può ridimensionare la finestra manualmente. Se siamo in esecuzione su un dispositivo mobile, siamo soggetti a cambiamenti di orientamento. Non menzionando che potremmo essere in esecuzione in un iframe che cambia le sue dimensioni arbitrariamente. Per mantenere la nostra bitmap interna correttamente dimensionata in ogni momento, dobbiamo controllare le modifiche nella dimensione della pagina (finestra),

Abbiamo spostato il nostro codice ridimensionamento bitmap:

// Ottieni il rapporto pixel del dispositivo, var pixelRatio = window.devicePixelRatio? window.devicePixelRatio: 1.0; // Regola le dimensioni del canvas, canvas.width = pixelRatio * canvas.offsetWidth; canvas.height = pixelRatio * canvas.offsetHeight;

Per una funzione separata, adjustCanvasBitmapSize:

function adjustCanvasBitmapSize () // Ottieni il rapporto pixel del dispositivo, var pixelRatio = window.devicePixelRatio? window.devicePixelRatio: 1.0; if ((canvas.width / pixelRatio)! = canvas.offsetWidth) canvas.width = pixelRatio * canvas.offsetWidth; if ((canvas.height / pixelRatio)! = canvas.offsetHeight) canvas.height = pixelRatio * canvas.offsetHeight; 

con una piccola modifica Poiché sappiamo quanto sono costosi i valori di assegnazione larghezza o altezza è, sarebbe irresponsabile fare così inutilmente. Ora ci limitiamo a impostare larghezza e altezza quando cambiano davvero.

Poiché la nostra funzione accede alla nostra tela, la dichiareremo dove può vederla. Inizialmente, è stato dichiarato in questa riga:

var canvas = document.getElementById ("canvas");

Questo lo rende locale al nostro funzione anonima. Potremmo aver appena rimosso il var parte e sarebbe diventato globale (o più nello specifico, a proprietà del oggetto globale, a cui si può accedere attraverso finestra):

canvas = document.getElementById ("canvas");

Tuttavia, sconsiglio vivamente dichiarazione implicita. Se dichiari sempre le tue variabili, eviterai molta confusione. Quindi, invece, lo dichiarerò al di fuori di tutte le funzioni:

tela var; contesto var;

Questo lo rende anche una proprietà dell'oggetto globale (con una piccola differenza che non ci disturba affatto). Esistono altri modi per creare una variabile globale: esaminali in questo thread StackOverflow. 

Oh, e mi sono intrufolato contesto anche lassù! Questo si rivelerà utile in seguito.

Ora, agganciamo la nostra funzione alla finestra ridimensionare evento:

window.addEventListener ('resize', adjustCanvasBitmapSize);

D'ora in poi, ogni volta che la dimensione della finestra viene cambiata, adjustCanvasBitmapSize è chiamato. Ma dal momento che l'evento dimensione della finestra non viene lanciato al caricamento iniziale, la nostra bitmap sarà ancora 1 * 1. Pertanto, dobbiamo chiamare adjustCanvasBitmapSize una volta da soli.

adjustCanvasBitmapSize ();

Questo praticamente si prende cura di esso ... tranne che quando ridimensionate la finestra, la linea scompare! Provalo in questa demo.

Fortunatamente, questo è normale. Ricordare i passaggi effettuati quando la bitmap viene ridimensionata? Uno di questi era inizializzarlo in nero trasparente. Questo è quello che è successo qui. La bitmap è stata sovrascritta con il nero trasparente, e ora lo sfondo di tela verde traspare. Questo succede perché disegniamo la nostra linea solo una volta all'inizio. Quando si verifica l'evento di ridimensionamento, i contenuti vengono cancellati e non ridisegnati. Risolvere questo dovrebbe essere facile. Spostiamoci disegnando la nostra linea in una funzione separata:

function drawScene () // Disegna la nostra linea, context.beginPath (); contesto.moveTo (0, 0); context.lineTo (canvas.width, canvas.height); context.stroke (); 

e chiama questa funzione dall'interno adjustCanvasBitmapSize:

// Ridisegna di nuovo tutto, drawScene ();

Tuttavia, in questo modo la nostra scena verrà ridisegnata ogni volta adjustCanvasBitmapSize viene chiamato, anche se non è stata apportata alcuna modifica alle dimensioni. Per gestire questo, aggiungeremo un semplice controllo:

// Annulla se nulla è cambiato, se (((canvas.width / pixelRatio) == canvas.offsetWidth) && ((canvas.height / pixelRatio) == canvas.offsetHeight)) return; 

Controlla il risultato finale:

Prova a ridimensionarlo qui.

Limitazione degli eventi di ridimensionamento

Finora stiamo andando alla grande! Tuttavia, ridimensionare e ridisegnare tutto può diventare molto costoso quando la tela è abbastanza grande e / o quando la scena è complicata. Inoltre, il ridimensionamento della finestra con il mouse può innescare il ridimensionamento degli eventi ad alta velocità. Ecco perché lo strozzeremo. Invece di:

window.addEventListener ('resize', adjustCanvasBitmapSize);

useremo:

window.addEventListener ('resize', function onWindowResize (event) // Attendi fino a quando il flood di ridimensionamento degli eventi si risolve, if (onWindowResize.timeoutId) window.clearTimeout (onWindowResize.timeoutId); onWindowResize.timeoutId = window.setTimeout (adjustCanvasBitmapSize, 600 ););

Primo,

window.addEventListener ('resize', function onWindowResize (event) ...);

invece di chiamare direttamente adjustCanvasBitmapSize quando il ridimensionare l'evento è licenziato, abbiamo usato un espressione di funzione per definire il comportamento desiderato. A differenza della funzione che abbiamo usato in precedenza per il caricare evento, questa funzione è a funzione denominata. Dare un nome alla funzione consente di fare facilmente riferimento ad esso all'interno della funzione stessa.

if (onWindowResize.timeoutId) window.clearTimeout (onWindowResize.timeoutId);

Proprio come gli altri oggetti, è possibile aggiungere proprietà oggetti funzione. inizialmente, timeoutId è non definito, quindi, questa affermazione non viene eseguita. Fai attenzione quando usi non definito e nullo nel espressioni logiche, perché possono essere difficili. Maggiori informazioni su di loro nella specifica del linguaggio ECMAScript.

Dopo, timeoutId terrà il timeoutID di un adjustCanvasBitmapSize tempo scaduto:

onWindowResize.timeoutId = window.setTimeout (adjustCanvasBitmapSize, 600);

Questo ritarda la chiamata adjustCanvasBitmapSize per 600 millisecondi dopo che l'evento è stato licenziato. Ma non impedisce all'evento di sparare. Se non viene sparato di nuovo entro questi 600 millisecondi, allora adjustCanvasBitmapSize viene eseguito e la bitmap viene ridimensionata. Altrimenti, clearTimeout annulla il programmato adjustCanvasBitmapSize e setTimeout pianifica altri 600 millisecondi in futuro. Il risultato è, finché l'utente sta ancora ridimensionando la finestra, adjustCanvasBitmapSize non è chiamato. Quando l'utente si ferma o si interrompe per un po ', viene chiamato. Vai avanti, provalo:

Err ... Voglio dire, qui.

Perché 600 millisecondi? Penso che non sia troppo veloce e non troppo lento, ma più di ogni altra cosa, funziona bene con l'inserimento / l'uscita di animazioni a schermo intero, che è fuori dallo scopo di questo tutorial.

Questo conclude il nostro tutorial per oggi! Abbiamo coperto tutto il codice specifico per la tela di cui abbiamo bisogno per impostare la nostra tela. La prossima volta, se Allah vuole, copriremo il codice specifico di WebGL ed eseguiremo effettivamente lo shader. Fino ad allora, grazie per aver letto!

Riferimenti

  • Elemento Canvas nella bozza di editor w3c
  • Versione w3c in cui il comportamento di inizializzazione della tela è effettivamente documentato
  • Elemento Canvas in specifiche live whatwg