Animating feComposite SVG filter elements - css

I have two svg shapes, one on top of the other, and I've applied the feComposite filter on both so that the top shape knocks out part of the bottom shape. The code is as follows and works fine.
<svg width="100" height="100">
<defs>
<filter id="myfilter">
<feImage xlink:href="#layer1" result="lay1"/>
<feImage xlink:href="#layer2" result="lay2"/>
<feComposite operator="out" in="lay1" in2="lay2"/>
</filter>
</defs>
<g filter="url(#myfilter)">
<circle id="layer1" cx="50" cy="50" r="40" stroke-
width="0" fill="green" />
<circle id="layer2" class="small" cx="20" cy="60" r="20" stroke-
width="0" fill="red" />
</g>
</svg>
Now I want to animate the top shape, but when I tried to apply the animation, both shapes animates and I can't figure out why because I've only targeted the second shape with class="small". Here is the css code for the animation.
#keyframes rock {
0% {
transform: rotate(45deg);
}
50% {
transform: rotate(0deg);
}
100% {
transform: rotate(-45deg);
}
}
.small {
animation-name: rock;
animation-duration: 5s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
I am puzzled by this behavior and hope someone can shed some light to this problem. Is it not possible to animate the svg elements individually when filters are applied to them as a group? But that doesn't seem to make sense because the svg elements can be targeted individually.
Here is a link to codepen: https://codepen.io/lanlanonearth/pen/bGbRyVo

Please try this: You put both circles in the <defs> and you <use xlink:href="#layer1". Next you apply the filter to the <use> element.
<svg width="100" height="100">
<defs>
<circle id="layer1" cx="50" cy="50" r="40" stroke-width="0" fill="green" />
<circle id="layer2" class="small" cx="20" cy="60" r="20" stroke-width="0" fill="red" />
<filter id="myfilter">
<feImage xlink:href="#layer1" result="lay1"/>
<feImage xlink:href="#layer2" result="lay2"/>
<feComposite operator="out" in="lay1" in2="lay2"/>
</filter>
</defs>
<use xlink:href="#layer1" filter="url(#myfilter)"></use>
</svg>
Check it on codepen

Related

Rotating the stroke but not the filling of a svg circle

I've followed this tuto in order to implement a progress ring for my Vue application. I still have an extra requirement: fill the circle with an image. That's the point I've reached to, using a pattern (copy pasted from my browser in order to avoid adding the extra complexity of Vue property evaluations):
HTML
<div>
<svg
height="600"
width="600">
<defs>
<pattern id="service" x="0%" y="0%" height="100%" width="100%"
viewBox="0 0 100 100">
<image x="5" y="5" width="90" height="90" href="https://upload.wikimedia.org/wikipedia/commons/thumb/7/7a/Selfie_icon.svg/2000px-Selfie_icon.svg.png"></image>
</pattern>
<linearGradient id="gradient">
<stop offset="0%" stop-color="#f6921e"/>
<stop offset="100%" stop-color="#f6921e88"/>
</linearGradient>
</defs>
<circle
stroke="url(#gradient)"
stroke-dasharray="1709.0264035528476 1709.0264035528476"
style="stroke-dashoffset: 512.708;"
stroke-linecap="round"
stroke-width="14"
fill="url(#service)"
r="272"
cx="300"
cy="300"
/>
</svg>
</div>
CSS
circle {
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
However, I find that rotating the circle obviously rotates its filling too.
Is there any way to overcome this problem? Why does the example rotate the entire SVG to make the circle gap be in the upside?
Codepen
You can use another circle in your SVG, one for the border and one for the background, then rotate just the circle with the border:
.circle-border {
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
<!-- begin snippet: js hide: false console: true babel: false -->
<div>
<svg height="600" width="600">
<defs>
<pattern id="service" x="0%" y="0%" height="100%" width="100%" viewBox="0 0 100 100">
<image x="5" y="5" width="90" height="90" href="https://upload.wikimedia.org/wikipedia/commons/thumb/7/7a/Selfie_icon.svg/2000px-Selfie_icon.svg.png"></image>
</pattern>
<linearGradient id="gradient">
<stop offset="0%" stop-color="#f6921e"/>
<stop offset="100%" stop-color="#f6921e88"/>
</linearGradient>
</defs>
<circle
stroke="url(#gradient)"
stroke-dasharray="1709.0264035528476 1709.0264035528476"
style="stroke-dashoffset: 512.708;"
stroke-linecap="round"
stroke-width="14"
fill="transparent"
class="circle-border"
r="272"
cx="300"
cy="300"
/>
<circle
stroke-width="0"
fill="url(#service)"
class="circle-bg"
r="272"
cx="300"
cy="300"
/>
</svg>
</div>

Portions of SVG that were offscreen while zoomed in disappear when zooming back out

I'm using css transitions to animate zooming into an SVG. The only problem is that when zooming out, portions of the SVG are missing until the animation is complete and then it all pops in.
(Only tested in Chrome on a Mac so far)
I'm not changing the SVG at all, just zooming in and then back out by setting the a scale transform on a group in the SVG.
How can I make the browser re-render these offscreen elements so that don't pop in like this?
const root = document.getElementById('root')
setTimeout(function() {
root.setAttribute('transform', 'scale(10,10)')
}, 1)
setTimeout(function() {
root.setAttribute('transform', 'scale(1,1)')
}, 4200)
#root {
transition: 4s transform;
}
circle {
stroke: white;
stroke-width: 3px;
}
<svg viewbox="0 0 300 100">
<g id="root">
<circle cx="50" cy="50" r="50" />
<circle cx="100" cy="50" r="50" />
<circle cx="150" cy="50" r="50" />
<circle cx="200" cy="50" r="50" />
<circle cx="250" cy="50" r="50" />
</g>
</svg>
As said in comments, this is probably because of some optimizations in the CSS renderer.
This is a Chrome bug (one of the many they have with their CSS paintings optimizations...), and you should let them know about it.
For the time being, have you considered using SMIL instead?
Since you used javascript in your code, I will assume you run this in a browser from some place where script execution is allowed (i/e not in <img> tag), and hence where you will be able to use a polyfill like FakeSmile.
So this will actually offer you a better browser support than through CSS transitions (IIRC IE<11 didn't support CSS transform transitions on svg elements), and moreover than the still experimental SVG2 only mix-up CSS transition of SVGTransformAttribute.
Indeed, only Chrome does support it for now (probably because while some attributes were already CSS transitionable in SVG1.1, transform having a different syntax than its CSS equivalent, the algo should be differents).
Here is what your example would look like in SMIL:
// and if you need JS control
document.onclick = e => {
document.getElementById('zoomin').beginElement();
};
circle {
stroke: white;
stroke-width: 3px;
transform: translateZ(1);
}
<svg viewbox="0 0 300 100">
<g id="root">
<animateTransform attributeName="transform" type="scale" id="zoomin"
from="1 1" to="10 10" dur="4s" begin="1s"/>
<animateTransform attributeName="transform" type="scale" id="zoomout"
from="10 10" to="1 1" dur="4s" begin="zoomin.end"/>
<circle cx="50" cy="50" r="50" />
<circle cx="100" cy="50" r="50" />
<circle cx="150" cy="50" r="50" />
<circle cx="200" cy="50" r="50" />
<circle cx="250" cy="50" r="50" />
</g>
<!-- for IE -->
<script xlink:href="https://cdn.rawgit.com/FakeSmile/FakeSmile/master/smil.user.js"></script>
</svg>
How about, transition every circle,
is this ok for you?
I actually dont have an explanation about why it works this way
const circles = document.getElementsByTagName('circle')
setTimeout(function() {
circles[0].setAttribute('transform', 'scale(10,10)');
circles[1].setAttribute('transform', 'scale(10,10)');
circles[2].setAttribute('transform', 'scale(10,10)');
circles[3].setAttribute('transform', 'scale(10,10)');
circles[4].setAttribute('transform', 'scale(10,10)');
}, 1)
setTimeout(function() {
circles[0].setAttribute('transform', 'scale(1,1)');
circles[1].setAttribute('transform', 'scale(1,1)');
circles[2].setAttribute('transform', 'scale(1,1)');
circles[3].setAttribute('transform', 'scale(1,1)');
circles[4].setAttribute('transform', 'scale(1,1)');
}, 4200)
#root {
transition: 4s transform;
}
circle {
stroke: white;
stroke-width: 3px;
transition: 4s transform;
}
<svg viewbox="0 0 300 100">
<g id="root">
<circle cx="50" cy="50" r="50" />
<circle cx="100" cy="50" r="50" />
<circle cx="150" cy="50" r="50" />
<circle cx="200" cy="50" r="50" />
<circle cx="250" cy="50" r="50" />
</g>
</svg>
Why not transition the entire svg element?
const root = document.getElementById('root')
setTimeout(function() {
root.setAttribute('transform', 'scale(10,10)')
}, 1)
setTimeout(function() {
root.setAttribute('transform', 'scale(1,1)')
}, 4200)
#root {
transition: 4s transform;
transform-origin: top left;
}
circle {
stroke: white;
stroke-width: 3px;
}
<svg id="root" viewbox="0 0 300 100">
<g>
<circle cx="50" cy="50" r="50" />
<circle cx="100" cy="50" r="50" />
<circle cx="150" cy="50" r="50" />
<circle cx="200" cy="50" r="50" />
<circle cx="250" cy="50" r="50" />
</g>
</svg>
The setAttribute need the style attribue. Try this script:
setTimeout(function() {
root.setAttribute('style', 'transform: scale(10,10)')
}, 1)
setTimeout(function() {
root.setAttribute('style', 'transform: scale(1,1)')
}, 4200)

SMIL to CSS Animation: multiple animations on single element

Now that SMIL is dying I want to convert my simple animated SVGs to using CSS Animations, but I am not super sure how to do the mapping. In this specific svg there are two animation elements.
#embedded {
color: red;
}
<svg id="embedded" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="50" cy="50" r="45" stroke="rgba(43,43,43,0.3)" fill="none" stroke-width="10" stroke-linecap="round"/>
<circle cx="50" cy="50" r="45" stroke="currentColor" fill="none" stroke-width="6" stroke-linecap="round">
<animate attributeName="stroke-dashoffset" dur="2s" repeatCount="indefinite" from="0" to="502"/>
<animate attributeName="stroke-dasharray" dur="2s" repeatCount="indefinite" values="150.6 100.4;1 250;150.6 100.4"/>
</circle>
</svg>
While I seemingly started out fine with these css rules I quickly got stuck
stroke-dasharray: 150.6 100.4 1 250 150.6 100.4;
animation-duration: 2s;
animation-iteration-count: infinite;
stroke-dashoffset: ?;
What would a complete mapping be like? Is it possible at all? Sara Soueidan says not all animations are possible using CSS that are possible using SMIL.
The below is how you'd convert that SMIL animation into its equivalent CSS animation:
Both your animate tags have the dur="2s" and so the CSS animation's duration (animation-duration) would also be 2s. You can either specify this value using the long-hand property or using the short-hand property like in the below snippet.
There is no calcMode attribute specified for your animate element and so the interpolation mode is linear. Since the interpolation mode is linear, the animation-timing-function will also be linear.
The repeatCount is indefinite and so the animation-iteration-count will be infinite.
For the stroke-dashoffset, the animation has only a from (0%) and a to (100%) value. So, in the CSS animation's keyframes, we specify the stroke-dashoffset as 0 (from value) at 0% and as 502 (to value) at 100%.
For the stroke-dasharray, the animation makes use of values instead of just from and to. In this case, there are three semi-colon separated values and so the first value from the list is given within the 0% keyframe , the second value from the list is given at 50% keyframe and the last is given within the 100% keyframe selector.
#embedded, #embedded2 {
color: red;
width: 200px;
height: 200px;
}
#embedded circle#animated {
animation: demo 2s infinite linear;
}
#keyframes demo {
0% {
stroke-dashoffset: 0;
stroke-dasharray: 150.6 100.4;
}
50% {
stroke-dasharray: 1 250;
}
100% {
stroke-dashoffset: 502;
stroke-dasharray: 150.6 100.4;
}
}
<svg id="embedded" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="50" cy="50" r="45" stroke="rgba(43,43,43,0.3)" fill="none" stroke-width="10" stroke-linecap="round" />
<circle cx="50" cy="50" r="45" stroke="currentColor" fill="none" stroke-width="6" stroke-linecap="round" id="animated">
</circle>
</svg>
<svg id="embedded2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="50" cy="50" r="45" stroke="rgba(43,43,43,0.3)" fill="none" stroke-width="10" stroke-linecap="round" />
<circle cx="50" cy="50" r="45" stroke="currentColor" fill="none" stroke-width="6" stroke-linecap="round">
<animate attributeName="stroke-dashoffset" dur="2s" repeatCount="indefinite" from="0" to="502" />
<animate attributeName="stroke-dasharray" dur="2s" repeatCount="indefinite" values="150.6 100.4;1 250;150.6 100.4" />
</circle>
</svg>

Animating an SVG Group

I currently have the following SVG:
<svg class="tower owner" height="60" width="60" viewBox="0 0 300 300">
<g transform="translate(75 75)" opacity="1">
<ellipse class="border" cx="0" cy="0" fill="#222" rx="65" ry="65" stroke-width="5"></ellipse>
<g class="rotatable" style="transform: rotate(5.497787143782138rad); transition: transform 2s;">
<rect fill="#aaa" height="50" stroke-width="7" stroke="#181818" width="40" x="-20" y="-80"></rect>
<rect fill="#555" height="58" rx="12" ry="10" width="78" x="-39" y="-25"></rect>
<rect fill="#ffe56d" height="46.4" y="-13.399999999999999" rx="12" ry="12" width="78" x="-39"></rect>
</g>
</g>
</svg>
I am currently animating a rotation on g.rotatable however I would like to use <animateTransform> if possible, and I haven't figured out how.
I have tried placing it at the start of the group, at the bottom of it, and even after it, however none have any affect.
<animateTransform attributeName="transform" attributeType="XML" dur="5s" keyTimes="0;0.4;0.75;1" repeatCount="indefinite" type="rotate" values="315deg;90deg;200deg;315deg" calcMode="linear"></animateTransform>
Since I've never really worked with SVGs or animating them, I'm not sure where I'm going wrong.
svg.tower .rotatable {
animation: tower 5s linear infinite;
}
#keyframes tower {
0% {
transform: rotate(315deg);
}
40% {
transform: rotate(90deg);
}
75% {
transform: rotate(200deg);
}
100% {
transform: rotate(315deg);
}
}
Above is my current CSS animation.
Can anyone tell me where I'm going wrong, so I can fix my mistakes, or if it's even possible, so I can potentially give up this line of action.
Note: You may want to reconsider using SMIL animations instead of CSS animations because Chrome has deprecated support for SMIL animations from v45.
There were two problems in your code and they are as follows:
The rotate transform in SVG just takes the degree number as value and doesn't need the deg suffix to be added to it. In addition a transform origin can also be specified (2nd and 3rd param) but it is not mandatory.
There was a style='transform: rotate(...)' on the .rotatable element. The CSS overrides the animateTransform and so you don't get to see any rotation. Avoid this stylesetting. If there is a need for an initial rotation you could probably use SVG's transform attribute.
Below is a working demo:
<svg class="tower owner" height="60" width="60" viewBox="0 0 300 300">
<g transform="translate(75 75)" opacity="1">
<ellipse class="border" cx="0" cy="0" fill="#222" rx="65" ry="65" stroke-width="5"></ellipse>
<g class="rotatable" transform="rotate(315)">
<rect fill="#aaa" height="50" stroke-width="7" stroke="#181818" width="40" x="-20" y="-80"></rect>
<rect fill="#555" height="58" rx="12" ry="10" width="78" x="-39" y="-25"></rect>
<rect fill="#ffe56d" height="46.4" y="-13.399999999999999" rx="12" ry="12" width="78" x="-39"></rect>
<animateTransform attributeName="transform" attributeType="XML" dur="5s" keyTimes="0;0.4;0.75;1" repeatCount="indefinite" type="rotate" values="315;90;200;315" calcMode="linear"></animateTransform>
</g>
</g>
</svg>

SVG - Css filter to blur some elements

I am trying to apply the CSS filter blur to some elements but somehow this is not working.
Here is an example in a jsfiddle:
http://jsfiddle.net/kuyraypj/
I have applied the following css but my '.blurred' circle is not blurred at all.
HTML:
<svg width="500px" height="500px">
<circle class="blurred" cx="100" cy="100" r="50" fill="red"></circle>
<circle cx="220" cy="100" r="50" fill="red"></circle>
</svg>
CSS:
svg circle.blurred {
fill: green;
-webkit-filter: blur(5px);
-moz-filter: blur(5px);
-o-filter: blur(5px);
-ms-filter: blur(5px);
filter: blur(5px);
}
Is there a way to apply CSS3 filter to some svg elements or is there any other way?
Many thanks
Here is a SVG with the same filter effect as your CSS describes:
<svg width="500px" height="200px">
<defs>
<filter id="blur">
<feGaussianBlur stdDeviation="5" />
</filter>
</defs>
<circle cx="100" cy="100" r="50" fill="green" filter="url(#blur)" ></circle>
<circle cx="220" cy="100" r="50" fill="red"></circle>
</svg>
You can either use the filter attribute like in the above snippet or you can use CSS:
svg circle.blurred {
filter: url(#blur);
}
<svg width="500px" height="200px">
<defs>
<filter id="blur">
<feGaussianBlur stdDeviation="5" />
</filter>
</defs>
<circle class="blurred" cx="100" cy="100" r="50" fill="green"></circle>
<circle cx="220" cy="100" r="50" fill="red"></circle>
</svg>
It would seem that you are trying this!
http://www.w3schools.com/svg/svg_fegaussianblur.asp
However it is not supported by certain browsers.
http://www.w3schools.com/svg/tryit.asp?filename=trysvg_fegaussianblur

Resources