Safari is not respecting scaling applied to a foreignObject

Viewed 2108

The following is a strange, and clearly buggy behavior of Safari (tested with versions 11 & 12). A <foreignObject> containing HTML, when scaled, still displays at its original size, even when its local userspace coordinates are scaled against the screen. The HTML content will visibly overflow the parent <svg>, even against explicit CSS rules.

Other answers around here point out that the width and height need to be set explicitly (I tested with both percentages and absolute units), and the namespace should be set (I tested with setting it on the <foreignObject> tag itself, and on a single immediate child), but nothing so far has helped.

The strange thing is that dev tools display the marking rectange (the overlay in the browser window) at its intended, scaled size, while the reported numbers for the size are the unscaled ones.

Here is the intended setup:

svg, foreignObject {
  overflow: hidden;
}
rect {
  fill:yellow;
}
#content {
  position: relative;
  width: 100%;
  height: 100%;
  background: red;
  border-radius: 50%;
}
<svg width="200px" height="200px" viewBox="0 0 400 400">
  <rect width="100%" height="100%" />
  <foreignObject width="400" height="400">
    <div id="content" xmlns="http://www.w3.org/1999/xhtml"></div>
  </foreignObject>
</svg>

Safari screenshot:

enter image description here

Using a transform attribute (also on a parent <g>) instead of implicit scaling via viewBox makes no difference. Also, I have played around with all combinations of absolute and relative sizings

Does anyone have an idea how to get around this issue?

2 Answers

This seems to be related to webkit bug 23113. The only workaround I've found so far is adding a CSS transform: scale(${scale}) (where you'd have to get the current scale using JS) property to a <section> inside foreignObject (an example can be found in marpit-svg-polyfill)

I couldn't think of any decent solution either, so I came up with a little hacky approach - you need to place the svg into the container, then get the width of the container and width of the svg, scale accordingly and position it properly:

if (window['safari'] !== undefined) {
    const svgContainerElement = document.getElementById('svgContainer');
    const svgElement = document.getElementById('svg');

    const zoomLevel = svgContainerElement.clientWidth / 867; // width of the foreign object
    svgElement.style.transform = `scale(${zoomLevel})`;

    const leftOffset = svgContainerElement.getBoundingClientRect().left - 
    svgElement.getBoundingClientRect().left;
    const topOffset = svgContainerElement.getBoundingClientRect().top - 
svgElement.getBoundingClientRect().top;

    svgElement.style.position = `relative`;
    svgElement.style.left = leftOffset;
    svgElement.style.top = topOffset;
}

you may be forced to re-draw the svg if the browser doesn't react to the style changes properly:

const parent = svgElement.parentElement;
if (parent) {
    parent.removeChild(el);
    parent.appendChild(el);
}

It may be like a few years late to this question, but I hope it will help somebody since this is a problem in safari to this date (November 2019)

Related