Mozilla's getting a new look. What do you think? https://mzl.la/brandsurvey

Dessiner des objets DOM dans un élément canvas Redirection 1

Bien que ce ne soit pas trivial (pour des raisons de sécurité), il est possible de dessiner du contenu DOM (comme du HTML) dans un élément canvas. Cet article, qui tire son origine de ce billet de Robert O'Callagan, explique comment le faire de manière sécurisée et sans danger en respectant la spécification.

Aperçu

Vous ne pouvez pas simplement dessiner du HTML dans un élément canvas. Ce qu'il est possible de faire en revanche est d'utiliser une image SVG renfermant le contenu que vous voulez restituer. Pour dessiner du contenu HTML, vous utiliseriez un élément <foreignobject> contenant le HTML et ensuite dessineriez l'image SVG dans votre élément canvas.

Étape par étape

La seule partie difficile (et c'est sans doute surestimé) est de créer le fichier SVG de votre image. Tout ce dont vous avez besoin est de créer une chaîne de caractères contenant le XML pour le SVG et de construire un Blob composé de ce qui suit :

  1. Le type de media MIME du blob doit être "image/svg+xml".
  2. L'élément <svg>.
  3. Au sein de cet élément, l'élément <foreignobject>.
  4. Le HTML (bien formé) voulu, inséré dans le <foreignobject>.

En utilisant un objet URL comme décrit ci-dessus, vous pouvez insérer votre code HTML dans le même bloc de code plutôt que de le charger depuis une source externe. Vous pouvez bien sûr utiliser une source externe si vous préférez, à condition que son origine reste la même que celle du document actuel.

Un exemple

<!DOCTYPE html>
<html>
<body>
<p><canvas id="canvas" style="border:2px solid black;" width="200" height="200"></canvas>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var data = "<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'>" +
             "<foreignObject width='100%' height='100%'>" +
               "<div xmlns='http://www.w3.org/1999/xhtml' style='font-size:40px'>" +
                 "<em>J'</em> aime <span style='color:white; text-shadow:0 0 2px blue;'>les licornes.</span>" +
               "</div>" +
             "</foreignObject>" +
           "</svg>";
var DOMURL = self.URL || self.webkitURL || self;
var img = new Image();
var svg = new Blob([data], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
    ctx.drawImage(img, 0, 0);
    DOMURL.revokeObjectURL(url);
};
img.src = url;
</script>
</body>
</html>

L'exemple ci-dessus produira le résultat suivant :

image avec le texte "j'aime les licornes"

La variable data est réglée avec le contenu de l'image SVG (elle-même incluant le code HTML) que l'on veut dessiner dans l'élément canvas.

On crée ensuite une nouvel élément <img> en HTML en appelant new Image(), on y ajoute data, on alloue une URL objet et on dessine l'image dans le contexte en invoquant drawImage() au chargement.

Securité

Vous pouvez vous demander en quoi cela est-il sécurisé, étant donné que l'on pourrait éventuellment lire des données sensibles depuis cet élément canvas. Voici pourquoi : cette solution repose sur le fait que l'implémentation des images SVG est très restrictive. Les images SVG ne sont pas autorisées à charger des ressources externes, y compris celles qui peuvent apparaître comme provenant du même domaine. Les ressources comme les images matricielles (images JPEG par exemples) ou les <iframe>s doivent être renseignées en tant qu' data: URIs.

En plus de cela, vous ne pouvez pas insérer de script dans une image SVG et il n'y a donc pas de risque d'accès au DOM depuis d'autres scripts, les éléments DOM des images SVG ne peuvent quant à eux pas recevoir d'événements d'entrée (input), il n'y a donc pas les moyens de charger des informations privilégiées dans un formulaire (comme un chemin absolu dans le fichier de l'élément <input>) puis de le sessiner et d'en interpréter l'information grâce aux pixels.

Les styles des liens visités ne s'appliquent pas aux liens affichés dans les images SVG afin qu'aucune information concernant l'historique ne puisse être déduite, les thèmes natifs ne sont eux aussi pas appliqués dans les images SVG, rendant ainsi plus difficile la détermination de la plate-forme de l'utilisateur.

L'élément canvas résultant doit avoir une origine propre, c'est à dire que vous pouvez appeler toBlob(function(blob){…}) pour retourner un blob pour l'élément canvas ou toDataURL() pour retourner une data: URI encodée en Base64.

Tester l'origine depuis les documents qui les contiennent.

Dessiner du HTML

Le code SVG devant être du code XML valide, vous devrez analyser le code HTML pour en produire un code HTML bien formé. Le code qui suit est le plus simple pour analyser du code HTML.

var doc = document.implementation.createHTMLDocument("");
doc.write(html);

// Vous devez donner une valeur au xmlns si vous souhaitez immédiatement normaliser le code du document HTML 
// en une chaîne de caractère plutôt que de l'ajouter à un <foreignObject> dans le DOM
doc.documentElement.setAttribute("xmlns", doc.documentElement.namespaceURI);

// Avoir le balisage bien formé
html = (new XMLSerializer).serializeToString(doc);

Voir aussi

Étiquettes et contributeurs liés au document

 Contributeurs à cette page : tregagnon
 Dernière mise à jour par : tregagnon,