Canvas est un moyen populaire de dessiner toutes sortes de graphiques à l'écran et un point d'entrée dans le monde de WebGL. Il peut être utilisé pour dessiner des formes, des images, exécuter des animations, ou même afficher et traiter du contenu vidéo. Il est souvent utilisé pour créer des expériences utilisateur esthétiques dans les applications Web multimédias et les jeux en ligne.
Il est programmable, ce qui signifie que le contenu dessiné sur le canevas peut être créé de manière programmatique, par exemple en JavaScript. Cela offre une grande flexibilité au canevas.
Dans les sites Web modernes, l'exécution de script est l'une des sources les plus fréquentes de problèmes de réactivité de l'utilisateur. Étant donné que la logique et le rendu du canevas se produisent sur le même thread que l'interaction utilisateur, les calculs (parfois lourds) impliqués dans les animations peuvent nuire aux performances réelles et perçues de l'application.
Heureusement, OffscreenCanvas est une réponse à cette menace.
Auparavant, les fonctionnalités de dessin du canevas étaient liées à l'élément <canvas>
, ce qui signifie qu'elles dépendaient directement du DOM. Comme son nom l'indique, OffscreenCanvas dissocie le DOM et l'API Canvas en les déplaçant hors écran.
Grâce à ce découplage, le rendu d'OffscreenCanvas est entièrement dissocié du DOM et offre donc des améliorations de vitesse par rapport au canevas standard, car il n'y a pas de synchronisation entre les deux.
De plus, il peut être utilisé dans un Web Worker, même s'il n'y a pas de DOM disponible. Cela permet tous types de cas d'utilisation intéressants.
Utiliser OffscreenCanvas dans un worker
Les workers sont la version Web des threads. Ils vous permettent d'exécuter des tâches en arrière-plan.
Déplacer certains de vos scripts vers un nœud de calcul offre à votre application plus de marge pour effectuer des tâches critiques pour l'utilisateur sur le thread principal. Sans OffscreenCanvas, il n'était pas possible d'utiliser l'API Canvas dans un worker, car aucun DOM n'était disponible.
OffscreenCanvas ne dépend pas du DOM et peut donc être utilisé. L'exemple suivant utilise OffscreenCanvas pour calculer une couleur de dégradé dans un worker:
// file: worker.js
function getGradientColor(percent) {
const canvas = new OffscreenCanvas(100, 1);
const ctx = canvas.getContext('2d');
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, ctx.canvas.width, 1);
const imgd = ctx.getImageData(0, 0, ctx.canvas.width, 1);
const colors = imgd.data.slice(percent * 4, percent * 4 + 4);
return `rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, ${colors[3]})`;
}
getGradientColor(40); // rgba(152, 0, 104, 255 )
Débloquer le thread principal
Déplacer un calcul lourd vers un nœud de calcul vous permet de libérer des ressources importantes sur le thread principal. Utilisez la méthode transferControlToOffscreen pour dupliquer le canevas standard dans une instance OffscreenCanvas. Les opérations appliquées à OffscreenCanvas seront automatiquement affichées sur le canevas source.
const offscreen = document.querySelector('canvas').transferControlToOffscreen();
const worker = new Worker('myworkerurl.js');
worker.postMessage({canvas: offscreen}, [offscreen]);
Dans l'exemple suivant, le calcul lourd se produit lorsque le thème de couleur change. Il devrait prendre quelques millisecondes, même sur un ordinateur de bureau rapide. Vous pouvez choisir d'exécuter des animations sur le thread principal ou dans le nœud de calcul. Dans le cas du thread principal, vous ne pouvez pas interagir avec le bouton tant que la tâche lourde est en cours d'exécution. Le thread est bloqué. Dans le cas du worker, cela n'a aucun impact sur la réactivité de l'UI.
L'inverse fonctionne également: le thread principal occupé n'a aucune incidence sur l'animation exécutée sur un worker. Vous pouvez utiliser cette fonctionnalité pour éviter les à-coups visuels et garantir une animation fluide malgré le trafic du thread principal, comme illustré dans la démonstration suivante.
Dans le cas d'un canevas standard, l'animation s'arrête lorsque le thread principal est surchargé artificiellement, tandis que l'OffscreenCanvas basé sur un nœud de calcul s'exécute de manière fluide.
Utiliser avec des bibliothèques populaires
Étant donné que l'API OffscreenCanvas est généralement compatible avec l'élément Canvas standard, vous pouvez l'utiliser comme amélioration progressive, y compris avec certaines des principales bibliothèques graphiques du marché.
Par exemple, vous pouvez le détecter et, le cas échéant, l'utiliser avec Three.js en spécifiant l'option de canevas dans le constructeur du rendu:
const canvasEl = document.querySelector('canvas');
const canvas =
'OffscreenCanvas' in window
? canvasEl.transferControlToOffscreen()
: canvasEl;
canvas.style = {width: 0, height: 0};
const renderer = new THREE.WebGLRenderer({canvas: canvas});
Le seul problème est que Three.js s'attend à ce que le canevas possède une propriété style.width
et style.height
.
OffscreenCanvas, étant complètement détaché du DOM, ne l'a pas. Vous devez donc le fournir vous-même, soit en le remplaçant par un bouchon, soit en fournissant une logique qui lie ces valeurs aux dimensions d'origine du canevas.
Voici comment exécuter une animation Three.js de base dans un worker:
N'oubliez pas que certaines des API liées au DOM ne sont pas facilement disponibles dans un worker. Par conséquent, si vous souhaitez utiliser des fonctionnalités Three.js plus avancées telles que les textures, vous devrez peut-être trouver d'autres solutions. Pour obtenir des idées sur la façon de commencer à les tester, regardez la vidéo de la conférence Google I/O 2017.
Si vous utilisez beaucoup les fonctionnalités graphiques du canevas, OffscreenCanvas peut avoir un impact positif sur les performances de votre application. Rendre les contextes de rendu du canevas disponibles pour les workers augmente le parallélisme dans les applications Web et permet une meilleure utilisation des systèmes multicœurs.