I am making an animated snow-flakes pattern in SVG and it works nicely in Chrome/ium but in Firefox the pattern will only make the occasional tiny movement when you move the mouse in and out of the patterned area but otherwise doesn't move.
The underlying problem seems to be the same firefox bug as in this question where animating a <use> element doesn't work. My additional problem is that I am animating the position of a <path> element by changing its x and y position separately to avoid an obvious looping movement and since <path> elements don't have those attributes I need to reference them in a <use> element.
I'm hoping that I'm missing some completely obvious solution where the <path> gets embedded in some other element which has x and y attributes which I can animate and avoid the use of <use>.
<svg xmlns="http://www.w3.org/2000/svg" width="800px" height="150px">
<defs>
<!-- two identical snow-flake paths but slightly different scaling and rotation -->
<!-- could be two uses of the same path but the firefox animation bug is from use so that's probably not what we want -->
<path
id="snow-flake-1"
fill="#fff" stroke="none"
d="M183.3125,43.09375 L183.3125,83.8125 L152.71875,66.125 L137.1875,92.9375 L183.3125,119.65625 L183.3125,179.75 L131.5,149.8125 L131.40625,96.28125 L100.40625,96.34375 L100.46875,131.90625 L65.09375,111.46875 L49.59375,138.3125 L84.875,158.6875 L54.25,176.3125 L69.6875,203.1875 L115.90625,176.59375 L167.90625,206.625 L116.09375,236.53125 L69.6875,209.84375 L54.25,236.71875 L85.0625,254.46875 L49.6875,274.875 L65.1875,301.71875 L100.46875,281.34375 L100.40625,316.6875 L131.40625,316.75 L131.5,263.4375 L183.5,233.4375 L183.5,293.25 L137.1875,320.09375 L152.71875,346.90625 L183.5,329.09375 L183.5,369.9375 L214.5,369.9375 L214.5,329.21875 L245.09375,346.90625 L260.625,320.09375 L214.5,293.375 L214.5,233.28125 L266.3125,263.21875 L266.40625,316.75 L297.40625,316.6875 L297.34375,281.125 L332.71875,301.5625 L348.21875,274.71875 L312.9375,254.34375 L343.5625,236.71875 L328.125,209.84375 L281.9375,236.4375 L229.90625,206.40625 L281.75,176.46875 L328.125,203.1875 L343.5625,176.3125 L312.75,158.5625 L348.125,138.15625 L332.625,111.3125 L297.34375,131.6875 L297.40625,96.34375 L266.40625,96.28125 L266.3125,149.59375 L214.3125,179.59375 L214.3125,119.78125 L260.625,92.9375 L245.09375,66.125 L214.3125,83.9375 L214.3125,43.09375 L183.3125,43.09375 z"
transform="scale(0.02) rotate(-15) translate(-202 -202)">
</path>
<path id="snow-flake-2"
fill="#fff" stroke="none"
d="M183.3125,43.09375 L183.3125,83.8125 L152.71875,66.125 L137.1875,92.9375 L183.3125,119.65625 L183.3125,179.75 L131.5,149.8125 L131.40625,96.28125 L100.40625,96.34375 L100.46875,131.90625 L65.09375,111.46875 L49.59375,138.3125 L84.875,158.6875 L54.25,176.3125 L69.6875,203.1875 L115.90625,176.59375 L167.90625,206.625 L116.09375,236.53125 L69.6875,209.84375 L54.25,236.71875 L85.0625,254.46875 L49.6875,274.875 L65.1875,301.71875 L100.46875,281.34375 L100.40625,316.6875 L131.40625,316.75 L131.5,263.4375 L183.5,233.4375 L183.5,293.25 L137.1875,320.09375 L152.71875,346.90625 L183.5,329.09375 L183.5,369.9375 L214.5,369.9375 L214.5,329.21875 L245.09375,346.90625 L260.625,320.09375 L214.5,293.375 L214.5,233.28125 L266.3125,263.21875 L266.40625,316.75 L297.40625,316.6875 L297.34375,281.125 L332.71875,301.5625 L348.21875,274.71875 L312.9375,254.34375 L343.5625,236.71875 L328.125,209.84375 L281.9375,236.4375 L229.90625,206.40625 L281.75,176.46875 L328.125,203.1875 L343.5625,176.3125 L312.75,158.5625 L348.125,138.15625 L332.625,111.3125 L297.34375,131.6875 L297.40625,96.34375 L266.40625,96.28125 L266.3125,149.59375 L214.3125,179.59375 L214.3125,119.78125 L260.625,92.9375 L245.09375,66.125 L214.3125,83.9375 L214.3125,43.09375 L183.3125,43.09375 z"
transform="scale(0.027) rotate(15) translate(-202 -202)">
</path>
<!-- A couple of animated snow flakes. Animate x and y separately to avoid obvious cycles. -->
<!-- Pick dur values with large smallest common multiple to make a long cycle. -->
<pattern id="pt-snow-3" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<use x="5" y="5" href="#snow-flake-1">
<animate attributeName="x" dur="4.2357s" repeatCount="indefinite" values="5; 5.7; 5; 3; 6.5; 5"/>
<animate attributeName="y" dur="3.9s" repeatCount="indefinite" values="5; 3; 5.7; 5; 6.5; 5"/>
</use>
<use x="15" y="15" href="#snow-flake-2">
<animate attributeName="x" dur="5.0s" repeatCount="indefinite" values="15; 13; 16.5; 15; 15.7; 15"/>
<animate attributeName="y" dur="3.7357s" repeatCount="indefinite" values="15; 13; 16.5; 15; 15.7; 15"/>
</use>
</pattern>
</defs>
<g>
<rect x="0" width="800" y="0" height="150" fill="#bbb"/>
<rect x="0" width="800" y="0" height="150" fill="url(#pt-snow-3)" stroke="#888"/>
</g>
</svg>
Based on comments by #PaulLeBeau and #RobertLongson I have made a version where there are two <animateTransform> tags attached to a <g> where the second one has addititve="sum". This <g> also contains the <path> to animate.
The two animations can have different durations to make for a long cycle and both can affect both x and y to make the illusion even more complete.
<svg xmlns="http://www.w3.org/2000/svg" width="500px" height="150px">
<defs>
<path
id="snow-flake"
fill=#fff stroke="none"
d="M183.3125,43.09375 L183.3125,83.8125 L152.71875,66.125 L137.1875,92.9375 L183.3125,119.65625 L183.3125,179.75 L131.5,149.8125 L131.40625,96.28125 L100.40625,96.34375 L100.46875,131.90625 L65.09375,111.46875 L49.59375,138.3125 L84.875,158.6875 L54.25,176.3125 L69.6875,203.1875 L115.90625,176.59375 L167.90625,206.625 L116.09375,236.53125 L69.6875,209.84375 L54.25,236.71875 L85.0625,254.46875 L49.6875,274.875 L65.1875,301.71875 L100.46875,281.34375 L100.40625,316.6875 L131.40625,316.75 L131.5,263.4375 L183.5,233.4375 L183.5,293.25 L137.1875,320.09375 L152.71875,346.90625 L183.5,329.09375 L183.5,369.9375 L214.5,369.9375 L214.5,329.21875 L245.09375,346.90625 L260.625,320.09375 L214.5,293.375 L214.5,233.28125 L266.3125,263.21875 L266.40625,316.75 L297.40625,316.6875 L297.34375,281.125 L332.71875,301.5625 L348.21875,274.71875 L312.9375,254.34375 L343.5625,236.71875 L328.125,209.84375 L281.9375,236.4375 L229.90625,206.40625 L281.75,176.46875 L328.125,203.1875 L343.5625,176.3125 L312.75,158.5625 L348.125,138.15625 L332.625,111.3125 L297.34375,131.6875 L297.40625,96.34375 L266.40625,96.28125 L266.3125,149.59375 L214.3125,179.59375 L214.3125,119.78125 L260.625,92.9375 L245.09375,66.125 L214.3125,83.9375 L214.3125,43.09375 L183.3125,43.09375 z"
transform="translate(-202 -202)">
</path>
<!-- A couple of animated snow flakes. Each has two animation loops to avoid obvious cycles. -->
<!-- Pick dur values with large smallest common multiple to make a long cycle. -->
<pattern id="pt-snow-3" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<g>
<use href="#snow-flake" transform="scale(0.02) rotate(-15)"/>
<animateTransform dur="5.000s" values="5,5; 5.7,4.7; 5.4,5; 3.7,5.2; 6.2,4.7; 5,5" attributeName="transform" type="translate" repeatCount="indefinite"/>
<animateTransform dur="4.789s" values="0,0; -1,3; -1,1; 0,-2; 2,-1; 0,0" additive="sum" attributeName="transform" type="translate" repeatCount="indefinite"/>
</g>
<g>
<use href="#snow-flake" transform="scale(0.027) rotate(15)"/>
<animateTransform dur="4.000s" values="15,14; 14.7,14; 15.2,14.1; 13,14; 15.5,14; 15,14" attributeName="transform" type="translate" repeatCount="indefinite"/>
<animateTransform dur="5.789s" values="0,0; 0,-2; -1,1; -1,1; 1,-1; 0,0" additive="sum" attributeName="transform" type="translate" repeatCount="indefinite"/>
</g>
</pattern>
</defs>
<g>
<rect x="0" width="500" y="0" height="150" fill="#bbb"/>
<rect x="0" width="500" y="0" height="150" fill="url(#pt-snow-3)" stroke="#888"/>
</g>
</svg>
Related
Is it possible to perform an animateTransform on a hover inside SVG?
I want the element to transform while the mouse is over it, and then return to its original position when the mouse has left.
I figure this is probably isn't possible. I have a mouseover to start the element, a mouseout to stop it, and set fill to remove to set its state back to the start.
But if the transform moves the object out from user the mouse cursor, the animation resets and starts moving again then moves out and resets and starts moving again ... flickering.
Here my simple example
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<g transform="translate(0 0)">
<rect x="10" y="10" width="20" height="40" fill="black" />
<animateTransform attributeName="transform" begin="mouseover" end="mouseout" dur="2s" type="translate" from="0 0" to="50 50" repeatCount="1" fill="remove" />
</g>
</svg>
Note: The svg is not being used on a HTML page - most solutions I have seen use "css" and "javascript", neither of which are available. I need to use SVG/SMIL only. If I were targetting html, I'd use css :hover.
You can use fill freeze on combination with restart whenNotActive. And then have an animation for both mouseover and mouseout.
I also removed the from attribute. The animation needs to start from the "freezed" point.
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<g transform="translate(0 0)">
<rect x="10" y="10" width="20" height="40" fill="black" />
<animateTransform attributeName="transform" begin="mouseover"
dur="2s" type="translate" to="50 50" repeatCount="none"
fill="freeze" restart="whenNotActive" />
<animateTransform attributeName="transform" begin="mouseout"
dur="2s" type="translate" to="0 0" repeatCount="none"
fill="freeze" restart="whenNotActive" />
</g>
</svg>
One variant is to link the animation to an invisible, unmoving element on top of the moving one. For that, the begin and end event values are prefixed with the id of that cover element. Like this:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<g transform="translate(0 0)">
<rect x="10" y="10" width="20" height="40" fill="black" />
<animateTransform attributeName="transform"
begin="cover.mouseover" end="cover.mouseout" dur="2s"
type="translate" from="0 0" to="50 50" repeatCount="1" fill="remove" />
</g>
<rect id="cover" x="10" y="10" width="20" height="40" opacity="0" />
</svg>
I'm sure this is really simple, but I'm making a SVG, where I have 4 <g> groups for each of CMYK colors and I'm having all 4 composited on top of each other.
I want to use a multiply color blending mode for each layer; each layer is its own solid color for each of CMYK, and I'd like the colors blended to produce the final image.
Using CSS I do:
<svg //put svg intro tag stuff here>
<rect width="100%" height="100%" fill="white"/>
<g fill="#0FF" style="mix-blend-mode: multiply">
<!--shape primitives that go on the cyan layer-->
</g>
<g fill="#F0F" style="mix-blend-mode: multiply">
<!--shape primitives that go on the magenta layer-->
</g>
<g fill="#FF0" style="mix-blend-mode: multiply">
<!--shape primitives that go on the yellow layer-->
</g>
<g fill="#000" style="mix-blend-mode: multiply">
<!--shape primitives that go on the key black layer-->
</g>
</svg>
This result is perfect... except it seems css in svg files isn't supported in one of the programs I'm using. So I'm trying to recreate this exact same effect using feBlend and feComposite.
<svg //put svg intro tag stuff here>
<defs>
<filter id="overcomp"><feComposite in="SourceGraphic" in2="BackgroundImage" operator="over" result="comp"/></filter>
<filter id="multiply"><feBlend mode="multiply" in2="BackgroundImage" in="SourceGraphic"/></filter>
</defs>
<rect width="100%" height="100%" fill="white"/>
<g fill="#0FF" filter="url(#overcomp)"> <!-- or #multiply)
<!--shape primitives that go on the cyan layer-->
</g>
<g fill="#F0F" filter="url(#overcomp)"> <!-- or #multiply)
<!--shape primitives that go on the magenta layer-->
</g>
<g fill="#FF0" filter="url(#overcomp)"> <!-- or #multiply)
<!--shape primitives that go on the yellow layer-->
</g>
<g fill="#000" filter="url(#overcomp)"> <!-- or #multiply)
<!--shape primitives that go on the key black layer-->
</g>
</svg>
The filter doesn't seem to do anything and the resultant image colors are not blended.
Each layer is 20k+ shapes so I would prefer not to duplicate the definitions if possible. How do I fix this?
BackgroundImage is not a supported input for any browser except IE10 (and has been deprecated for SVG2)
You have three options for pulling in groups like this into a SVG filter.
The first - which works everywhere except Firefox - is to pull them into a single filter using feImage and a fragment identifier.
<svg width="200px" height="200px">
<defs>
<g fill="#0FF" id="layer1">
<rect x="90" y="10" width="20" height="180"/>
</g>
<g fill="#F0F" id="layer2">
<circle cx="100" cy="100" r="80"/>
</g>
<g fill="#FF0" id="layer3">
<rect x="10" y="90" width="180" height="20"/>
</g>
<g fill="#000" id="layer4">
<path d="M 50 50 L150 50 L 150 150 L 50 150z"/>
</g>
<filter id="blendDefs">
<feImage xlink:href="#layer1" result="lay1"/>
<feImage xlink:href="#layer2" result="lay2"/>
<feImage xlink:href="#layer3" result="lay3"/>
<feImage xlink:href="#layer4" result="lay4"/>
<feBlend mode="multiply" in="lay1" in2="lay2" result="lay12"/>
<feBlend mode="multiply" in="lay3" in2="lay4" result="lay34"/>
<feBlend mode="multiply" in="lay12" in2="lay34" result="lay1234"/>
</filter>
</defs>
<rect filter="url(#blendDefs)" width="200" height="200" fill="none" stroke="none"/>
</svg>
The second - which is a hack, but bear with me, is to lay out each group in a column (or a row or a grid) within a single SourceImage, but use feOffsets to manually overlay them inside the filter. You have to do cleanup by manually drawing a rect over the overflow - hence the final rect below. (You used to be able to clip this by using filter dimensions but apparently Chrome now clips intermediate filter outputs to the filter window, so you can't use the feOffset method and filter clipping together. You also used to be able to use positioned feFloods to overpaint stuff inside a filter - but that's also broken in Chrome.)
<svg width="200px" height="800px">
<defs>
<filter id="blendOffset" >
<feOffset dy="0" result="lay1"/>
<feOffset dy="-200" result="lay2"/>
<feOffset dy="-200" result="lay3"/>
<feOffset dy="-200" result="lay4"/>
<feBlend mode="multiply" in="lay1" in2="lay2" result="lay12"/>
<feBlend mode="multiply" in="lay3" in2="lay4" result="lay34"/>
<feBlend mode="multiply" in="lay12" in2="lay34" result="lay1234"/>
</filter>
</defs>
<g id="OUTERCONTAINER" filter="url(#blendOffset)">
<rect x="0" y="0" height="800" width="200" fill="none" stroke="none"/>
<g fill="#0FF" >
<rect x="90" y="10" width="20" height="180" />
</g>
<g fill="#F0F" >
<circle cx="100" cy="100" r="80" transform="translate(0 200)"/>
</g>
<g fill="#FF0" >
<rect x="10" y="90" width="180" height="20" transform="translate(0 400)"/>
</g>
<g fill="#000">
<path d="M 50 50 L150 50 L 150 150 L 50 150z" transform="translate(0 600)"/>
</g>
</g>
<rect x="0" y="200" height="600" width="200" fill="#FFF" stroke="none"/>
</svg>
The third- which is unwieldly - is to inline each group as a separate image/svg URI within each feImage. This works cross browser - your code will look like this ...
<feImage xlink:href="data:svg+xml; etc...."/>
<feImage xlink:href="data:svg+xml; etc...."/>
<feBlend in="layer2" in2="layer2" ... etc.
After the end of an AnimateTransform action, the element snaps back to the original value.
This isn't exactly unexpected as it's in the SMIL documentation:
As with all animation elements, this only manipulates the presentation value, and when the animation completes, the effect is no longer applied
But it is unwanted. I'd like to find a way to persist the changes using XML animations
Here's an example in SVG
<svg width="200" height="200" viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<rect id="outline" stroke="black" fill="white"
width="100" height="100" >
<animateTransform id="one"
attributeType="XML"
attributeName="transform"
type="translate"
from="0" to="-7"
dur="1s" repeatCount="1" />
</rect>
</svg>
One idea I had was to call a set action with dur="indefinite" that was triggered by the end of the first animation with begin="one.end", but can't quite seem to get the syntax right. I haven't found any documentation that show how to call set for a transformed value.
<svg width="200" height="200" viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<rect id="outline" stroke="black" fill="white"
width="100" height="100" >
<animateTransform id="one"
attributeType="XML"
attributeName="transform"
type="translate"
from="0" to="-7"
dur="1s" repeatCount="1" />
<!-- Doesn't work -->
<set attributeType="XML"
attributeName="transform"
type="translate"
to="-7" begin="one.end" />
<!-- Does work (as POC) -->
<set attributeType="css"
attributeName="fill"
to="green" begin="one.end" />
</rect>
</svg>
This question on persisting the end state of the animation shows how to do this with css transforms by using -webkit-animation-fill-mode: forwards;, but that obviously won't have any affect on an svg animation
fill="freeze" will persist the state of an animation e.g.
<svg width="200" height="200" viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<rect id="outline" stroke="black" fill="white"
width="100" height="100" >
<animateTransform id="one"
attributeType="XML"
attributeName="transform"
type="translate"
from="0" to="-7"
dur="1s" repeatCount="1"
fill="freeze"/>
</rect>
</svg>
For my use it would be convenient for me to have a list of SVG symbols centered on zero, making their placement with use easier. For example a symbol which is simply a circle would have its center at zero and then a given radius. However with the standard clipping that clips away 3/4 of the circle. Here's an example:
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1" width="400.0" height="400.0" preserveAspectRatio="xMidYMid meet"
viewBox="0.0 0.0 230.0 150.0">
<rect x="0.0" y="0.0" width="230.0" height="150.0" style="stroke:#000000; stroke-width:0.5 ; fill:#B9FFFF;"/>
<symbol id="concentric">
<circle cx="0.0" cy="0.0" r="10.0" style="stroke:#FF0000; stroke-width:0.266; fill:none"/>
<circle cx="0.0" cy="0.0" r="5.0" style="stroke:#00FF00; stroke-width:0.266; fill:none"/>
</symbol>
<use xlink:href="#concentric" x="20" y="20" />
<use xlink:href="#concentric" x="40" y="20" />
<use xlink:href="#concentric" x="60" y="20" />
</svg>
This will result in three uses of the symbol called "concentric" but since the original symbol has two circles at 0,0 three quarters of the symbol is clipped.
What is the easiest way of not clipping the symbols at 0 0?
You can use overflow="visible" to turn clipping off if you don't want it.
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1" width="400.0" height="400.0" preserveAspectRatio="xMidYMid meet"
viewBox="0.0 0.0 230.0 150.0">
<rect x="0.0" y="0.0" width="230.0" height="150.0" style="stroke:#000000; stroke-width:0.5 ; fill:#B9FFFF;"/>
<symbol id="concentric" overflow="visible">
<circle cx="0.0" cy="0.0" r="10.0" style="stroke:#FF0000; stroke-width:0.266; fill:none"/>
<circle cx="0.0" cy="0.0" r="5.0" style="stroke:#00FF00; stroke-width:0.266; fill:none"/>
</symbol>
<use xlink:href="#concentric" x="20" y="20" />
<use xlink:href="#concentric" x="40" y="20" />
<use xlink:href="#concentric" x="60" y="20" />
</svg>
I mean after my SVG stops completing the animation I want the animation to start over again and end and then again start over and so on.
I tried:
<svg>
<animate repeatCount="indefinite"/>
</svg>
But it doesn't work.
See example animMotion01 in the w3 SVG spec for a fairly reduced example illustrating SVG animation (including use of repeatCount="indefinite"):
<svg width="5cm" height="3cm" viewBox="0 0 500 300"
xmlns="http://www.w3.org/2000/svg" version="1.1"
xmlns:xlink="http://www.w3.org/1999/xlink" >
<desc>Example animMotion01 - demonstrate motion animation computations</desc>
<rect x="1" y="1" width="498" height="298"
fill="none" stroke="blue" stroke-width="2" />
<!-- Draw the outline of the motion path in blue, along
with three small circles at the start, middle and end. -->
<path id="path1" d="M100,250 C 100,50 400,50 400,250"
fill="none" stroke="blue" stroke-width="7.06" />
<circle cx="100" cy="250" r="17.64" fill="blue" />
<circle cx="250" cy="100" r="17.64" fill="blue" />
<circle cx="400" cy="250" r="17.64" fill="blue" />
<!-- Here is a triangle which will be moved about the motion path.
It is defined with an upright orientation with the base of
the triangle centered horizontally just above the origin. -->
<path d="M-25,-12.5 L25,-12.5 L 0,-87.5 z"
fill="yellow" stroke="red" stroke-width="7.06" >
<!-- Define the motion path animation -->
<animateMotion dur="6s" repeatCount="indefinite" rotate="auto" >
<mpath xlink:href="#path1"/>
</animateMotion>
</path>
</svg>