In the following example I created a blinking eyes animation using CSS and an SVG: http://codepen.io/JamesTheHacker/pen/oLZVrY
It works fine in chrome, but on Firefox the eyes do not appear unless I specifically provide a width and height attribute on the <rect>.
Without the attribute the eyes are not visible. If I add the attribute the CSS height animation has no effect.
In SVG 1.1 height and width are attributes i.e. you can't set the height and width via CSS.
In SVG 2 it is proposed width and height should be CSS properties.
Back in 2016 only Chrome had implemented this part of the unfinished SVG 2 specification, since then Firefox has also implemented it so the testcase works as expected.
You have already an excellent answer indicating what the problem is
You can solve it this way
* { box-sizing: border-box; }
body { background-color: rgb(0, 184, 234); }
svg {
display: block;
margin: 90px auto;
width: 380px;
height: 130px;
}
/*
* Keyframes for blink animation
*/
#keyframes blink {
0% { transform: scaleY(1); }
40% { transform: scaleY(0); }
80% { transform: scaleY(1); }
}
.eye {
height: 20px;
width: 20px;
animation-name: blink;
animation-duration: 1s;
animation-iteration-count: infinite;
transform-origin: center 315px;
}
<svg>
<g transform="translate(-108.75 -258.41)">
<path id="specs" fill="#FFF" d="M328.911,258.412v10.29h-19.127v16.192h-19.16v16.249h19.287v-16.191h19v62.169h19.432
v-68.736h123.995v68.761h19.432v-88.709l-18.047,0.001v-0.025h-125.38H328.911z M124.069,258.454v0.001h-15.321v88.709h19.427
v-68.733h123.996l0.008,68.757h19.423v-62.401h19.032v-16.25h-19.032v-10.053h-18.047v-0.026H124.072L124.069,258.454z
M348.294,347.163v17.488h19.4v19.951h85.141v-19.976h-85.109v-17.464H348.294L348.294,347.163z M452.819,347.171v17.439h19.431
v-17.439H452.819z M128.133,347.203v17.487h19.398v19.951h85.149l-0.008-19.975h-85.117l0.001-17.464h-19.427H128.133z
M232.658,347.212v17.439h19.423v-17.439H232.658z"/>
<g id="eyes">
<rect class="eye" x="181.759" y="305.026" width="20" height="20" fill="#FFF" />
<rect class="eye" x="402.759" y="305.026" width="20" height="20" fill="#FFF" />
</g>
</g>
</svg>
Related
JSFiddle example. The idea is, hovering over the big rectangle scales it in addition causing the other two smaller rectangles to scale too. Not vica versa.
HTML
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<g class="lower_element">
<rect x="19" y="19" width="35" height="15" />
<rect x="36" y="36" width="36.75" height="15" />
</g>
<g class="top">
<rect x="149" y="100" width="96.4" height="96.3"/>
</g>
</svg>
CSS
svg {
height: 220px;
width: 400px;
background: grey;
}
.lower_element {
fill: blue;
transform-origin: center;
transform-box: fill-box;
transition-duration: 0.1s;
}
.lower_element:hover {
transform: scale(1.7);
-moz-transform: scale(1.7);
-webkit-transform: scale(1.7);
-o-transform: scale(1.7);
-ms-transform: scale(1.7);
}
.top {
fill: blue;
transform-origin: center;
transform-box: fill-box;
transition-duration: 0.1s;
}
.top:hover ~ .lower-element:hover ~ .lower-element {
transform: scale(1.7);
-moz-transform: scale(1.7);
-webkit-transform: scale(1.7);
-o-transform: scale(1.7);
-ms-transform: scale(1.7);
}
At present the two smaller rectangles are scaling. Hovering on the big rectangle has no effect. Tried to use ~ for lower_element, which seems to work with div elements, like in this example here, but it doesn't do the job in SVG.
PS. The solution is saved in the 1st mentioned JSFiddle. What I noticed while playing with the original SVG, which has more than two thousand lines of g code, that it didn't work as shown in JSFiddle. The problem was, the parent "top" element was mentioned below the sibling element. I assumed it would work, but it didn't. I was making a parallel with CSS whereas anything that is mentioned below will replace anything mentioned above and thus should work. Placing the parent "top" element above the sibling elements solved the issue.
your class name of lower_element is wrong in css. also try this:
html:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<g class="top">
<rect x="149" y="100" width="96.4" height="96.3"/>
</g>
<g class="lower_element">
<rect x="19" y="19" width="35" height="15" />
<rect x="36" y="36" width="36.75" height="15" />
</g>
</svg>
css:
svg {
height: 220px;
width: 400px;
background: grey;
}
.lower_element {
fill: white;
transform-origin: center;
transition-duration: 0.1s;
}
.top {
fill: blue;
transform-origin: center;
transition-duration: 0.1s;
}
.top:hover + .lower_element {
fill: red;
}
When we apply a 2D CSS rotation to an SVG shape in Firefox (I'm on 63.0.1 - latest version), it gets misaligned. There are plenty of questions on this topic, eg. Setting transform-origin on SVG group not working in FireFox
I'm not seeing it as fixed, but perhaps I'm missing something. Best to look at my CodePen first: https://codepen.io/MSCAU/pen/GwozbO
Here's the gist of it:
circle {
fill: none;
transform-origin: center;
// transform-origin: 6px 6px; /* Makes no difference */
// transform-box: fill-box; /* Makes no difference */
}
circle:nth-child(1) {
stroke: red;
stroke-width: 2;
}
circle:nth-child(2) {
stroke: blue;
stroke-width: 1;
transform: rotate(-90deg);
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="120" height="120">
<circle cx="6" cy="6" r="5"/>
<circle cx="6" cy="6" r="5"/>
</svg>
vector-effect seem to cause artifacts. If you hover over the star, you can see how it leaves trails in latest release version of Chrome:
If I remove vector-effect the artifacts are gone.
The only way I was able to fix this is by setting overflow: hidden on the svg element during the animation, such that the corners which overflow the element bounding box because of the stroke thickness are hidden, which is not desired.
body {
padding: 10px;
}
.svg-wrapper {
width: 300px;
height: 300px;
-webkit-transition: width 2s, height 2s; /* Safari */
transition: width 2s, height 2s;
}
.svg-wrapper:hover {
width: 100px;
height: 100px;
}
.svg-wrapper svg {
overflow: visible;
}
<div class="svg-wrapper">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" preserveAspectRatio="none" viewBox="31, 25, 238, 226" stroke-width="10" stroke="red" >
<polygon points="150,25 179,111 269,111 197,165 223,251 150,200 77,251 103,165 31,111 121,111" vector-effect="non-scaling-stroke" />
</svg>
</div>
Is this issue know? Looking forward for some good workarounds
I am trying to make a fancy animation only in CSS. I started with a tutorial on W3 School and wanted to make it better. My idea is to have a square loader turning clockwise while another inside would turn in the opposite direction.
On this link you will see what I'm talking about, the only difference is that I would like the red part to be turning in the opposite direction.
In order to do so I tried adding another div with class name .spinner. Here's my try at it: https://jsfiddle.net/avhjj4ps/
.loader-container {
position: absolute;
left: calc(50% - 75px);
width: 150px;
height: 150px;
padding: 5px;
border: 1px solid red;
top: calc(50% - 75px);
}
img {
width: 200px;
margin: 20px;
/*animation: move 2s alternate infinite linear;*/
}
#myClip, #svg {
position: absolute;
left: 50%;
top: 50%;
}
.loader, .spinner {
position: absolute;
}
.loader {
left: calc(50% - 35px);
top: calc(50% - 35px);
width: 40px;
height: 40px;
border: 15px solid transparent;
border-top: 15px solid none;
/*-webkit-animation: loader 2s linear infinite;
animation: loader 2s linear infinite;*/
}
.spinner {
left: calc(50% - 55.1px);
top: calc(50% - 55.1px);
/*clip-path: url(#myClip);*/
width: 40px;
border-radius: 50%;
height: 40px;
border: 36px solid #f3f3f3;
border-top: 36px solid #5cb85c;
/*-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;*/
}
#-webkit-keyframes loader {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
#keyframes loader {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(-360deg); }
}
#keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(-360deg); }
}
<div class="loader-container">
<div class="loader"></div>
<div class="spinner"></div>
<svg id="svg" width="0" height="0">
<defs>
<clipPath id="myClip">
<rect x="-35" y="-35" width="15" height="70" />
<rect x="20" y="-35" width="15" height="70" />
<rect x="-35" y="-35" width="70" height="15" />
<rect x="-35" y="20" width="70" height="15" />
</clipPath>
</defs>
</svg>
</div>
I am trying to show the green spinner only where there is the square loader. It would be like a mask. In the above snippet (also available here: http://codepen.io/anon/pen/ZOoByA), I'm trying to use the clip-path property.
Can some tell me why clip-path: url(#myClip); doesn't work ? When I comment this line the loader shows completely, however while active it's not showing at all.
You can create your loader in svg with some polygons and then clip the inner green loader away with clipPath.
First, define the gray border as a polygon:
<polygon id="loader" points="0,0 0,70 70,70 70,0 0,0 15,15 55,15 55,55 15,55 15,15" />
As we will reuse this shape (the actual loader and the clip-path shape), we put into the defs tag:
<svg height="0" width="0">
<defs>
<polygon id="loader" points="..." />
</defs>
</svg>
Then we put the clipPath into the same defs tag:
<clipPath id="loaderClipper">
<use xlink:href="#loader" x="15" y="15" />
</clipPath>
The offset of 15 is calculated in the following way: The loader's width is 70, but if it is rotated by 45 degrees, it's width is 70√2 which rounds to 100. The whitespace in the left and in the right is (100 - 70) / 2 = 15.
The svg for the actual used element looks like this:
<svg width="100" height="100" viewbox="0 0 100 100" clip-path="url(#loaderClipper)">
<use xlink:href="#loader" class="loader" x="15" y="15" />
<polygon class="spinner" points="0,0 100,0 50,50" x="30" y="30" />
</svg>
And some css for colors and the animation:
svg {
animation: rotate 2s linear infinite;
transform-origin: 50px 50px;
}
.loader {
fill: #dcdada;
}
.spinner {
fill: #5cb85c;
animation: rotate 1s linear infinite reverse;
transform-origin: 50px 50px;
}
Result fiddle: https://jsfiddle.net/apLepsv3/10/
Successfully tested on both mobile and desktop Firefox and Chrome.
For a CSS-only solution without SVG you need some helper elements:
<div class="loader">
<div class="square"></div>
<div class="cutter">
<div class="spinner">
</div>
</div>
</div>
And then this CSS code:
.square {
width: 40px;
height: 40px;
background: #f3f3f3;
z-index: 1;
}
.cutter {
width: 70px;
height: 70px;
left: -15px;
top: -15px;
overflow: hidden;
}
.spinner {
width: 54px;
border-radius: 50%;
height: 54px;
border: 8px solid transparent;
border-top: 8px solid #5cb85c;
-webkit-animation: spin 1s linear infinite;
animation: spin 1s linear infinite;
margin-left: -15px;
margin-top: -15px;
}
Result: https://jsfiddle.net/avhjj4ps/3/
Disadvantage: Inner square must have a solid background (no gradient or image) if it has to match the parent's / body's background.
You can create the spinner with HTML and CSS and then cut the overflow away by using the clip-path property in combination with a svg <clipPath> element.
Your html structure of the spinner:
<div class="loader">
<div class="spinner">
</div>
</div>
Now position the two elements over each other:
.loader {
width: 40px;
height: 40px;
position: relative;
left: 30px;
top: 30px;
border: 15px solid #dcdada;
border-top: 15px solid none;
-webkit-animation: loader 2s linear infinite;
animation: loader 2s linear infinite;
}
.spinner {
width: 0px;
height: 0px;
position: relative;
left: -30px;
top: -30px;
border: 50px solid transparent;
border-top: 50px solid #5cb85c;
-webkit-animation: spin 1s linear infinite;
animation: spin 1s linear infinite;
}
But there's still that green overflow outside and inside of the gray border. So we need to cut it away with a svg <polygon>.
<svg height="0" width="0">
<defs>
<clipPath id="loaderClipper">
<polygon points="0,0 0,70 70,70 70,0 0,0 15,15 55,15 55,55 15,55 15,15"/>
</clipPath>
</defs>
</svg>
The points define a 70x70 square with a 40x40 square cut off.
Then add the clip-path property that references to the svg <clipPath> element:
.loader {
clip-path: url(#loaderClipper);
}
Fiddle: https://jsfiddle.net/apLepsv3/2/
Disadvantage: Only supported in Firefox, not Chrome
Is it possible to achieve perspective with 3d transforms on a SVG elements?
I'm talking about something similar with how the Star Wars opening titles look like with 3d perspective. This is a jsfiddle with the desired effect achieved using CSS3 3d transforms:
<section style="transform: perspective(200px) rotateX(-30deg); transform-origin: 50% 100%; text-align: justify; width: 100px;">
<p style="backface-visibility: hidden;">TEXTTEXTTEXT</p>
</section>
Update Nov 2018:
Testing the snipet from the question in latest chrome and Firefox works. Although support for 3d transforms on svg elements isn't very wide, browsers are implementing it more and more.
Origin answer :
3D transforms aren't supported on SVG elements. There are a few workarounds though :
If the svg doesn't contain elements that shouldn't be transformed, you can use CSS 3d transforms on the SVG element itself :
svg {
width: 70%;
margin: 0 auto;
display: block;
-webkit-transform: perspective(300px) rotateX(30deg);
transform: perspective(300px) rotateX(30deg);
}
<svg viewbox="0 0 100 20">
<text x="0" y="20">TEXTEXTEX</text>
</svg>
In case of polygons, you make a 2D polygon look like a 3D polygon. In the following example, the red rectangle is 3D rotated (rotateX(40deg)) and the black rectangle is a 2D SVG polygon made to look like a 3D rotated rectangle:
div{
display:inline-block;
width:200px; height:100px;
background:red;
transform:perspective(500px) rotateX(40deg);
}
svg{
display:inline-block;
width:220px; height:auto;
}
div, svg{
display:inline-block;
margin:0 10px;
}
<div></div>
<svg viewbox="0 0.5 10 4">
<polygon points="9.9 4.1 0.1 4.1 0.7 0.6 9.3 0.6" fill=""/>
</svg>
3D transforms are supported inside <svg> elements (f.e. on <circle>) (at least to some extent, it seems like perspective is isometric only).
For example, here's animation of transform: rotate3d applied to <circle> elements (tested in Chrome only):
body, html {
background: black;
width: 100%; height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
}
svg {
width: 100%;
}
.gAExgp {
transform-origin: 50% 50% 0px;
animation-name: phEs, ipaUyp;
animation-duration: 4s, 7s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.PwswZ {
transform-origin: 50% 50% 0px;
animation-name: gcRPJT, ipaUyp;
animation-duration: 4s, 8s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
#keyframes phEs {
50% {
transform: rotate3d(0, 2, 1, 180deg);
}
100% {
transform: rotate3d(0, 2, 1, 360deg);
}
}
#keyframes gcRPJT {
50% {
transform: rotate3d(2, 0, 1, 180deg);
}
100% {
transform: rotate3d(2, 0, 1, 360deg);
}
}
#keyframes ipaUyp {
0% {
stroke: magenta;
}
33% {
stroke: cyan;
}
66% {
stroke: yellow;
}
100% {
stroke: magenta;
}
}
<!-- Logo from https://rebassjs.org -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" style="display:block;max-width:100%;margin:0;fill:none;stroke:cyan" vector-effect="non-scaling-stroke" class="sc-htoDjs hCHUAb"><circle cx="32" cy="32" r="32" fill="#000" stroke="none"></circle><circle cx="32" cy="32" r="30" stroke-width="1" vector-effect="non-scaling-stroke" opacity="0.5"></circle><g><circle cx="32" cy="32" r="24" stroke-width="2" vector-effect="non-scaling-stroke" class="sc-dnqmqq gAExgp"></circle><circle cx="32" cy="32" r="24" stroke-width="2" vector-effect="non-scaling-stroke" class="sc-iwsKbI PwswZ"></circle></g><text x="32" y="34" text-anchor="middle" font-family="system-ui, sans-serif" font-weight="bold" font-size="4" stroke="none" fill="white" style="text-transform:uppercase;letter-spacing:0.5em">Rebass</text></svg>
Also available here: https://codepen.io/anon/pen/MPeyEj