I've been asked to take a pure CSS3 loading spinner and make it dynamically resizable by pixels to use in different places in a program.
My current code is: (Which apparently doesn't run well in SO's snippets)
.loader {
animation:spin 1s infinite linear;
border:solid 2vmin transparent;
border-radius:50%;
border-right-color:#71c491;
border-top-color:#f7941d;
box-sizing:border-box;
height:20vmin;
left:calc(50% - 10vmin);
position:fixed;
top:calc(50% - 10vmin);
width:20vmin;
z-index:1;
&:before {
animation:spin 2s infinite linear;
border:solid 2vmin transparent;
border-radius:50%;
border-right-color:#21409a;
border-top-color:#92278f;
box-sizing:border-box;
content:"";
height:16vmin;
left:0;
position:absolute;
top:0;
width:16vmin;
}
&:after {
animation:spin 3s infinite linear;
border:solid 2vmin transparent;
border-radius:50%;
border-right-color:#13b0e6;
border-top-color:#18244c;
box-sizing:border-box;
content:"";
height:12vmin;
left:2vmin;
position:absolute;
top:2vmin;
width:12vmin;
}
}
#keyframes spin {
100% {
transform:rotate(360deg);
}
}
<div class="loader"></div>
I googled and tried transform:scale() but as far as I can tell that only takes specific input and increases/decreases the size by percentage. (2 = 200% size)
I'm thinking I need some sort of wrapper, but I'm not too familiar with advanced CSS to get the effect. When I tried to create my own, only the top border of the spinner would be resized into a weird shape and not the inner borders. I'm just stumped. If you could point me in the right direction, I'd be appreciative. Thank you.
You could try a mix of CSS var() / calc() / clamp() / grid ... and relative/absolute positionning to lay the loader over the parent where you need it , if that inspire you :
demo with a few loader within a div sized and the possibility to set an average size to start from, % size based on the width of the parent.
value to reset in the demo is --size ; you may also tune the other --MyVarCss values to your needs.
* {
box-sizing: border-box;
}
:root { /* init for the var() values */
--size: 20;/* value used to set the loader's width and adjust border's width */
--width: calc(var(--size) * 1%);
--widthBorder: calc( clamp(20px, 6vw, 80px) * var(--size) * 0.005);
}
.a,/* for the demo , just a bunch of containers */
.b,
.c,
.d,
.d,
.e {
position: relative;
/* what the parent loader needs to be (absolute/fixed/sticky works too, static not) */
float: left;
border: solid;
margin: 1em;
}
div.a {
--size: 50; /* reset the value used to set the loader's width */
width: 50%;
padding-top: 50%;
}
.b {
--size: 10;/* reset the value used to set the loader's width */
width: 600px;
height: 200px;
}
.c {
--size: 15;/* reset the value used to set the loader's width */
width: 25%;
padding-top: 20%;
}
.d {
--size: 30;/* reset the value used to set the loader's width */
width: 800px;
height: 400px;
}
.e {
--size: 14;/* reset the value used to set the loader's width */
width: 90%;
min-height: 20vh;
}
div {
width: 20%;
padding-top: 20%;
}
/* loader styles */
.loader {
position: absolute;
top: 0;
left: 0;
display: flex;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.loader b {
display: grid;
animation: rotate 3s -1s infinite linear;
border: solid var(--widthBorder) transparent;
padding: calc(var(--widthBorder) / 2);
border-radius: 50%;
border-right-color: #71c491;
border-top-color: #f7941d;
grid-row: 1;
grid-column: 1;
margin: 0;
}
.loader>b {
margin: auto;
width: var(--width);
}
.loader>b:before {
content: "";
padding-top: 100%;
grid-row: 1;
grid-column: 1;
}
.loader b b {
border-right-color: #21409a;
border-top-color: #92278f;
}
.loader b b b {
border-right-color: #13b0e6;
border-top-color: #18244c;
padding: 0;
}
#keyframes rotate {
100% {
transform: rotate(360deg);
}
}
<div class=a>
<div class="loader"><b><b><b></b></b>
</b>
</div>
</div>
<div class=b>
<div class="loader"><b><b><b></b></b>
</b>
</div>
</div>
<div class=c>
<div class="loader"><b><b><b></b></b>
</b>
</div>
</div>
<div class=d>
<div class="loader"><b><b><b></b></b>
</b>
</div>
</div>
<div class=e>
<div class="loader"><b><b><b></b></b>
</b>
</div>
</div>
world {
width: 100%;
height: 100%;
position: absolute;
perspective:800px;
}
bg {
width: 100%;
height: 100%;
position: absolute;
z:1;
transform:rotateX(20deg) rotateY(10deg);
background: url(https://images.pexels.com/photos/1154498/pexels-photo-1154498.jpeg?auto=compress&cs=tinysrgb&h=1250);
}
layer_wrap {
width: 350px;
height: 60px;
position: absolute;
top: 50%;
left: 50%;
mix-blend-mode: overlay;
z:100;
perspective:500px;
transform-style:flat;
backface-visibility:hidden;
}
layer {
position:relative;
background: rgba(0, 122, 255,0.8);
padding: 20px;
color: #fff;
text-transform: uppercase;
font-family: "Arial";
font-size: 30px;
text-align: center;
transformOrigin:50% 50% 50%;
transform:translateX(-50%) translateY(-50%) rotateX(30deg) rotateY(5deg) translateZ(0.001px);
}
<world>
<bg></bg>
<layer_wrap>
<layer>THIS IS A LAYER</layer>
</layer_wrap>
</world>
In the example the bg container and the layer_wrap container must be on the same level to be able to use mix-blend-mode and for other requirements of the Project.
The Results how it is rendered in Safari and Chrome are different. Safari cuts the half of the Layer rendering the whole world in one container. transformStyle preserve-3d or other Tricks were not helpful.
Also a transformZ is not the solution since it would change the size and distance to viewer.
Hope one of you guys have a good solution for this. Cheers !
When we use CSS3 transform: operation1(...) operation2(...), which one is done first?
The first operation done seems to be the one the most on the right., i.e. here operation2 is done before operation1. Just to be sure, is it true?
Note: I have read one thing and its contrary in some places (answers, articles on the internet), thus the question here.
Yes, the first operation done is the one the most on the right., i.e. here operation2 is done before operation1.
This MDN article states indeed:
The transform functions are multiplied in order from left to right, meaning that composite transforms are effectively applied in order from right to left.
Here is the documentation : http://www.w3.org/TR/css-transforms-1/.
Example 1
Here the scaling is done first, and then the translation of 100px vertically (if translation was done first, the scaling would make the translation of 500px!)
#container {
position: absolute;
transform: translate(0,100px) scale(5);
transform-origin: 0 0; }
<div id="container"><img src="https://i.stack.imgur.com/xb47Y.jpg"></img></div>
Example 2
Here the translation is done first, and then the scaling (the scaling done after makes that the translation looks like a 500px-translation!)
#container {
position: absolute;
transform: scale(5) translate(0,100px);
transform-origin: 0 0; }
<div id="container"><img src="https://i.stack.imgur.com/xb47Y.jpg"></img></div>
This has been mentioned in other answers and comments, but not with enough emphasis in my opinion: the short answer is both ways are valid.
It all depends whether you consider your coordinates attached to your element (left to right) or fixed to the page based on the initial element position (right to left).
Here is an article showing the difference with animations (which makes it easier to understand): Chaining transforms.
Here is a snippet showing the animations from the article:
html, body { height: 100%; }
body {
background: #aaa;
color: #000;
font-family: Calibri,Candara,Segoe,"Segoe UI",Optima,Arial,sans-serif;
overflow: hidden;
margin: 0;
}
.info {
text-align: center;
font-family: Consolas,monaco,monospace;
font-size: 20px;
font-weight: bold;
margin-bottom: 4px;
color: #fff;
}
.split { white-space: nowrap; }
.side {
display: inline-block;
width: 50%;
}
.label {
text-align: center;
font-size: 20px;
}
.container {
position: relative;
font-size: 50px;
margin: .6em auto 0;
width: 0; height: 0;
transform: translateX(-1em);
}
.ltr .object {
position: absolute;
left: 0; top: 0;
width: 1em; height: 1em;
margin: -.5em 0 0 -.5em;
background: rgb(114,34,34);
animation: ltrObj 5s infinite;
}
#keyframes ltrObj {
from, 10% { transform: rotate( 0deg) translateX(0em); }
40% { transform: rotate(45deg) translateX(0em); }
70%, to { transform: rotate(45deg) translateX(2em); }
}
.object.shadow {
animation: none;
opacity: .2;
}
.ltr .axes {
position: absolute;
left: .5em; top: .5em;
width: 1em; height: 1em;
color: #111;
box-sizing: border-box;
border-left: 2px solid;
border-top: 2px solid;
}
.ltr .axes::before, .ltr .axes::after {
content: '';
position: absolute;
width: .2em; height: .2em;
box-sizing: border-box;
border-left: 2px solid;
border-top: 2px solid;
transform-origin: top left;
}
.ltr .axes::before { top: 100%; left: 0; margin-left: -1px; margin-top: 1px; transform: rotate(225deg); }
.ltr .axes::after { top: 0; left: 100%; margin-top: -1px; margin-left: 1px; transform: rotate(135deg); }
.rtl .axes {
position: absolute;
left: 0; top: 0;
width: 2.5em; height: 2.3em;
color: #111;
box-sizing: border-box;
border-left: 2px solid;
border-top: 2px solid;
}
.rtl .axes::before, .rtl .axes::after {
content: '';
position: absolute;
width: .2em; height: .2em;
box-sizing: border-box;
border-left: 2px solid;
border-top: 2px solid;
transform-origin: top left;
}
.rtl .axes::before { top: 100%; left: 0; margin-left: -1px; margin-top: 1px; transform: rotate(225deg); }
.rtl .axes::after { top: 0; left: 100%; margin-top: -1px; margin-left: 1px; transform: rotate(135deg); }
.rtl .object {
position: absolute;
left: 0; top: 0;
width: 1em; height: 1em;
margin: -.5em 0 0 -.5em;
background: rgba(100,0,0,0.8);
animation: rtlObj 5s infinite;
}
#keyframes rtlObj {
from, 10% { transform: rotate( 0deg) translateX(0em); }
40% { transform: rotate( 0deg) translateX(2em); }
70%, to { transform: rotate(45deg) translateX(2em); }
}
.helper-mask {
position: absolute;
left: 0; top: 0;
width: 3em; height: 3em;
overflow: hidden;
}
.helper {
position: absolute;
left: 0; top: -2em;
width: 0; height: 2em;
margin-top: 2px;
box-sizing: border-box;
border: 2px solid #00c;
border-left: none;
border-radius: 0 100% 0 0;
transform-origin: bottom left;
animation: helper 5s infinite;
}
#keyframes helper {
from, 10% { width: 0em; transform: rotate( 0deg); }
40% { width: 2em; transform: rotate( 0deg);}
70%, to { width: 2em; transform: rotate(45deg);}
}
<div class="info">rotate(45deg) translateX(2em)</div>
<div class="split">
<div class="side ltr">
<div class="label">Left to Right</div>
<div class="container">
<div class="object shadow"></div>
<div class="object">
<div class="axes"></div>
</div>
</div>
</div>
<div class="side rtl">
<div class="label">Right to Left</div>
<div class="container">
<div class="axes"></div>
<div class="object"></div>
<div class="helper-mask">
<div class="helper"></div>
</div>
</div>
</div>
</div>
Whether the actual implementation uses left to right or right to left is irrelevant, both are equally valid when creating an animation, as long as you keep the difference in mind.
Transforms are performed left to right. Transforms correspond to matrix operations, and these are performed left to right.
There is intuition behind it, it's not just that this is literally in the spec as a normative rule (point 3 here: https://drafts.csswg.org/css-transforms-1/#transform-rendering)
Here's a pen to try: https://codepen.io/monfera/pen/YLWGrM
Explanation:
Each transform step establishes its own coordinate system. So
transform: translateX(500px);
establishes a new coordinate system 500px along the X axis of its parent, and the element will be rendered there.
Similarly,
background-color: blue;
transform: translateX(500px) rotate(60deg);
first establishes a new coordinate system 500px along the X axis (to the right) of its parent, and only then, within that (translated, but it's now irrelevant) coordinate system does it perform the rotation. So it'll be a shape that's 500px to the right, and rotated in place (around the so-called transform-origin which is interpreted in the local coordinate system, and the default 50% 50% for rotation means, rotation around the center of the rectangle, but it's an aside).
The reverse order
background-color: orange;
transform: rotate(60deg) translateX(500px);
first establishes a new coordinate system that's rotated 60 degrees relative to the parent, and then translates 100px along the X axis of the now rotated coordinate system, in a direction that is not actually to the right from the global viewpoint of the document (or user). So, in this case, it's as if you first rotated the paper, and then slid the shape 500 units along the side of the paper (from the origin, which is in this case the top left corner).
For a more advanced discussion, and understanding of how it's possible to intuitively understand it for both directions, check out Composing Transformations - CSS transforms follow the post-multiplication model, so look for the page with the heading "Think of transformations as transforming the local coordinate frame" (illustrations seem to be a little off though)
It applies the leftmost transformation first.
As you can see in the image above, the first transformation takes a longer distance as compared to the second. The reason is the first example undergoes scale first and then it takes the distance specified by translate based on its new width on the x-axis. Because it is wider now, 50% will cause it to take a longer distance. The measure specified by 50% is calculated by taking half of the width of itself.
the site I cited from
I just created a demo of a 3d room in HTML using CSS transforms. I made a 200x200 DIV for a back wall, leaving it in that position. Then I made a left wall starting in the same size and position, then added
transform: translate3d(-100px,0px,100px) rotateY(90deg).
Then I made a right wall and added
transform: translate3d( 100px,0px,100px) rotateY(90deg).
This created the room correctly. But this is with version 13 of Safari. Originally I tried to list the rotation step first, but the wall was in an odd position. So I'm seeing a right-to-left behavior.
I see an annoying bug happening at the end of the animation loop, where it blinks for a fraction of a second, and makes the animation look choppy.
Here is the pen.
SCSS:
$dim: 60px;
$mult: 1.8;
$color: #bada55;
body, html {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: #36a;
}
.circle {
background-color: $color;
border-radius: 50%;
width: $dim;
height: $dim;
position: relative;
}
.circle:before {
content: "";
display: table;
background-color: $color;
border-radius: 50%;
position: absolute;
animation: notification 800ms ease-in infinite;
}
#keyframes notification{
0% {
opacity: 1;
width: $dim;
height: $dim;
left: 0;
top: 0;
}
90% {
opacity: 0;
left: -(($dim * $mult) - $dim)/2;
top: -(($dim * $mult) - $dim)/2;
width: $dim * $mult;
height: $dim * $mult;
}
100% {
opacity: 0;
width: $dim;
height: $dim;
left: 0;
top: 0;
}
}
I've tried adding another frame, but it doesn't really remove it. I also tried hiding the before div afterwards, but doesn't work. Neither with z-index.
Any suggestions?
The problem with the "choppy" behaviour is: You change the dimensions and the positioning of your element. This forces the browser to re-render the element (in this case your :before pseudo-element. This makes the calculation for the browser way harder than it has to be.
Instead of alternating the dimensions, you could use a simple transform. Transforming elements does not force a re-rendering of the element and therefore performs way smoother. Also, it makes the code a bit easier as well. I forked your CodePen and used the transform instead of the dimensions: http://codepen.io/HerrBertling/pen/NbrPJb
It's certainly not perfect concerning the animation and the dimensions, but it should run way smoother.
(Check e.g. this Medium post for further info about the browser's behaviour: https://medium.com/outsystems-experts/how-to-achieve-60-fps-animations-with-css3-db7b98610108#.sykm5uqyv)
I'm working on a site with a knotted rope-style bar that expands to show more information on hover, and I'm having issues getting the animation to look right. (Here's a staging link: http://couchcreative.co/tcc/).
Currently, the bar for the first step will move down to the bottom of the box before it animates upwards to its new position, while I want it to just move up to its new position on hover without starting at the bottom of the hover box. Can anyone help explain why this is happening? The relevant CSS starts with the ".look" class.
Apologies if I'm not explaining this right, but hopefully when you visit the site you'll see what I mean about the animation looking a bit… off. Thanks for the help!
I would rework your HTML structure to make it more semantic and less repetitious.
Demo: http://jsfiddle.net/krmn4/5/
HTML:
<a href="/testicularcancer/" class="look">
<figure><img src="http://couchcreative.co/tcc/img/look.png" /></figure>
<div class="bar"></div>
<div class="off">
<h4>Look</h4>
</div>
<div class="on">
<h4>Relax your scrotum.</h4>
<p>Check your testicles just after you’ve had a bath or shower, when the muscles in the scrotum are relaxed, making it easier for you to feel any lumps, growths or tenderness. Stand in front of the mirror. Look for any swelling on the skin of your scrotum.</p>
<span>Learn More</span>
</div>
</a>
CSS:
.look {
position: absolute;
bottom: 0;
left: 0;
width: 235px;
overflow: hidden;
/* optional styling */
color: #000;
text-align: center;
text-decoration: none;
}
.look h4 {
/* optional styling */
line-height: 48px;
font-size: 20px;
font-weight: bold;
}
.look .bar {
height: 48px;
background: url(http://couchcreative.co/tcc/img/step_1.png) 0 0 repeat-x;
margin: -24px 0 0; /* half of height */
/* necessary so figure img doesn't overlap */
position: relative;
z-index: 1;
}
.look figure,
.look .off,
.look .on {
-webkit-transition: all 300ms linear;
-moz-transition: all 300ms linear;
transition: all 300ms linear;
height: 0;
opacity: 0;
overflow: hidden;
}
.look figure {
/* optional styling */
background-color: #b2d5e6;
padding: 12px;
margin: 0;
}
.look .off {
height: 48px;
opacity: 1;
}
/* hover state */
.look:hover .off {
height: 0;
opacity: 0;
}
.look:hover figure {
height: 120px; /* or however tall it needs to be */
opacity: 1;
}
.look:hover .on {
height: 220px; /* or however tall it needs to be */
opacity: 1;
}