SVG ForeignObjects uses all other elements in Chrome

Update April 22, 2016: Nothing new, I just decided to check it out and post a quick code update. Firefox still works as expected, IE doesn't work at all, and Chrome 50.0.2661.87 m still behaves the same as last year. The fiddle link and code below have been updated to reflect the latest working version (in Firefox).

Background: I play with rendering canvas + HTML elements in PNG. Of course, this means creating an intermediate SVG that places HTML as a foreignObject.

All this is a layer-cake of elements. I have a background, an element layer, a canvas, and another element layer. You can see how it looks in the snippet below.

I could approach this in two ways:

  • Write everything, including the image, in one SVG, which will be displayed on the canvas.

  • Write two SVGs, one for the background and the elements behind the image, and the other for the elements in front of the image, then draw the inverse SVG, then the image, and then the front SVG on the target canvas.

I chose option 1 because it seemed simple and simple.

Problem: It is assumed that the SVG output order should follow the DOM order, but in the case of Chrome (38 and Canary), it acts as if it renders foreignObjects after it displays its own objects, completely covering the native objects. (The code works as expected in Firefox, and with an error in IE11). So who is right? Is this a bug in Chrome, Firefox, or none of them handle this correctly? Or is there some user error that I missed?

Thank!

function putAnImageInTheCanvas() {
  var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  var svgNS = svg.namespaceURI;
  svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  svg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
  svg.setAttribute('height', '310');
  svg.setAttribute('width', '310');
  svg.setAttribute('version', '1.1');

  var svgRect = document.createElementNS(svgNS, 'rect');
  svgRect.setAttribute('x', '125');
  svgRect.setAttribute('y', '25');
  svgRect.setAttribute('height', '250');
  svgRect.setAttribute('width', '50');
  svgRect.setAttribute('fill', 'rgb(0,255,255)');

  svg.appendChild(svgRect);

  var dataSrc = 'data:image/svg+xml;base64,' + btoa(svg.outerHTML);

  var img = document.createElement('img');
  img.setAttribute('src', dataSrc);

  var c = document.getElementById('myCanvas');
  var ctx = c.getContext("2d");
  img.addEventListener('load', function() {
    ctx.drawImage(img, 0, 0, 310, 310, 0, 0, 310, 310);
  });
}

function render(darwBackground) {
  var myCanvas = document.getElementById('myCanvas');

  var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  var svgNS = svg.namespaceURI;
  svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  svg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
  svg.setAttribute('height', '310');
  svg.setAttribute('width', '310');
  svg.setAttribute('version', '1.1');

  var background = document.getElementById('main').cloneNode();
  background.setAttribute("xmlns", document.documentElement.namespaceURI);
  var svgFO_BG = document.createElementNS(svgNS, 'foreignObject');
  svgFO_BG.setAttribute('height', '310');
  svgFO_BG.setAttribute('width', '310');
  svgFO_BG.setAttribute('x', '0');
  svgFO_BG.setAttribute('y', '0');
  svgFO_BG.innerHTML = background.outerHTML.replace(/(\r\n|\n|\r|\t|[\s]{2,})/gm, '');

  var back = document.getElementById('back').cloneNode(true);
  back.setAttribute("xmlns", document.documentElement.namespaceURI);
  var svgFO_AB = document.createElementNS(svgNS, 'foreignObject');
  svgFO_AB.setAttribute('height', '310');
  svgFO_AB.setAttribute('width', '310');
  svgFO_AB.setAttribute('x', '0');
  svgFO_AB.setAttribute('y', '0');
  svgFO_AB.innerHTML = back.outerHTML.replace(/(\r\n|\n|\r|\t|[\s]{2,})/gm, '');

  var front = document.getElementById('front').cloneNode(true);
  front.setAttribute("xmlns", document.documentElement.namespaceURI);
  var svgFO_AA = document.createElementNS(svgNS, 'foreignObject');
  svgFO_AA.setAttribute('height', '310');
  svgFO_AA.setAttribute('width', '310');
  svgFO_AA.setAttribute('x', '0');
  svgFO_AA.setAttribute('y', '0');
  svgFO_AA.innerHTML = front.outerHTML.replace(/(\r\n|\n|\r|\t|[\s]{2,})/gm, '');

  var svgImage = document.createElementNS(svgNS, 'image');
  svgImage.setAttribute('xlink:href', myCanvas.toDataURL());
  svgImage.setAttribute('x', '0');
  svgImage.setAttribute('y', '0');
  svgImage.setAttribute('height', '310');
  svgImage.setAttribute('width', '310');

  var g = document.createElementNS(svgNS, 'g');
  if (darwBackground) {
    g.appendChild(svgFO_BG);
    svg.appendChild(g);
  }

  g = document.createElementNS(svgNS, 'g');
  g.appendChild(svgFO_AB);
  svg.appendChild(g);

  g = document.createElementNS(svgNS, 'g');
  g.appendChild(svgImage);
  svg.appendChild(g);

  g = document.createElementNS(svgNS, 'g');
  g.appendChild(svgFO_AA);
  svg.appendChild(g);

  var data = svg.outerHTML;

  document.getElementById('renderOutput').innerHTML = data;
}
<input type="button" value="load canvas image" onclick="putAnImageInTheCanvas();" />
<input type="button" value="render with background" onclick="render(true);" />
<input type="button" value="render without background" onclick="render(false);" />
<h2>Preview:</h2>
<div id="main" style="border: 5px blue solid; width: 300px; height: 300px; background: yellow; position: relative; top: 0; left: 0; overflow: hidden;">
  <canvas id="myCanvas" height="300px" width="300px" style="display: block; position: absolute; top: 0; left: 0; z-index: 1;"></canvas>
  <div id="back" style="position: relative; top: 0; left: 0;">
    <div style=" width: 100px; position: absolute; top: 75px; left: 75px; font-size: 20px; font-family: times; z-index: 0;">
      <div style="background: orange;">BACK</div>
    </div>
  </div>
  <div id="front" style="position: relative; top: 0; left: 0;">
    <div style=" width: 100px; position: absolute; top: 150px; left: 150px; font-size: 20px; font-family: times; z-index: 2;">
      <div style="background: lime;">FRONT</div>
    </div>
  </div>
</div>
<h2>Render Result:</h2>
<div id="renderOutput">

</div>
Run codeHide result
+4
source share

All Articles