2

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.

mpromonet
  • 11,326
  • 43
  • 62
  • 91
Mourad Over Flow
  • 191
  • 1
  • 5
  • 16
  • Why not apply the transformations just to the rectangle element instead of the whole SVG? – Sean Nov 04 '22 at 14:45
  • @Sean The rect is just for the matter of example in my real case I have an SVG with couple paths, but anyways can you apply a perspective(30em) on an SVG rect ? I could not do that . – Mourad Over Flow Nov 04 '22 at 15:41
  • You should be able to. If not with CSS, then with native SVG transforms. And you can apply it to a group as well (``) – Sean Nov 04 '22 at 16:18
  • @Sean, Does it change something If you apply it only on rect ? – Mourad Over Flow Nov 04 '22 at 17:26
  • Are you able to change the SVG structure ? e.g. add a `g` element or move elements around? – wasserholz Nov 04 '22 at 18:59
  • Yes if it is a solution for making the icon straight on the plan I can figure out to add g element (or anything actually). Do you have a solution like this ? – Mourad Over Flow Nov 04 '22 at 19:33

2 Answers2

5

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:

  • SVG has no third dimension ("z-axis"), so dimensional transforms or perspective are not possible there, unless you do all the maths yourself. It hasn't even z-index and layers are just denoted by the source (DOM) order of appearance. SVG in its nature really is just a "flat" two dimensional vector graphic.
  • SVG maps many of CSS style properties to its presentational attributes, but since in SVG there is no rotate3d, translate3d, rotatex() etc, they have no effect.
  • CSS in HTML has those.
  • In your snippet you are applying CSS transform to a HTML node that is by definition just a rectangle on a flat plane (like everything in the page). That node in your case is a SVG element, but it could be a <div> with background image or just HTML <img> - in all cases those are just "viewport" rectangles showing (SVG) content within infinite (possibly transformed) plane.
  • So you cannot "pop something out of it"; you can only manipulate "planes" constituted by HTML elements.

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

  • SVG is now without icons and icons are separate <img>.
  • Each <img> is positioned and transformed to corresponding location on the map. Custom properties here help with reusability, but static values could be hardcoded as well.
  • Custom property --slope and calc also ease "automation" and allows alterations initiated from the range input.

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

myf
  • 9,874
  • 2
  • 37
  • 49
  • Nice answer, I wonder why using `position: fixed` in scene instead of `position: relative` that allow scrolling. – mpromonet Nov 19 '22 at 15:12
  • 1
    @mpromonet good point, no particular reason for fixed position, just habitually used in my doodling sandbox where I usually do not want scrollbars at all, and copied that here. Changed to relative as suggested, thanks for heads-up. – myf Nov 23 '22 at 21:03
0

It may be possible to achive that picture just by cancelling the rotateX to let the icon (which is put on the separate overlay svg) face to the viewpoint under the same perspective projection.

For example:

<div>
  <svg id="svgPlan" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" x="0px" y="0px" viewBox="0 0 2118.5 624.2" enable-background="new 0 0 2118.5 624.2" preserveAspectRatio="none" style="position: absolute;  transform: perspective(30em) rotateX(30deg) scale(1.17) scaleZ(1.17);">
    <rect width="1200" height="400" style="fill:rgb(224,224,224);stroke-width:3;stroke:rgb(0,0,0)" />
    <rect width="610" height="160" y="240" fill="none" stroke="blue"/>
  </svg>

  <svg id="svgIcon" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" viewBox="0 0 2118.5 624.2" preserveAspectRatio="none" 
    style="position: absolute; 
       transform: perspective(30em) rotateX(30deg) scale3d(1.17, 1.17, 1.17) 
          translate3d(calc(100% * 610 / 2118.5 - 50%), calc(100% * 240 / 624.2 - 50%), 0) 
          rotateX(-30deg) 
          translate3d(calc(-100% * 610 / 2118.5 + 50%), calc(-100% * 240 / 624.2 + 50%),0)">
      <image x="550" y="120" width="120" height="120" xlink:href="https://i.stack.imgur.com/KZVZ0.png" preserveAspectRatio="xMidYMin slice"></image>
  </svg>
</div>

Scaling effect on the icon due to perspective projection is to remain though.

Brief explanations of transforms applied to the icon

  • perspective(30em) rotateX(30deg) scale3d(1.17, 1.17, 1.17) : A copy of transform applied to svgPlan

  • translate3d(calc(100% * 610 / 2118.5 - 50%), calc(100% * 240 / 624.2 - 50%), 0) : Translation from css transform-origin(50%, 50%) back to the original position(610,240)

  • rotateX(-30deg) : Transform to cancel the rotation

  • translate3d(calc(-100% * 610 / 2118.5 + 50%), calc(-100% * 240 / 624.2 + 50%),0) : Translation from the bottom-center of the icon(610, 240) to css transform-origin(50%, 50%)

ardget
  • 2,561
  • 1
  • 5
  • 4