htmlsvginheritancetransformperspective

How to inverse perspective Transformation on SVG


Let's say I have a div in which there are two SVG elements : svgPlan and svgIcon (which is an SVG Image element).

svgPlan:

SVG Plan

svgIcon:

SVG Icon

A set of transformations (perspective, rotateX, scale, and translate) are applied on the parent element (svgPlan) :

svg.style('transform', 'perspective(30em) rotateX(33deg) scale(1.7) translate(0%, -6%)');

svgPlan after transformation:

SVG Plan after transformation

I want to display the svgIcon inside the svgPlan.

THE PROBLEM : the transformations are applied on both the parent element svgPlan and the child svgIcon. It seems that the child is automaticlly inherting the styles applied on the parent and this is not what I want. I want the icon to appear whitout any effect.

What I have

THE QUESTION : How can I Disinherit my child element from father's style (which I beleive is not possible on SVG at the moment), or apply an Inverse Transformation to make the Icon appear without any perspective or style ?

What I want

THE MINIMAL REPRODUCTIBLE EXAMPLE :

    <div>
      <svg id="svgPlan" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 2118.5 624.2" enable-background="new 0 0 2118.5 624.2" xml:space="preserve"
    style="transform:perspective(30em) rotateX(30deg) scale(1.17)">
         <rect width="1200" height="400" style="fill:rgb(224,224,224);stroke-width:3;stroke:rgb(0,0,0)" />
         <g id="svgIcon">
              <image x="550" y="120" width="120" height="120" xlink:href="https://svgshare.com/i/npz.svg">
              </image>
         </g>
      </svg>
    </div>

Thank you for your help.


Solution

  • This answer proposes solution that is not very related to SVG, instead it uses more general approach where perspective transforms from CSS are applied exclusively to HTML elements - some of those may be or may contain SVG images.

    First some trivia:

    As I understand it, your use-case is to display marker icons like "sprites" that always face a camera above some tilted image (say a "map"). From your questions is not clear whether distant icons (those farther from the camera) should be smaller and overlapped by closer ones (what feels more natural, but contradicts that "un-inheriting" stance) or be same size each, with undefined overlapping strategy. Let's assume the former.

    In "HTML" world you can nest transformed elements and let the children move with parent (with help of transform-style: preserve-3d;) to get them to desired place, and then apply negated transform(s) to their children to make those camera-facing again.

    Let's start with baseline pure SVG flat image - "map" overview with two "markers" without transforms:

    <svg xmlns='http://www.w3.org/2000/svg' viewBox="0 0 800 400"
     stroke-width="10" stroke="black" fill="none">
     <g transform="translate(100 100)">
      <rect width="500" height="200" fill="silver"></rect>
      <path d="M 0 200 L 250 100 L 310 30 L 500 200" stroke="white"></path>
      <g transform="translate(250 100)">
       <circle r="20" fill="darkturquoise"></circle>
       <image width="80" height="100" transform="translate(-40 -100)" preserveAspectRatio="none" href="data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-200 -200 400 516' stroke='black' stroke-width='20'%3E%3Cpath fill='red' d='M187-14c-15-232-359-232-374 0l-1 23c0 131 77 244 188 297A329 329 0 0 0 187-14z'/%3E%3Ccircle fill='darkturquoise' r='127'/%3E%3Ctext font-size='150' text-anchor='middle' stroke='none' %3Ea%3C/text%3E%3C/svg%3E"
       ></image>
      </g>
      <g transform="translate(310 30)">
       <circle r="20" fill="red"></circle>
       <image width="80" height="100" transform="translate(-40 -100)" preserveAspectRatio="none" href="data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-200 -200 400 516' stroke='black' stroke-width='20'%3E%3Cpath fill='darkturquoise' d='M187-14c-15-232-359-232-374 0l-1 23c0 131 77 244 188 297A329 329 0 0 0 187-14z'/%3E%3Ccircle fill='red' r='127'/%3E%3Ctext font-size='150' text-anchor='middle' stroke='none' %3Eb%3C/text%3E%3C/svg%3E"
       ></image>
      </g>
     </g>
    </svg>

    (I've extracted image icons to simplified dataURIs and changed positioning to group transforms for better reusability and independence.)

    Now let's introduce the slope and icon "sprites".

    .scene {
     position: relative;
     width: 500px;
     height: 200px;
     margin: 50px 10px;
     transform-origin: center center;
     transform-style: preserve-3d; /* this is important */
     --rotx-positive: calc( var(--slope, 30) * 1deg );
     --rotx-negative: calc( var(--rotx-positive) * -1 );
     transform:
      perspective(5em)
      /* slope: */
      rotateX(var(--rotx-positive));
    }
    img {
     position: absolute;
     top: calc(1px * var(--y));
     left: calc(1px * var(--x));
     transform:
      /* to have the bottom center peak touching the POI: */
      translate(-50%, -100%)
      /* negate the slope: */
      rotatex( var(--rotx-negative) );
     transform-origin: bottom center;
    }
    /*
     Unrelated hotfix: when the scene is tilted perpendicularly to camera (slope is 90° and) it is invisible, but *obstructs* whole viewport (or something), so there is no way to return back.
     Presumably because the camera happents to be "inside" it (?)  - its bottom edge is behind the camera.
    */
    .scene {
     pointer-events: none; 
    }
    Slope: 
    <input type="range" value="30" min="0" max="90" value="0"
     oninput="s.style.setProperty('--slope', this.value);o.value=this.value">
     <output id="o">30</output>°.
    
    <div class="scene" id="s">
     <svg xmlns='http://www.w3.org/2000/svg' viewBox="0 0 500 200"
      stroke-width="10" stroke="black" fill="none">
      <rect width="500" height="200" fill="silver"></rect>
      <path d="M 0 200 L 250 100 L 310 30 L 500 200" stroke="white"></path>
      <g transform="translate(250 100)">
       <circle r="20" fill="darkturquoise"></circle>
      </g>
      <g transform="translate(310 30)">
       <circle r="20" fill="red"></circle>
      </g>
     </svg>
     <img style="--x: 250; --y: 100;" width="80" height="100" src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-200 -200 400 516' stroke='black' stroke-width='20'%3E%3Cpath fill='red' d='M187-14c-15-232-359-232-374 0l-1 23c0 131 77 244 188 297A329 329 0 0 0 187-14z'/%3E%3Ccircle fill='darkturquoise' r='127'/%3E%3Ctext font-size='150' text-anchor='middle' stroke='none' %3Ea%3C/text%3E%3C/svg%3E">
     <img style="--x: 310; --y: 30;" width="80" height="100" src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-200 -200 400 516' stroke='black' stroke-width='20'%3E%3Cpath fill='darkturquoise' d='M187-14c-15-232-359-232-374 0l-1 23c0 131 77 244 188 297A329 329 0 0 0 187-14z'/%3E%3Ccircle fill='red' r='127'/%3E%3Ctext font-size='150' text-anchor='middle' stroke='none' %3Eb%3C/text%3E%3C/svg%3E">
    </div>

    Should render something like:

    For single 3D transform this fits; for multiple simultaneous transforms either more maths should be involved or more nested wrappers for transformations. See example of nested transform layers producing sprite-like "dots" in 3d space in pen using this technique.