SVG foreignObject内容不显示,除非是纯文本

时间:2022-11-20 17:50:45

I am trying to output HTML using the foreignObject tag inside an SVG drawing. I am using d3 to generate the elements. The only time the HTML content inside the foreignObject tag shows up is when the content inside the foreignObect tag is plain text, otherwise it just shows up as empty/blank. Please see this jsfiddle for an example of my problem: http://jsfiddle.net/R9e3Y/29/

我正在尝试使用SVG绘图中的foreignObject标记输出HTML。我正在使用d3生成元素。foreignObject标签内的HTML内容出现的唯一时间是当foreignObect标签内的内容是纯文本时,否则它只显示为空/空。请参见这个jsfiddle以了解我的问题:http://jsfiddle.net/R9e3Y/29/

The contents inside the foreignObject tag show up on inspecting the element this this:

foreignObject标签中的内容在检查元素时出现如下:

<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> 

but are not visible on the screen? How do I get the content to show up?

但是在屏幕上看不到吗?如何显示内容?

5 个解决方案

#1


48  

Since you're using d3 you need to tell d3 that the div is a html div and not some element in the svg namespace. Try

因为您使用的是d3,所以需要告诉d3, div是一个html div,而不是svg名称空间中的某个元素。试一试

.append("xhtml:div")

#2


11  

<foreignObject> allows embedding all kinds of markup, not just HTML. This means, there must be a way of determining what language is used. That's where namespaces come into play.

允许嵌入各种标记,而不仅仅是HTML。这意味着,必须有一种方法来确定使用什么语言。这就是名称空间发挥作用的地方。

To tell SVG what kind of foreignObject you have, you have to put the content in the proper namespace.

要告诉SVG您有什么类型的foreignObject,您必须将内容放在适当的名称空间中。

<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> 

In your example, the <div> element is in the SVG namespace, i.e. it's an SVG element, not an HTML one (albeit a non-standard one).

在您的示例中,

元素位于SVG名称空间中,即它是SVG元素,而不是HTML元素(尽管不是标准的)。

The <foreignObject> element also has a requiredExtensions attribute to tell the browser which extension is used, however different browsers seem to be interpreting this attribute differently, so it's probably best to not set it.

元素也有一个requiredextense属性来告诉浏览器使用的是哪个扩展,尽管不同的浏览器对这个属性的解释不同,所以最好不要设置它。

#3


2  

Make sure you set width and height parameters.

确保设置了宽度和高度参数。

Adding xmlns did not fix things for me. It turned out the problem for me was even more simple... I did not add width and height parameters, so content inside the foreignObject did not show, even though it was there. Both parameters seem to default to 0.

添加xmlns并不能修复问题。结果对我来说,问题更简单了……我没有添加宽度和高度参数,所以foreignObject中的内容没有显示出来,即使它在那里。两个参数都默认为0。

#4


0  

I developed something for this. The code is below. A more advanced version uses a proxy to pull in external non-CORS resources. svg foreignObject blocks loading of any non-CORS cross-origin request. I wrote a simple proxy to run on Runkit. See at the bottom.

我为此开发了一些东西。下面的代码。更高级的版本使用代理来获取外部非cors资源。svg foreignObject阻止装入任何非cors的跨源请求。我编写了一个在Runkit上运行的简单代理。看到底部。

The limitations of this are: no external non-CORS fonts, no non-CORS images. Anyone who wants to help improve this, including adding in support for images and fonts, can contribute here: https://github.com/dosyago-coder-0/dompeg.js/blob/master/dompeg.js

它的局限性是:没有外部非cors字体,没有非cors图像。任何想要帮助改进这一点的人,包括添加对图像和字体的支持,都可以在这里提供帮助:https://github.com/dosyago-coder-0/dompeg.js/blob/master/dompeg.js

web page script:

网页脚本:

(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());
  }
}());

runkit proxy server script:

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;

#5


-2  

var a = function(a) {
            var b = enter code heredocument.createElementNS("http://www.w3.org/1999/xhtml", "div");
            b.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg">' + a + "</svg>";
            for (var c = document.createDocumentFragment(); b.firstChild.firstChild; ){
                c.appendChild(b.firstChild.firstChild);
            } 
            return c;
        }

        $('#app_canvasContainer svg').append(a('<foreignObject overflow="visible" width="200" height="100" x="'+ x +'"  y="'+ y +'" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><textarea class="text-entity" readonly="true" resizeable=false xmlns="http://www.w3.org/1999/xhtml" >'+ val +'</textarea>`enter code here`</foreignObject>'));

#1


48  

Since you're using d3 you need to tell d3 that the div is a html div and not some element in the svg namespace. Try

因为您使用的是d3,所以需要告诉d3, div是一个html div,而不是svg名称空间中的某个元素。试一试

.append("xhtml:div")

#2


11  

<foreignObject> allows embedding all kinds of markup, not just HTML. This means, there must be a way of determining what language is used. That's where namespaces come into play.

允许嵌入各种标记,而不仅仅是HTML。这意味着,必须有一种方法来确定使用什么语言。这就是名称空间发挥作用的地方。

To tell SVG what kind of foreignObject you have, you have to put the content in the proper namespace.

要告诉SVG您有什么类型的foreignObject,您必须将内容放在适当的名称空间中。

<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> 

In your example, the <div> element is in the SVG namespace, i.e. it's an SVG element, not an HTML one (albeit a non-standard one).

在您的示例中,

元素位于SVG名称空间中,即它是SVG元素,而不是HTML元素(尽管不是标准的)。

The <foreignObject> element also has a requiredExtensions attribute to tell the browser which extension is used, however different browsers seem to be interpreting this attribute differently, so it's probably best to not set it.

元素也有一个requiredextense属性来告诉浏览器使用的是哪个扩展,尽管不同的浏览器对这个属性的解释不同,所以最好不要设置它。

#3


2  

Make sure you set width and height parameters.

确保设置了宽度和高度参数。

Adding xmlns did not fix things for me. It turned out the problem for me was even more simple... I did not add width and height parameters, so content inside the foreignObject did not show, even though it was there. Both parameters seem to default to 0.

添加xmlns并不能修复问题。结果对我来说,问题更简单了……我没有添加宽度和高度参数,所以foreignObject中的内容没有显示出来,即使它在那里。两个参数都默认为0。

#4


0  

I developed something for this. The code is below. A more advanced version uses a proxy to pull in external non-CORS resources. svg foreignObject blocks loading of any non-CORS cross-origin request. I wrote a simple proxy to run on Runkit. See at the bottom.

我为此开发了一些东西。下面的代码。更高级的版本使用代理来获取外部非cors资源。svg foreignObject阻止装入任何非cors的跨源请求。我编写了一个在Runkit上运行的简单代理。看到底部。

The limitations of this are: no external non-CORS fonts, no non-CORS images. Anyone who wants to help improve this, including adding in support for images and fonts, can contribute here: https://github.com/dosyago-coder-0/dompeg.js/blob/master/dompeg.js

它的局限性是:没有外部非cors字体,没有非cors图像。任何想要帮助改进这一点的人,包括添加对图像和字体的支持,都可以在这里提供帮助:https://github.com/dosyago-coder-0/dompeg.js/blob/master/dompeg.js

web page script:

网页脚本:

(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());
  }
}());

runkit proxy server script:

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;

#5


-2  

var a = function(a) {
            var b = enter code heredocument.createElementNS("http://www.w3.org/1999/xhtml", "div");
            b.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg">' + a + "</svg>";
            for (var c = document.createDocumentFragment(); b.firstChild.firstChild; ){
                c.appendChild(b.firstChild.firstChild);
            } 
            return c;
        }

        $('#app_canvasContainer svg').append(a('<foreignObject overflow="visible" width="200" height="100" x="'+ x +'"  y="'+ y +'" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><textarea class="text-entity" readonly="true" resizeable=false xmlns="http://www.w3.org/1999/xhtml" >'+ val +'</textarea>`enter code here`</foreignObject>'));