Let's say I have a div in which there are two SVG elements : svgPlan and svgIcon (which is an SVG Image element).
svgPlan:
svgIcon:
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:
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.
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 ?
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.
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:
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.rotate3d
, translate3d
, rotatex()
etc, they have no effect.<div>
with background image or just HTML <img>
- in all cases those are just "viewport" rectangles showing (SVG) content within infinite (possibly transformed) plane.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".
<img>
.<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.--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>
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.