Having some trouble building a CSS3 loader using keyframe animations.
The loader consists of 4 boxes that animate going up and down. The issue I'm having is that when the animation is supposed to stop, the boxes jump to the initial position. The behaviour I'm looking for is: loader is animating infinitely until loading is done, at which point it should animate to the initial position and stop, sort of like having animation-iteration-count: infinite and changing it to animation-iteration-count: 1 to stop the animation. (which doesn't work btw).
See this fiddle to see what I mean: https://jsfiddle.net/cazacuvlad/qjmhm4ma/ (when clicking the stop button, the boxes should animate to the initial position, instead of jumping)
The basic setup is:
<div class="loader-wrapper"><span></span><span></span><span></span><span></span></div>
To start the loader, I'm adding a loader-active class that contains the animation to the loader-wrapper.
LESS:
.loader-wrapper {
&.loader-active {
span {
.animation-name(loader);
.animation-duration(1200ms);
.animation-timing-function(ease-in-out);
.animation-play-state(running);
.animation-iteration-count(infinite);
&:nth-child(1) {
}
&:nth-child(2) {
.animation-delay(300ms);
}
&:nth-child(3) {
.animation-delay(600ms);
}
&:nth-child(4) {
.animation-delay(900ms);
}
}
}
}
I've tried adding the animation to the spans in the loader-wrapper class w/o loader-active and playing around with animation-iteration-count and animation-play-state when loader-active is added without any luck.
Found a pretty simple workaround. Still not pure CSS, it involves a bit of JS, but it works well.
Updated fiddle: https://jsfiddle.net/cazacuvlad/qjmhm4ma/2/
What I did was to move the loader-active class to each span (instead of the wrapper), listen to the animationiteration event on each span and stop the animation then.
$('.loader-wrapper span').on('animationiteration webkitAnimationIteration', function () {
var $this = $(this);
$this.removeClass('loader-active');
$this.off();
});
This basically stops the animation at the very end of an iteration cycle.
Updated LESS
.loader-wrapper {
span {
&.loader-active {
.animation-name(loader);
.animation-duration(1200ms);
.animation-timing-function(ease-in-out);
.animation-play-state(running);
.animation-iteration-count(infinite);
&:nth-child(1) {
}
&:nth-child(2) {
.animation-delay(300ms);
}
&:nth-child(3) {
.animation-delay(600ms);
}
&:nth-child(4) {
.animation-delay(900ms);
}
}
}
}
You can also add a class which specifies the iteration count to stop the infinite loop. The advantage of this approach is that you can change the duration and timing-function which can be nice for easing out some animation (Like a rotating logo for example).
.animate-end {
animation-iteration-count: 3;
animation-duration: 1s;
animation-timing-function: ease-out;
}
We can add this class with js and it will now stop the animation at count 3.
document.querySelector(".loader-wrapper").classList.add("animate-end");
But you can also end the current itertion by counting it and change the style of the element dynamcly with Js.
let iterationCount = 0;
document.querySelector(".loader-wrapper span").addEventListener('animationiteration', () => {
//count iterations
iterationCount++;
});
yourElement.style.animationIterationCount = iterationCount + 1;
Here is a demo with your code:
document.querySelector("#start_loader").addEventListener("click", function(){
document.querySelector(".loader-wrapper").classList.add("loader-active");
})
let iterationCount = 0;
document.querySelector(".loader-wrapper span").addEventListener('animationiteration', () => {
//count iterations
iterationCount++;
console.log(`Animation iteration count: ${iterationCount}`);
});
document.querySelector("#stop_loader").addEventListener("click", function(){
//For some animation it can be nice to change the duration or timing animation
document.querySelector(".loader-wrapper").classList.add("animate-end");
//End current iteration
document.querySelectorAll(".loader-wrapper span").forEach(element => {
element.style.animationIterationCount = iterationCount + 1;
});
//Remove Classes with a timeout or animationiteration event
setTimeout(() => {
document.querySelector(".loader-wrapper").classList.remove("loader-active");
document.querySelector(".loader-wrapper").classList.remove("animate-end");
}, 1200);
})
#-moz-keyframes 'loader' {
0% {
-moz-transform: translate3d(0, 0, 0);
}
50% {
-moz-transform: translate3d(0, -10px, 0);
}
100% {
-moz-transform: translate3d(0, 0, 0);
}
}
#-webkit-keyframes 'loader' {
0% {
-webkit-transform: translate3d(0, 0, 0);
}
50% {
-webkit-transform: translate3d(0, -10px, 0);
}
100% {
-webkit-transform: translate3d(0, 0, 0);
}
}
#-o-keyframes 'loader' {
0% {
-o-transform: translate3d(0, 0, 0);
}
50% {
-o-transform: translate3d(0, -10px, 0);
}
100% {
-o-transform: translate3d(0, 0, 0);
}
}
#keyframes 'loader' {
0% {
transform: translate3d(0, 0, 0)
}
50% {
transform: translate3d(0, -10px, 0)
}
100% {
transform: translate3d(0, 0, 0)
}
}
.loader-wrapper {
margin-bottom: 30px;
}
.loader-wrapper.loader-active span {
-webkit-animation-name: loader;
-moz-animation-name: loader;
-ms-animation-name: loader;
-o-animation-name: loader;
animation-name: loader;
-webkit-animation-duration: 1200ms;
-moz-animation-duration: 1200ms;
-ms-animation-duration: 1200ms;
-o-animation-duration: 1200ms;
-webkit-animation-timing-function: ease-in-out;
-moz-animation-timing-function: ease-in-out;
-ms-animation-timing-function: ease-in-out;
-o-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
-webkit-animation-play-state: running;
-moz-animation-play-state: running;
-ms-animation-play-state: running;
-o-animation-play-state: running;
animation-play-state: running;
-webkit-animation-iteration-count: infinite;
-moz-animation-iteration-count: infinite;
-ms-animation-iteration-count: infinite;
-o-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.loader-wrapper.animate-end span {
/* Works great for some animations */
/*animation-iteration-count: 1;*/
/*animation-duration: 1s;*/
}
.loader-wrapper.loader-active span:nth-child(1) {}
.loader-wrapper.loader-active span:nth-child(2) {
animation-delay: 300ms;
}
.loader-wrapper.loader-active span:nth-child(3) {
animation-delay: 600ms;
}
.loader-wrapper.loader-active span:nth-child(4) {
animation-delay: 900ms;
}
.loader-wrapper span {
margin-right: 5px;
display: inline-block;
vertical-align: middle;
background: black;
width: 10px;
height: 10px;
}
<div class="loader-wrapper"><span></span><span></span><span></span><span></span></div>
<button id="start_loader">Start</button>
<button id="stop_loader">Stop</button>
Related
I am new to css and javascript. I am trying to animate the spans within an h2 to fall from the top of the page on load and then on hover to have a bouncy effect. I was able to make them happen both BUT now the problem is that the animation delay I intended for the fall animation are applying to the bounce animation as well. When I add the animation name to the animation delay all the letter fall at the same time. What am I missing?
I tried specifying the animation name for the animation delay but it didn't work. When I add the animation name to the animation delay all the letter fall at the same time. What am I missing?. And I also tried to change the animation delay in JS after the first animation happens but wasn't able to figure out.
This is my html
<h2 class="test">
<span class="q">T</span><span class="q">a</span><span class="q">d</span><span
class="q">a</span><span class="q">a</span><span class="q">k</span><span class="q">i</span>
</h2>
<h2 class="test2">
<span class="q2">K</span><span class="q2">u</span><span class="q2">w</span><span
class="q2">a</span><span class="q2">y</span><span class="q2">a</span><span
class="q2">m</span><span class="q2">a</span>
</h2>
This is the CSS
span {
color: black;
font-size: 1em;
display: flex;
margin-bottom: 0;
padding-bottom: 0;
}
.onLoadAnimation {
position: relative;
transform: translateY(-100vh);
animation: fall 1s forwards;
animation-iteration-count: 1;
}
#keyframes fall {
100% {
transform: translateY(0);
}
}
.test span:nth-child(2) {
animation-delay: 0.1s;
}
.test span:nth-child(3) {
animation-delay: 0.2s;
}
.test span:nth-child(4) {
animation-delay: 0.3s;
}
.test span:nth-child(5) {
animation-delay: 0.4s;
}
.test span:nth-child(6) {
animation-delay: 0.5s;
}
.test span:nth-child(7) {
animation-delay: 0.6s;
}
.test2 span:nth-child(1) {
animation-delay: 0.1s;
}
.test2 span:nth-child(2) {
animation-delay: 0.12s;
}
.test2 span:nth-child(3) {
animation-delay: 0.3s;
}
.test2 span:nth-child(4) {
animation-delay: 0.4s;
}
.test2 span:nth-child(5) {
animation-delay: 0.5s;
}
.test2 span:nth-child(6) {
animation-delay: 0.6s;
}
.test2 span:nth-child(7) {
animation-delay: 0.7s;
}
.test2 span:nth-child(8) {
animation-delay: 0.8s;
}
.spanHoverEffect {
color: #0f4c5c;
animation: animate 0.6s;
}
#keyframes animate {
25% {
transform: scale(0.8, 1.3);
}
50% {
transform: scale(1.1, 0.8);
}
75% {
transform: scale(1.1, 0.8);
}
}
This is the JS
let letters = document.getElementsByClassName("q");
let lettersTwo = document.getElementsByClassName("q2");
window.onload = () => {
for (l of letters) {
l.classList.toggle('onLoadAnimation');
l.addEventListener('mouseover', function () {
this.classList
.remove('onLoadAnimation')
.add('spanHoverEffect')
});
l.addEventListener('mouseout', function () {
this.classList
.remove('onLoadAnimation')
.remove('spanHoverEffect')
});
}
for (l of lettersTwo) {
l.classList.toggle('onLoadAnimation');
l.addEventListener('mouseover', function () {
this.classList
.remove('onLoadAnimation')
.add('spanHoverEffect')
});
l.addEventListener('mouseout', function () {
this.classList
.remove('onLoadAnimation')
.remove('spanHoverEffect')
});
}
};
In this fiddle, I made two changes. The letters fall in sequence in Chrome, Firefox and Edge and the animation delay is not present when moused over in any of those browsers.
I added the onLoadAnimation class to the CSS for each letter.
.test .onLoadAnimation:nth-child(2) {
animation-delay: 0.1s;
}
And I removed the chained changes to classList, which is not something that classList supports in any of the browsers mentioned above.
l.classList.remove('onLoadAnimation')
l.classList.add('spanHoverEffect')
The reason I mention web browsers is that "this.classList.remove('onLoadAnimation').add('spanHoverEffect')" causes an error in all of those browsers, because remove returns undefined, so I am wondering if you are using a less-used browser that may work differently.
I'm trying to animate a list of elements to slide in one after the other when rendered into the page.
Everything works well in Chrome and Firefox, even in Safari 11 work well, but safari 12 is not doing the animation well.
As shown in the following image, all items should be aligned to the top when the animation is completed, but for some reason only in Safari 12, the items are randomly rendered. In addition to that, the mouse over on the button is off.
You can take a look at the problem here: https://codepen.io/crysfel/pen/GwoQxE (Make sure to open the link with safari 12)
I think the css is pretty standard:
#keyframes slideIn {
from {
opacity: 0;
transform: translateY(60px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide-in {
opacity:0;
transform: translateY(60px);
animation: slideIn ease 1;
animation-fill-mode: forwards;
animation-duration: 175ms;
}
And a simple javascript to animate the items one after the other:
function animateIn() {
$('ul li').each(function(index) {
$(this).removeClass('slide-in');
setTimeout(() => {
$(this).addClass('slide-in');
}, 50 * index)
})
}
$(() => {
animateIn();
$('#show').click(function() {
animateIn();
});
});
EDIT:
I've fixed the issue: It turns out all I had to do was removing transform: translateY(60px); from slide-in. Apparently safari was using that style at the end of the animation overwriting the final value. It's very weird, because visually looks wrong but the active zones and all are fine.
You probably need to add a prefix to keyframes and animation for safari. Use something like this:
#keyframes slideIn {
from {
opacity: 0;
transform: translateY(60px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
#-webkit-keyframes slideIn {
from {
opacity: 0;
-webkit-transform: translateY(60px);
}
to {
opacity: 1;
-webkit-transform: translateY(0);
}
}
.slide-in {
opacity:0;
transform: translateY(60px);
-webkit-transform: translateY(60px);
animation: slideIn ease 1;
-webskit-animation: slideIn ease 1;
animation-fill-mode: forwards;
-webkit-animation-fill-mode: forwards;
animation-duration: 175ms;
-webkit-animation-duration: 175ms;
}
A helpful tool to use is shouldiprefix.com
So I have got different animations made in CSS, though the problem is that they start right away when the page loads (ofcourse). I do not want this though. Is there a way in Vanilla JavaScript to get the animation to fire up only when it is in the viewport?
I have searched in a lot of places, but I either find a plugin I need to use or jQuery.
HTML:
<div class="introduction">
<h1>I can do the following for you:</h1>
<ul>
<li>Create a custommade, new website.</li>
<li>Code a PSD template into a working website.</li>
<li>Rework an outdated website.</li>
<li>Clean up messy code of a website.</li>
</ul>
</div>
CSS:
#keyframes showOnLoad {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.introduction li {
list-style-type: none;
margin-bottom: 5px;
text-align: center;
opacity: 0;
-webkit-animation: showOnLoad;
animation: showOnLoad;
-webkit-animation-duration: 2s;
animation-duration: 2s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
}
.introduction li:nth-child(2) {
-webkit-animation-delay: 1s;
animation-delay: 1s;
}
.introduction li:nth-child(3) {
-webkit-animation-delay: 2s;
animation-delay: 2s;
}
.introduction li:nth-child(4) {
-webkit-animation-delay: 3s;
animation-delay: 3s;
}
This is the code you need.
window.addEventListener("scroll", onScroll);
function onScroll() {
for (var item of document.querySelectorAll(".introduction li")) {
elementVisible(item);
}
}
function elementVisible(el) {
let top = el.offsetTop;
let height = el.offsetHeight;
let bottom = top + height;
let IsOverBottom = top > (window.pageYOffset + window.innerHeight);
let IsBeforeTop = bottom < window.pageYOffset;
if (!IsOverBottom && !IsBeforeTop) {
el.classList.add("show");
}
}
And a bit of CSS
#keyframes slideIn {
0% {
opacity: 0;
transform: translateX(100%);
}
100% {
opacity: 1;
transform: translateX(0%);
}
}
.show {
animation: slideIn 5s ease-in-out;
}
This is a basic implementation but it gets you closer.
http://jsbin.com/hetapaj/1/edit?css,js,output
I'm trying to create a function with a callback that will execute at the end.
Basically I'm adding a css class to a div that will animate it, and want to remove that div when the animation terminates. This is how I'm doing it :
this.animate = function(cssClass, callback) {
$(div).addClass(cssClass);
callback();
}
And when calling this function :
this.animate('animated', function () {
$(div).remove();
}
However, I noticed that the div got removed before the animation fires.
EDIT : here is the css class :
#-webkit-keyframes fadeOutUp {
0% {
opacity: 1;
-webkit-transform: translateY(0);
}
100% {
opacity: 0;
-webkit-transform: translateY(-20px);
}
}
#-moz-keyframes fadeOutUp {
0% {
opacity: 1;
-moz-transform: translateY(0);
}
100% {
opacity: 0;
-moz-transform: translateY(-20px);
}
}
#-o-keyframes fadeOutUp {
0% {
opacity: 1;
-o-transform: translateY(0);
}
100% {
opacity: 0;
-o-transform: translateY(-20px);
}
}
#keyframes fadeOutUp {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(-20px);
}
}
.animated.fadeOutUp {
-webkit-animation-name: fadeOutUp;
-moz-animation-name: fadeOutUp;
-o-animation-name: fadeOutUp;
animation-name: fadeOutUp;
-webkit-animation-duration: 0.25s;
-moz-animation-duration: 0.25s;
-o-animation-duration: 0.25s;
animation-duration: 0.25s;
}
EDIT : (solution)
I solved this problem using the one() method as follows :
$(div).one('webkitAnimationEnd oanimationend msAnimationEnd animationend',
function(e) {
// code to execute after animation ends
$(this).remove();
});
Hope it can help others facing the same problem.
I recommend you to use a jQuery animation function, which has a complete event callback you can hook up when the animation completes. Pass a set of CSS rules over your animation function.
this.animate = function(cssRules, callback) {
$(div).animate(cssRules, 1000, "linear", function() {
console.log('animation completed');
callback(); //executes your callback here
});
}
cssRules are the rules defined in your argument cssClass; it would look like:
{
height: 200,
width: 400,
opacity: 0.5
}
Start with something easy; change only one css rule e.g. font-size or height. There may have a way to animate on the basis of a css class, Google it, you never know.
See the documentation at the very end of the page (about the complete callback): http://api.jquery.com/animate/
Hope this helps,
R.
I have some keyframe animations in my css file. There is already an animation-delay specified.
The wrapper div has the attribute data-delay.
I want to get the animation-delay in the css file and add the value of data-delay to it.
Then i want that the animation start with the new delay.
I tried ele[i].style.animationDelay.
But it seems that this returns null until I set a value to it.
If I set ele[i].style.animationDelay = '5s' the animation still runs with the delay of the css file.
HTML
<div id="wrapper" data-delay="2s" >
<h1 id="hi">Hi</h1>
<h1 id="name">test!</h1>
</div>
CSS
body { font-size: 300%; }
#wrapper h1 { position: absolute; }
#hi {
transform: translate(-200px, 100px);
animation-name: hi;
animation-duration: .5s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 0s;
}
#name {
transform: translate(-200px, 150px);
animation-name: name;
animation-duration: .5s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
#keyframes hi{
100% { transform: translate(50px, 100px) };
}
#keyframes name{
100% { transform: translate(50px, 150px) };
}
JS
var wrapper = document.getElementById('wrapper');
var ele = wrapper.children;
var delay = wrapper.getAttribute('data-delay');
for (var i=0;i<ele.length;i++) {
alert(ele[i].style.animationDelay);
ele[i].style.animationDelay = delay;
alert(ele[i].style.animationDelay);
}
http://jsfiddle.net/FHuKN/4/
I've only tested this on Mac 10.8 Chrome 25, Safari 6.0, and FF 18.0.
Sounds like the main thing you wanted to do was add the data-delay value to whatever existing animation delay was applied to the elements.
HTML - unchanged
<div id="wrapper" data-delay="5.1s" >
<h1 id="hi">Hi</h1>
<h1 id="name">test!</h1>
</div>
CSS - Vendor prefixes and initial keyframes (0%) were added.
body { font-size: 300%; }
#wrapper h1 { position: absolute; }
#hi {
-webkit-transform: translate(-200px, 100px);
-webkit-animation-name: hi;
-webkit-animation-duration: .5s;
-webkit-animation-timing-function: linear;
-webkit-animation-fill-mode: forwards;
-webkit-animation-delay: 2.1s;
-moz-transform: translate(-200px, 100px);
-moz-animation-name: hi;
-moz-animation-duration: .5s;
-moz-animation-timing-function: linear;
-moz-animation-fill-mode: forwards;
-moz-animation-delay: 2.1s;
transform: translate(-200px, 100px);
animation-name: hi;
animation-duration: .5s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 2.1s;
}
#name {
-webkit-transform: translate(-200px, 150px);
-webkit-animation-name: name;
-webkit-animation-duration: .5s;
-webkit-animation-timing-function: linear;
-webkit-animation-fill-mode: forwards;
-webkit-animation-delay: 3.1s;
-moz-transform: translate(-200px, 150px);
-moz-animation-name: name;
-moz-animation-duration: .5s;
-moz-animation-timing-function: linear;
-moz-animation-fill-mode: forwards;
-moz-animation-delay: 3.1s;
transform: translate(-200px, 150px);
animation-name: name;
animation-duration: .5s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 3.1s;
}
#-moz-keyframes hi{
0% { -moz-transform: translate(-200px, 100px); }
100% { -moz-transform: translate(50px, 100px); }
}
#-webkit-keyframes hi {
0% { -webkit-transform: translate(-200px, 100px); }
100% { -webkit-transform: translate(50px, 100px); }
}
#keyframes hi{
0% { transform: translate(-200px, 100px); }
100% { transform: translate(50px, 100px); }
}
#-moz-keyframes name {
0% { -moz-transform: translate(-200px, 150px); }
100% { -moz-transform: translate(50px, 150px); }
}
#-webkit-keyframes name {
0% { -webkit-transform: translate(-200px, 150px); }
100% { -webkit-transform: translate(50px, 150px); }
}
#keyframes name {
0% { transform: translate(-200px, 150px); }
100% { transform: translate(50px, 150px); }
}
JAVASCRIPT
On an element, the style property doesn't hold all the style information because it only represents what is being set directly on the element via the style attribute. MDN
window.getComputedStyle() seems to work pretty well.
Juggling the prefixed properties is a little clunky, but it worked in the browsers I tested with.
(function(undefined) {
var wrapper = document.getElementById('wrapper'),
elms = wrapper.children,
delay = wrapper.getAttribute('data-delay'),
prop,
styl,
cur,
i;
delay = !delay ? 0 : Number(delay.replace(/[^\d\.]/g, ''));
if (!elms.length) {
return;
}
styl = window.getComputedStyle(elms[0]);
if (styl.getPropertyValue('animation-delay')) {
prop = 'animation-delay';
} else if (styl.getPropertyValue('-webkit-animation-delay')) {
prop = '-webkit-animation-delay';
} else if (styl.getPropertyValue('-moz-animation-delay')) {
prop = '-moz-animation-delay';
} else {
console.log('unable to find prop');
return;
}
// console.log('prop', prop);
for (i = 0; i < elms.length; i++) {
styl = window.getComputedStyle(elms[i]);
cur = styl.getPropertyValue(prop);
cur = Number(cur.replace(/[^\d\.]/g, ''));
elms[i].style.setProperty(prop, (cur + delay) + 's');
console.log('delay: ' + cur + 's -> ' + (cur + delay) + 's')
}
})();
http://jsfiddle.net/FHuKN/11/
Old Firefoxes (at least up to 16), Opera before migrating to Blink (<15), IE at least 10 - will not redraw the animation if we just change some of its attributes like (-prefix-)animation-delay. In order to make them do so, we have to apply some depper tricks.
The first will be removing and reinserting the animated element. And - for the sake of Webkit - applying all the style changes on it.
Just change the code from #tiffon's fiddle
elms[i].style.setProperty(prop, (cur + delay) + 's');
To
var newEl = elms[i].cloneNode(true);
newEl.style.setProperty(prop, (cur + delay) + 's', '');
elms[i].parentNode.replaceChild(newEl,elms[i]);
http://jsfiddle.net/FHuKN/28/
Remove the class name ar the attribute value, which the animation is attached to, wait for a bit (setTimeout) of - better - trigger the reflow (say, element.offsetWidth = element.offsetWidth;), and add the class name again.
http://jsfiddle.net/FHuKN/29/
The idea is not mine, all credit goes to Chris Coyer