Reuse set of many objects but now assign each object a separate opacity, without copying all code? - css

Let's imagine we have a large set of many objects, say rectangles. They all have opacity 1.
Now imagine that we want to copy all of those objects again to create a new set, but this time each object would need to be assigned a separate opacity.
One can create many such sets, and each time all of the individual objects would separately be assigned a certain opacity.
In cross-browser compatible SVG1.1 (optionally with SMIL/CSS), is there a way to do this, without needing to redraw all of the shapes and their alignments all over again (redrawing would make the code sort of long), for example using set?

The pattern you are looking for goes like this:
<svg width="300" height="150"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect id="template" x="20" y="20" width="80" height="80" />
<use xlink:href="#template" x="100" style="opacity:0.6" fill="blue" />
<use xlink:href="#template" x="200" style="opacity:0.3" fill="green" />
</svg>
The <use> elements reuse the template element. The <rect> is now in the so-called "shadow DOM" below that use element. While it is treated as if it was a child, it cannot be targeted by CSS style rules. But it can inherit styles (or presentation attributes) from the parent <use> element.
Note that the template element does not set an opacity. As a default, it is rendered with opacity="1". Now if the <use> element sets another opacity, it can be applied to the cloned <rect> by inheritance.
If the template had an explicit opacity="1", the cloned <rect> would also get that. That style would have a higher specificity than the inherited one, and the rectangle would stay fully opaque.
Building on this answer, if you want to clone sets of elements at once but give each of them individual opacity values, CSS variables can be leveraged. Note that presentation attributes no longer work. The most concise way to write this is a stylesheet that turns out to be a list of the property values to use.
.stand {
--red: 1;
--amber: 0.3;
--green: 0.3;
}
.wait {
--red: 1;
--amber: 1;
--green: 0.3;
}
.go {
--red: 0.3;
--amber: 0.3;
--green: 1;
}
.stop {
--red: 0.3;
--amber: 1;
--green: 0.3;
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="200">
<symbol id="trafficlight">
<rect x="20" y="20" width="60" height="160" />
<circle cx="50" cy="50" r="20" fill="#d00" style="opacity:var(--red, 1)" />
<circle cx="50" cy="100" r="20" fill="#fa0" style="opacity:var(--amber, 1)" />
<circle cx="50" cy="150" r="20" fill="#0b0" style="opacity:var(--green, 1)" />
</symbol>
<use x="0" xlink:href="#trafficlight" class="stand" />
<use x="100" xlink:href="#trafficlight" class="wait" />
<use x="200" xlink:href="#trafficlight" class="go" />
<use x="300" xlink:href="#trafficlight" class="stop" />
</svg>

Related

Clear an already set "fill" property in CSS to get defaults back for an SVG

I'm using a platform that within it's own CSS it sets the fill property as so:
.tatsu-svg-icon-custom svg * {
fill: currentColor;
}
This ends up with an SVG I am adding being black in this instance - which is not helpful. This particular SVG is a multi-colored SVG and handles all the fill properties itself within the code of the SVG.
Obviously if I change this property to aother color, it colors the whole SVG that color - so that is not helpful either.
So my question is, how do I get the defaults back so it doesn't apply any color to it? Setting it to initial makes the SVG transparent.
The keyword that would help in this situation is revert-layer. Unfortunately, it is currently only implemented in Firefox (>= 97).
(This example will seem to work even for other browsers. But that is because for them, it is an invalid keyword.)
.tatsu-svg-icon-custom svg * {
fill: revert-layer;
}
<div class="tatsu-svg-icon-custom">
<svg width="100" viewBox="0 0 20 20">
<circle r="5" cx="5" cy="5" fill="yellow" />
<circle r="5" cx="5" cy="15" fill="blue" />
<circle r="5" cx="15" cy="5" fill="red" />
<circle r="5" cx="15" cy="15" fill="green" />
</svg>
</div>

How to control `transform-box` for `<use>` elements?

Background
I’m loving the expanded CSS support in SVG2. It’s great not having to rewrite attributes over and over. So I’ve been converting some code in a project from SVG attributes to CSS. Most of this has worked just fine.
When it comes to transforms, things can seem tricky if you are comfy with how CSS transforms work in HTML. (This is especially true for rotate() transformations, which is the focus of this question.) That’s because SVG doesn’t have the “automatic flow” that HTML does.
In other words, when you have a bunch of HTML elements, one after another, they will automatically lay themselves out according to the box model.
There is no such “automatic” or “default” layout in SVG. As a result, SVG transforms default to being calculated from the origin. (That’s 0,0 in user coordinates).
The Almost-Perfect Solution
For most elements, there’s a simple solution: the awesome CSS property transform-box. In most cases, using the following CSS will allow you to transform SVG elements in pretty much the same way as HTML elements:
/* whatever elements you want to transform */
rect, polygon {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate90 {
transform: rotate(90deg);
}
Now, you can just do something like...
<rect class="rotate90" x="123" y="789" width="50" height="50" />
And it will rotate around the transform-origin specified in the CSS. Since the above example used a transform-origin of center center, it rotates in place.
That matches the behavior of HTML elements using transform: rotate(…). And—especially if there’s a lot of rotations like this in an SVG image—it is way, way better than the equivalent SVG markup.
Why is CSS better than the SVG markup equivalent?
Because SVG’s rotate() function has a slightly different syntax that does not have an equivalent to the transform-box: fill-box CSS used above, unless you specify an X and Y coordinate for every rotation.
That means you have to put in the rotation point every time, like so:
<rect x="123" y="789" width="50" height="50"
transform="rotate(90 123 789)"
></rect>
<!--
In this example, the rotation pivots around the X and Y coordinates of the `rect` element.
If you wanted to rotate around the center, you would have to calculate:
x + width/2
y + width/2
And use those values in the `rotate()` function instead.
-->
The Problem
The problem I’ve run into is that the CSS solution does not work with <use /> elements.
It’s pretty clear why: the <use /> element clones the referenced element into a new location. So, as far as the CSS is concerned, it is transforming the <use /> element, and not the cloned (Shadow-DOM) content.
When it comes to other challenges involving applying CSS to <use />—such as setting up different color schemes—there are solutions (like this one from SVG superhero Sara Soueidan).
But when it comes to getting around the issue of coordinates, I haven’t figured out a way around this.
Example Code
EDIT: To be more explicit about what I’m going for, here is some sample code.
.transform-tl,
.transform-tc,
.transform-tr,
.transform-cl,
.transform-cc,
.transform-cr,
.transform-bl,
.transform-bc,
.transform-br {
transform-box: fill-box;
}
.transform-tl { transform-origin: top left; }
.transform-tc { transform-origin: top center; }
/*
…and so on, for the other combinations of `transform-origin` keyword values…
*/
.rotate90.cw {
transform: rotate(90deg)
}
.rotate90.ccw {
transform: rotate(-90deg)
}
<!--
Using the CSS classes in the following manner works as intended for most SVG elements as intended.
But with the `<use />` element, the rotation does not pivot around the top left corner, as expected... :(
-->
<use
class="transform-tl rotate90 cw"
x ="0"
y ="1052"
href ="#block-A12-2"
></use>
(Thanks to #PaulLeBeau for the nudge to include this bit of code.)
Does anyone have a solution?
(Even a workaround solution—as long as it involves less repetition than specifying the SVG transform attribute on every <use />—would be welcome!)
Assuming the same conditions as #Sphinxxx postulated...
Then you can also just wrap your <use> element in a group (<g>) element and apply the rotate class to that.
/* whatever elements you want to transform */
rect, polygon, use, g {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate45 {
transform: rotate(45deg);
}
<svg width="300" height="200">
<defs>
<rect id="rr" x="80" y="60" width="50" height="50" />
</defs>
<use href="#rr" x="100" y="0" fill="tomato" />
<g class="rotate45">
<use href="#rr" x="100" y="0" fill="lime" />
</g>
</svg>
What's going on? Why does this work?
The reason has to do with how <use> elements are dereferenced and what happens to transforms when that happens. If you read the section about <use> elements in the SVG spec, it says:
In the generated content, the ‘use’ will be replaced by ‘g’, where all attributes from the ‘use’ element except for ‘x’, ‘y’, ‘width’, ‘height’ and ‘xlink:href’ are transferred to the generated ‘g’ element. An additional transformation translate(x,y) is appended to the end (i.e., right-side) of the ‘transform’ attribute on the generated ‘g’, where x and y represent the values of the ‘x’ and ‘y’ attributes on the ‘use’ element.
So, the following SVG (which I presume will be something like you were trying):
<use href="#rr" x="100" y="0" fill="lime" class="rotate45" />
will be dereferenced into the equivalent of:
<g fill="blue" transform="rotate(45) translate(100,0)">
<rect x="80" y="60" width="50" height="50" />
</g>
Since you can think of transforms as being applied from right to left, the translate(100,0) will be applied before the rotation, meaning that the rotation will be moving something that is offset 100 pixels away from where you want it. That's why the transform-box: fill-box is not working for <use> elements.
rect, polygon, use, g {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate45 {
transform: rotate(45deg);
}
<svg width="300" height="200">
<defs>
<rect id="rr" x="80" y="60" width="50" height="50" />
</defs>
<use href="#rr" x="100" y="0" fill="tomato" />
<use href="#rr" x="100" y="0" fill="blue" stroke="blue" class="rotate45" />
<g fill="lime" transform="rotate(45) translate(100,0)">
<rect x="80" y="60" width="50" height="50" />
</g>
</svg>
However with my solution, the <g> + <use> set...
<g class="rotate45">
<use href="#rr" x="100" y="0" fill="lime" />
</g>
will be dereferenced into the equivalent of:
<g class="rotate45">
<g fill="lime" transform="translate(100,0)">
<rect x="80" y="60" width="50" height="50" />
</g>
</g>
rect, polygon, use, g {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate45 {
transform: rotate(45deg);
}
<svg width="300" height="200">
<defs>
<rect id="rr" x="80" y="60" width="50" height="50" />
</defs>
<use href="#rr" x="100" y="0" fill="tomato" />
<g class="rotate45">
<use href="#rr" x="100" y="0" fill="blue" stroke="blue"/>
</g>
<g class="rotate45">
<g fill="lime" transform="translate(100,0)">
<rect x="80" y="60" width="50" height="50" />
</g>
</g>
</svg>
In this version, each of the two transform operations is applied to different elements, so the transform-box and transform-origin are applied separately. And the transform origin has no effect on translations.
So what that means is that the transform-box calculation for the rotation (ie on the <g>) will be applied to the already-translated object. So it will behave as you want.
In the days before transform-box, these two examples would have given the same result.
So you want to put an element somewhere with <use x="..." y="...", and then rotate it in place?
The simplest solution I have found does include the transform attribute, but you don't need to specify the rotation point. See the following example, where the green rectangle does what you want.
In CSS, we include use elements in the transform-box rule. Then we position and rotate each element with the transform attribute (replacing x, y and CSS rotation):
/* whatever elements you want to transform */
rect, polygon, use {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate45 {
transform: rotate(45deg);
}
<svg width="300" height="200">
<rect id="rr" x="80" y="60" width="50" height="50" />
<use href="#rr" x="100" y="0" class="rotate45" fill="tomato" />
<use href="#rr" transform="translate(100 0) rotate(45)" fill="lime" />
</svg>

SVG apply same transform-origin to all children of a group/symbol

I can't get reusable items or all children of a group to conform to a specified transform-origin. The goal is to be able to reuse the same shape over and over with the same template of style. However, considering transform-origin the styling, CSS or otherwise, does not cascade. It will only apply at time of <use>.
For example:
svg {
width: 125px; height: 125px;
background: rgba(0, 0, 0, 0.5);
}
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
<defs>
<ellipse id="svg-ellipse-def" cx="500" cy="500" rx="140" ry="455" transform-origin="center" style="transform-origin: center"/>
</defs>
<symbol id="svg-ellipse" >
<ellipse cx="500" cy="500" rx="140" ry="455" transform-origin="center" style="transform-origin: center"/>
</symbol>
<g fill="none" stroke="red" stroke-width="50">
<use xlink:href="#svg-ellipse"/>
<use xlink:href="#svg-ellipse" transform="rotate(45)"/>
<use xlink:href="#svg-ellipse" transform="rotate(90)"/>
<use xlink:href="#svg-ellipse-def" transform="rotate(-45)"/>
</g>
</svg>
Essentially, the transform origin doesn't apply regardless if I use class= attribute or transform-origin= property or even inline style. I've also tried wrapping it in a <defs> container.
Desired outcome:
<use xlink:href="#svg-ellipse" transform="rotate(45)"/>
<use xlink:href="#svg-ellipse" transform="rotate(90)"/>
<use xlink:href="#svg-ellipse" transform="rotate(-45)"/>
But right now it looks like this:
<use xlink:href="#svg-ellipse" transform="rotate(45)" style="transform-origin:center"/>
<use xlink:href="#svg-ellipse" transform="rotate(90)" style="transform-origin:center"/>
<use xlink:href="#svg-ellipse" transform="rotate(-45)" style="transform-origin:center"/>
svg {
width: 125px; height: 125px;
background: rgba(0,0,0,0.5);
}
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
<symbol id="svg-ellipse" >
<ellipse cx="500" cy="500" rx="140" ry="455"/>
</symbol>
<g fill="none" stroke="red" stroke-width="50">
<use xlink:href="#svg-ellipse"/>
<use xlink:href="#svg-ellipse" transform="rotate(45)" transform-origin="center"/>
<use xlink:href="#svg-ellipse" transform="rotate(90)" transform-origin="center"/>
<use xlink:href="#svg-ellipse" transform="rotate(-45)" transform-origin="center"/>
</g>
</svg>
According to the documentation:
If the ‘use’ element references a ‘symbol’ element:
In the generated content, the ‘use’ will be replaced by ‘g’, where all attributes from the ‘use’ element except for ‘x’, ‘y’, ‘width’, ‘height’ and ‘xlink:href’ are transferred to the generated ‘g’ element. An additional transformation translate(x,y) is appended to the end (i.e., right-side) of the ‘transform’ attribute on the generated ‘g’, where x and y represent the values of the ‘x’ and ‘y’ attributes on the ‘use’ element. The referenced ‘symbol’ and its contents are deep-cloned into the generated tree, with the exception that the ‘symbol’ is replaced by an ‘svg’.
Looking at the dev console, this is confirmed, and the style is applied inline but not honored:
Even if the <use> element has an inline styling of transform-origin, because the symbol now is converted to it's own SVG as a child of the <use> element, and it has it's own inline styling, shouldn't that take higher priority over it's parent's inline?
You can target the <use> element itself, but none of its content. The content can, in principle, inherit CSS properties. But the barrier you'll always run into is: neither the CSS transform nor the transform-origin property are inheritable.
If you rotate the <use> element, you rotate it around the tranform origin of the <use> element, while the contents of the shadow DOM stay in place relative to its root.
If you set a transform-origin for the <symbol> or <ellipse>, it will only be applied if you transform that itself, and the transformation will be cloned into each of its reuses.
The best solution I see is giving all use elements that reference the same symbol the same transform-origin. For the attribute selector to work, you'll need to leave off the xlink namespace. That is deprecated anyway, but you'll have to consider browser compatibility.
But then, the same is true for transform-origin support in SVG. That, by the way is the reason for
setting transform-box: fill-box. After there were some differences in implementation between Firefox and Chrome, it is now accepted that this
property is needed for SVG elements to transform them in relation to their bounding box. I've changed your example a bit to demonstrate.
svg {
width: 125px; height: 125px;
background: rgba(0, 0, 0, 0.5);
}
use[href="#symbol1"] {
transform-origin: center;
transform-box: fill-box;
}
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1500 1500">
<symbol id="symbol1" >
<ellipse cx="500" cy="500" rx="140" ry="455" />
</symbol>
<g fill="none" stroke="red" stroke-width="50">
<use href="#symbol1" transform="translate(200, 400)" />
<use href="#symbol1" transform="translate(200, 400) rotate(45)"/>
<use href="#symbol1" transform="translate(200, 400) rotate(90)"/>
<use href="#symbol1" transform="translate(200, 400) rotate(-45)"/>
</g>
</svg>

svg - keep fill texture original size when resizing a path

I define a svg <pattern> like this:
<svg height="10" width="10" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<pattern id="circles-1_4" patternUnits="userSpaceOnUse" width="10" height="10">
<image xlink:href="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIxMHB4IiBoZWlnaHQ9IjEwcHgiIHZpZXdCb3g9IjAgMCAxMCAxMCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTAgMTAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxjaXJjbGUgY3g9IjEuMjUiIGN5PSIxLjI1IiByPSIxLjI1Ii8+PHJlY3QgZmlsbD0ibm9uZSIgd2lkdGg9IjEwIiBoZWlnaHQ9IjEwIi8+PC9zdmc+"
x="0" y="0" width="10" height="10">
</image>
</pattern>
</defs>
</svg>
visually, this is the equivalent of this:
So then I call my css for svg as a fill like this:
svg #VISUEL-3 * {fill: url(#circles-1);}
I get a pretty good result:
But when I display my graphic smaller (1/4 in this i.e), the fill adapt like this
It's difficult to see on the screenshots because the scale is broken due to the width 100% of stackoverflow but the vector-effect:non-scaling-stroke works perfectly so the strokes have the same size between the first and the second screenshot and the number "1,2,3,4,5,6" as well.
So as you can see the fill has adapted...
Is it possible to keep the same pattern size (same size of dots) like in css? That look messy visually when I have two graphics that are not of the same size and are next to each other.
Is my method right to obtain that? (I'm ready to change my method..)
The pattern elements are applied to the referencing element before transformations on the element or one of its parents are applied - so the pattern size gets transformed as well. The only way to counteract this is to write a seperate pattern element for each scale you us it at, including a patternTransform with the inverse scale. Fortunately, there is a mechanism for cloning patterns with a xlink:href attribute.
.simple {
fill: url(#dots);
}
.quarter {
fill: url(#quadrupleDots);
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="150">
<defs>
<pattern id="dots" patternUnits="userSpaceOnUse" width="10" height="10">
<circle r="1.25" cx="1.25" cy="1.25" />
</pattern>
<pattern id="quadrupleDots" xlink:href="#dots" patternTransform="scale(4)" />
</defs>
<rect id="shape" class="simple" x="20" y="20" width="100" height="100" />
<rect class="quarter" x="800" y="80" width="100" height="100" transform="scale(0.25)" />
</svg>

svg grouped elements jointly hover

Is it possible for hover on an svg element cause other elements with the same class to hover too without jQuery? Or do I have to next the two into an outer group?
I have inside an inline svg the following groups:
<g class="class1">
<path....>
<path....>
</g>
<g class="class1">
<path....>
<path....>
</g>
I then have in my CSS:
class1 {
...
}
class1:hover {
...
}
I guess you can't do it directly, but you can achieve it by adding an id to the parent element, no classes needed, like that:
#circles:hover circle{
fill: Wheat;
}
<svg id="circles" width="100" height="260" >
<circle cx="50" cy="50" r="40" stroke="Tomato" stroke-width="4" fill="Tomato" />
<circle cx="50" cy="140" r="40" stroke="Tomato" stroke-width="4" fill="Aquamarine"/>
</svg>

Resources