web-dev-qa-db-fra.com

Les contenus SVG foreignObject ne s'affichent que si du texte brut

J'essaye de générer du HTML en utilisant la balise foreignObject dans un dessin SVG. J'utilise d3 pour générer les éléments. Le contenu HTML à l'intérieur de la balise foreignObject s'affiche uniquement lorsque le contenu à l'intérieur de la balise foreignObect est en texte brut. Sinon, il apparaît simplement comme vide/vide. S'il vous plaît voir ce jsfiddle pour un exemple de mon problème: http://jsfiddle.net/R9e3Y/29/

Le contenu de la balise foreignObject s'affiche lors de l'inspection de l'élément, ceci:

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
    <foreignObject x="40" y="40" width="100" height="100">
        <div>test</div>
    </foreignObject>
</svg> 

mais ne sont pas visibles sur l'écran? Comment puis-je faire apparaître le contenu?

27
Atomix

Puisque vous utilisez d3, vous devez indiquer à d3 que la div est une div html et non un élément de l’espace de noms svg. Essayer

.append("xhtml:div")
53
Robert Longson

<foreignObject> permet d’incorporer toutes sortes de balises, pas seulement HTML. Cela signifie qu'il doit y avoir un moyen de déterminer quelle langue est utilisée. C'est là que les espaces de noms entrent en jeu.

Pour dire à SVG quel genre de foreignObject vous avez, vous devez placer le contenu dans le namespace approprié.

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
  <foreignObject x="40" y="40" width="100" height="100">
    <div xmlns="http://www.w3.org/1999/xhtml">test</div>
  </foreignObject>
</svg> 

Dans votre exemple, l’élément <div> se trouve dans l’espace de noms SVG, c’est-à-dire qu’il s’agit d’un élément SVG, et non HTML, bien qu’il ne s’agisse pas d’un élément standard.

L'élément <foreignObject> possède également un attribut requiredExtensions pour indiquer au navigateur quelle extension est utilisée, bien que différents navigateurs semblent interpréter cet attribut différemment. Il est donc probablement préférable de ne pas le définir.

12
Thomas W

Assurez-vous de définir les paramètres width et height.

Ajouter xmlns n'a pas réglé les problèmes pour moi. Il s’est avéré que le problème était pour moi encore plus simple ... Je n’ai pas ajouté les paramètres width et height; le contenu de la foreignObject ne s’est donc pas affiché, même s’il était présent. Les deux paramètres semblent par défaut à 0.

4
Melle

J'ai développé quelque chose pour cela. Le code est ci-dessous. Une version plus avancée utilise un proxy pour extraire des ressources externes non CORS. svg foreignObject bloque le chargement de toute demande d'origine croisée non CORS. J'ai écrit un proxy simple à exécuter sur Runkit. Voir en bas.

Les limitations de ceci sont: pas de polices externes non CORS, pas d'images non CORS. Quiconque souhaite contribuer à l'amélioration de cette situation, notamment en ajoutant le support des images et des polices, peut contribuer ici: https://github.com/dosyago-coder-0/dompeg.js/blob/master/dompeg.js

script de page Web:

(async function (){ 
  const width = document.scrollingElement.scrollWidth;
  const height = document.scrollingElement.scrollHeight;
  const doc = document.implementation.createHTMLDocument('');
  doc.write(document.documentElement.outerHTML);
  doc.documentElement.setAttribute('xmlns', doc.documentElement.namespaceURI);

  const styles = [];
  for( let i = 0; i < document.styleSheets.length; i++ ) {
    const ss = document.styleSheets[i];
    if ( ss.cssRules ) {
      for( let j = 0; j < ss.cssRules.length; j++ ) {
         styles.Push( ss.cssRules[j].cssText );
      }
    } else {
      try {
        const res = await fetch(ss.href);
        const cssText = await res.text();
        styles.Push(cssText);
      } catch(e) {
          /** fetch to proxy here as fallback
           * uncomment if you set up your proxy server 
        try {
          const res = await fetch(`https://${YOUR PROXY SERVER}.runkit.sh/?url=${btoa(ss.href)}`);
          const cssText = await res.text();
          styles.Push(cssText);
        } catch(e) { **/
          console.warn(`Exception adding styles from ${ss.href}`, e, e.stack);
        /** uncomment if you setup proxy  
        }  
        **/
      }
    }
  }


  Array.from( doc.querySelectorAll('noscript, link, script')).forEach( el => el.remove() );
  stripComments(doc);
  Array.from( doc.querySelectorAll('*[style]')).forEach( el => {
    const styleText = el.getAttribute('style');
    const uniq = (Math.random()+''+performance.now()).replace(/\./g,'x');
    const className = `class${uniq}`;
    const cssText = `.${className} {${ styleText }}`;
    styles.Push( cssText );
    el.classList.add( className );
  });


  const styleElement = doc.createElement('style');
  styleElement.innerText = styles.join('\n');
  doc.documentElement.appendChild(styleElement);


  const canvas = document.createElement('canvas');
  Object.assign( canvas, {width,height});
  const ctx = canvas.getContext('2d');

  const data = `
  <svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
    <foreignObject width="100%" height="100%">
     ${(new XMLSerializer).serializeToString(doc).slice(15)}
    </foreignObject>
  </svg>`;
  const DOMURL = window.URL || window.webkitURL || window;

  const img = new Image();
  const svg = new Blob([data], {type: 'image/svg+xml'});

  Object.assign( img, {width,height});  
  img.crossOrigin = "Anonymous";
  img.onload = function() {
    ctx.fillStyle = 'white';
    ctx.fillRect( 0, 0, canvas.width, canvas.height );
    ctx.drawImage(img, 0, 0);
    const datauri = canvas.toDataURL('image/jpeg');
    const anchor = document.createElement('a');
    anchor.download = 'screen.jpg';
    anchor.href = datauri;
    anchor.target = "_new";
    anchor.innerText = 'download screen.jpg';
    anchor.addEventListener('click', e => {e.stopPropagation();anchor.remove();}, { capture: true });
    document.body.appendChild(anchor);
    Object.assign( anchor.style, {
      position: 'fixed',
      background:'white',
      fontSize: '18px',
      fontFamily: 'monospace',
      color: 'blue',
      top: 0,
      left: 0,
      zIndex: Number.MAX_SAFE_INTEGER
    });
  }
  img.src = buildSvgImageUrl(data);  
  img.style.position = "absolute";
  img.style.zIndex = "10000000";
  img.style.backgroundColor = "white";
  //document.body.appendChild(img);

  function buildSvgImageUrl(svg) {
    const b64 = btoa(unescape(encodeURIComponent(svg)));
    return "data:image/svg+xml;base64," + b64;
  }

  function stripComments(docNode){
    const commentWalker = docNode.evaluate('//comment()', docNode, null, XPathResult.ANY_TYPE, null);
    let comment = commentWalker.iterateNext();
    const cuts = [];

    while (comment) {
      cuts.Push(comment);
      comment = commentWalker.iterateNext();
    }
    cuts.forEach( node => node.remove());
  }
}());

script de serveur proxy runkit:

const request = require("request");
const rp = require('request-promise');
const {URL} = require('url');
const express = require("@runkit/runkit/express-endpoint/1.0.0");
const b64 = require('base-64');
const bodyParser = require('body-parser');
const page = (url,err) => `
        <form method=POST style="
            position: fixed;
            position: sticky;
            display: table;
            top: 0px;
            z-index:12000000;
            background: white;">
            <label for=hider99>X</label><input id=hider99 type=checkbox>
            <style>
                #hider99:checked ~ fieldset {
                    display: none;
                }
            </style>
            <fieldset><legend>Proxy</legend>
            <p>
                <input required type=url size=62 name=url placeholder="any url" value="${url||'https://google.com/favicon.ico'}">
                <button style=background:Lime>Load</button>
                ${ !! err ? `<p><span style=color:red>${err}</span>` : '' }           
            </fieldset>
        </form>`;
const app = express(module.exports);
app.use(bodyParser.urlencoded({ extended: false }));

app.get("/", async (req,res,next) => {
    console.log(req.query.url);
    let url;
    res.type('html');
    res.set('access-control-allow-Origin', '*');
    try {
        url = b64.decode(req.query.url);
        new URL(url);
        } catch(e) { res.end(page('',"not a url"+e)); return; }
    try {
        res.type(type(url));
        const data = await rp(url);
        res.end(data);
        } catch(e) { res.end(page('',""+e)); }
});

app.get("/:anything", async (req,res,next) => {
    res.type('html');
    res.end('404 Not found');
});

function type(s = '') {
    return s.split(/\./g).pop() || 'html';
}
void 0;
0
Cris