Search code examples
csssvgcss-transforms

CSS3 transform: matrix3d gives other results in Chrome, Edge & Safari vs Firefox


I am trying to place 2 images on top of each other in an SVG using the css transform matrix3d. In Firefox this looks fine, but in Chrome, Safari & Edge I get a different result.

Anyone has got any idea what I am doing wrong or if there are some sort of limitations?

Relevant code:

<svg width="1920" height="1080" class="picture">
    <image xlink:href="/images/psv/screenreplace/exposure-led-wall.jpg" width="1920" height="1080"></image>
    <image id="screen-image-0" xlink:href="https://youreka-virtualtours.be/screenreplacer/362/1/logos/wallpaper4-1670251089552.jpg" width="640" height="252"></image>
    <defs>
        <style>
            #screen-image-0 {
                transform:matrix3d(1.52966, 0.704576, 0, 0.00068864, -0.0772169, 1.48607, 0, -0.00014322, 0, 0, 1, 0, 910, 299, 0, 1);
            } 
        </style>
    </defs>
</svg>

The result in Firefox: Firefox

The result in the other browsers: Chrome etc


Solution

  • The issue is that Safari and Chrome do convert your 3D matrix to a 2D one.
    It's not entirely clear who is right here.

    Some CSS attributes must map to SVG presentation attributes. transform is one of these, so it's normal you can set an SVG element's transform through CSS. However, the SVG transform attribute is far from being clear. Current specs state

    8.5. The ‘transform’ property

    User agents must support the transform property and presentation attribute as defined in [css-transforms-1].

    And when we follow this [css-transforms-1] we end on two links, both stating

    This specification is the convergence of the CSS 2D Transforms and SVG transforms specifications.

    Only this latter document does talk about 3D transforms. But the "convergence" documents, that do actually rule, completely ignore it.

    Even the DOM interfaces that are supposed to reflect the current transformation matrix applied on the element are defined to be 2D only.

    So what should happen when we apply a 3D matrix to an SVG element?

    Interop issues apparently.

    Chrome and Safari behavior kind of makes sense, they "filter" the matrix component to be only 2D just like what would happen if you were to modify the element's .transform.baseVal SVGTransformList. I.e they do map 1-1 CSS and presentation attributes. Firefox on the other hand does what one would probably want to happen, i.e they do respect the authored CSS rule.
    Once again, I'm not entirely sure which is correct.

    const button = document.querySelector("button");
    const el = document.querySelector(".trans");
    const list = el.transform.baseVal;
    const matrix = new DOMMatrix("matrix3d(1.5, 0.7, 0, 0.0007, -0.07, 1.48, 0, -0.00014, 0, 0, 1, 0, -800, 0, 0, 1)");
    let newTransform;
    try {
      newTransform = list.createSVGTransformFromMatrix(matrix);
    }
    catch(err) {
      // Chrome doesn't support createSVGTransformFromMatrix(DOMMatrix)...
      const { a, b, c, d, e, f } = matrix;
      const mat = document.querySelector("svg").createSVGMatrix();
      Object.assign(mat, { a, b, c, d, e, f });
      newTransform = list.createSVGTransformFromMatrix(mat);
    }
    button.onclick = (evt) => {
      el.classList.remove("trans");
      list.clear();
      list.appendItem(newTransform); // Won't change anything in Chrome and Safari
                                     // Will make Firefox behave like the others
    };
    svg { height: 500px }
    .trans {
      transform: matrix3d(1.5, 0.7, 0, 0.0007, -0.07, 1.48, 0, -0.00014, 0, 0, 1, 0, -800, 0, 0, 1);
    }
    <button>apply matrix</button><br>
    <svg width="1920" height="1080" viewBox="0 0 1900 1200">
      <rect class="trans" width="640" height="252"/>
    </svg>

    Note that both Safari and Chrome do support 3D transforms on the root <svg> element, so you might be able to achieve the same result by using an other <svg> just to render that transformed image, but that enters the hack area and might not be trivial to get it right.