I have two CSS #keyframe animations applied to the same element. One that fires on :hover and the other that fires on mouse out, both applied with CSS.
I was curious to know if there was a way to detect the end of a selected keyframe animation rather than it being attached to the element and firing twice?
if there was a way to detect the end of a selected keyframe animation
If your intention is to detect the ending of a keyframe animation itself instead of detect end of every keyframe then, yes, it can be done using the animationend event. This event is fired every time any animation that is attached to the element is completed and the context info has one parameter named animationName using which we can find which animation had ended.
The animationName parameter is important because when multiple animations would be applied to the same element like in your case then you'd need to know which animation had actually ended because this event would get fired at the end of every animation.
Using vanilla JS:
window.onload = function() {
var elm = document.querySelector('.animate');
var op = document.querySelector('.output');
elm.addEventListener('animationend', function(e) { /* this is fired at end of animation */
op.textcontent = 'Animation ' + e.animationName + ' has ended';
});
elm.addEventListener('animationstart', function(e) { /* this is fired at start of animation */
op.textcontent = 'Animation ' + e.animationName + ' has started';
});
}
.animate {
height: 100px;
width: 100px;
margin: 10px;
border: 1px solid red;
animation: shake-up-down 2s ease;
}
.animate:hover {
animation: shake-left-right 2s ease forwards;
}
#keyframes shake-up-down {
0% {
transform: translateY(0px);
}
25% {
transform: translateY(10px);
}
75% {
transform: translateY(-10px);
}
100% {
transform: translateY(0px);
}
}
#keyframes shake-left-right {
0% {
transform: translateX(0px);
}
25% {
transform: translateX(10px);
}
75% {
transform: translateX(-10px);
}
100% {
transform: translateX(0px);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class='animate'></div>
<div class='output'></div>
Using jQuery:
$(document).ready(function() {
var elm = $('.animate');
var op = $('.output');
elm.on('animationend', function(e) { /* fired at the end of animation */
op.html('Animation ' + e.originalEvent.animationName + ' has ended');
});
elm.on('animationstart', function(e) { /* fired at the start of animation */
op.html('Animation ' + e.originalEvent.animationName + ' has started');
});
});
.animate {
height: 100px;
width: 100px;
margin: 10px;
border: 1px solid red;
animation: shake-up-down 2s ease;
}
.animate:hover {
animation: shake-left-right 2s ease forwards;
}
#keyframes shake-up-down {
0% {
transform: translateY(0px);
}
25% {
transform: translateY(10px);
}
75% {
transform: translateY(-10px);
}
100% {
transform: translateY(0px);
}
}
#keyframes shake-left-right {
0% {
transform: translateX(0px);
}
25% {
transform: translateX(10px);
}
75% {
transform: translateX(-10px);
}
100% {
transform: translateX(0px);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class='animate'></div>
<div class='output'></div>
In the above snippet, you can see how the .output div's content indicates the name of the animation that is ended after each animation completes.
Note: CSS animations still need vendor prefixes in some browsers/versions. To be on the safer side, you need to listen for the prefixed versions of the animationend event also.
Try this example:
function whichAnimationEvent(){
var t,
el = document.createElement("fakeelement");
var animations = {
"animation" : "animationend",
"OAnimation" : "oAnimationEnd",
"MozAnimation" : "animationend",
"WebkitAnimation": "webkitAnimationEnd"
}
for (t in animations){
if (el.style[t] !== undefined){
return animations[t];
}
}
}
var animationEvent = whichAnimationEvent();
$(".button").click(function(){
$(this).addClass("animate");
$(this).one(animationEvent,
function(event) {
// Do something when the animation ends
});
});
Related
The snippet below shows a spinning circle. Every 1 second I want this circle to double in size and then shrink back to it's original size(to look like a heartbeat). The way I am attempting to do this is by creating a timer in javascript so that every one second, the class which causes the grow effect is removed from the circle, and then immediately added back on. I was hoping that having the class added back on after being removed would trigger the animation but I guess not. Right now the "heartbeat" only happens once.
Also I would like to have the circle spinning at constant speed if that's possible. Right now the circle really slows down at the end, and starts a little bit slow.
// set timeout
let tid = setTimeout(mycode, 1000);
function mycode() {
// do some stuff...
let ic = document.getElementById('inner-circle')
ic.classList.remove('heartbeat')
ic.classList.add('heartbeat')
tid = setTimeout(mycode, 1000); // repeat myself
}
function abortTimer() { // to be called when you want to stop the timer
clearTimeout(tid);
}
#spinning-circle {
animation-name: spinning-circle;
animation-duration: 10s;
animation-iteration-count: infinite;
width: 40px;
height: 40px;
}
.heartbeat {
width: 100%;
height: 100%;
animation-name: heartbeat;
animation-duration: 0.15s;
animation-iteration-count: 2;
animation-direction: alternate;
animation-fill-mode: both;
}
#inner-circle img {
width: 100%;
height: auto;
}
#-webkit-keyframes heartbeat {
100% {
transform: scale(2,2);
-webkit-transform: scale(2,2);
}
}
#-webkit-keyframes spinning-circle {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
<div id="spinning-circle">
<div id='inner-circle'>
<img src="http://i.stack.imgur.com/WbNlQ.jpg">
</div>
</div>
use setInterval() and clearInterval() instead of setTimeout(), and remove the setTimeout() inside the function mycode()
// set timeout
let tid = setInterval(mycode, 1000);
function mycode() {
// do some stuff...
let ic = document.getElementById('inner-circle')
ic.classList.remove('heartbeat')
ic.classList.add('heartbeat')
}
function abortTimer() { // to be called when you want to stop the timer
clearInterval(tid);
}
and for the animation speed add animation-timing-function: linear; to .heartbeat {} and #spinning-circle {}
You don't need javascript at all:
#spinning-circle {
margin-top: 40px;
margin-left: 40px;
animation: spinning-circle linear 10s infinite;
width: 40px;
height: 40px;
overflow: visible;
}
#inner-circle {
animation: heartbeat 1s infinite;
}
#inner-circle img {
width: 100%;
height: auto;
}
#keyframes heartbeat {
0% {
transform: scale(1);
}
25% {
transform: scale(2);
}
50% {
transform: scale(1);
}
100% {
transform: scale(1);
}
}
#keyframes spinning-circle {
0% {
transform: rotate(0turn);
}
100% {
transform: rotate(-1turn);
}
}
<div id="spinning-circle">
<div id='inner-circle'>
<img src="http://i.stack.imgur.com/WbNlQ.jpg">
</div>
</div>
I'm trying to reproduce a specific animation each time a button is pressed.
Specifically, I'm using jQuery and the animate.css library for this effect: on button click, a class is added (two classes to be precise: fadeInDown animated) to the element I want to animate.
The animation does work correctly, but only once. Why is this?
Fiddle: http://jsfiddle.net/jqm4vjLj/2/
I want the animation to reset every time I click the button, even if it is halfway through completion from a previous click. It should also work whenever I click the button, not only once.
JS:
$("#button").click(function () {
$("#button").removeClass();
$("#button").addClass("fadeInDown animated");
});
Classes from animate.css:
#-webkit-keyframes fadeInDown {
0% {
opacity: 0;
-webkit-transform: translateY(-20px);
transform: translateY(-20px);
}
100% {
opacity: 1;
-webkit-transform: translateY(0);
transform: translateY(0);
}
}
#keyframes fadeInDown {
0% {
opacity: 0;
-webkit-transform: translateY(-20px);
-ms-transform: translateY(-20px);
transform: translateY(-20px);
}
100% {
opacity: 1;
-webkit-transform: translateY(0);
-ms-transform: translateY(0);
transform: translateY(0);
}
}
.fadeInDown {
-webkit-animation-name: fadeInDown;
animation-name: fadeInDown;
}
.animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
A much safer approach will be use the animation end event like
$("#button").click(function() {
$("#button").addClass("fadeInDown animated").one('animationend webkitAnimationEnd oAnimationEnd', function() {
$("#button").removeClass();
});
});
#button {
height: 30px;
width: 120px;
border: 1px solid black;
}
.animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
}
#-webkit-keyframes fadeInDown {
0% {
opacity: 0;
-webkit-transform: translateY(-20px);
transform: translateY(-20px);
}
100% {
opacity: 1;
-webkit-transform: translateY(0);
transform: translateY(0);
}
}
#keyframes fadeInDown {
0% {
opacity: 0;
-webkit-transform: translateY(-20px);
-ms-transform: translateY(-20px);
transform: translateY(-20px);
}
100% {
opacity: 1;
-webkit-transform: translateY(0);
-ms-transform: translateY(0);
transform: translateY(0);
}
}
.fadeInDown {
-webkit-animation-name: fadeInDown;
animation-name: fadeInDown;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="button"></div>
See http://css-tricks.com/restart-css-animation/, which discusses this exact problem.
I think the cleanest solution is to wrap the functionality of removing the element and re-inserting it in the same position in the HTML tree in a global function. Then, when you want to restart an animation, you just remove the class, call the reset function (grabbing the newly-inserted element from the return value), and then add the animation class(es) back to start the animation again.
For example:
function reset($elem) {
$elem.before($elem.clone(true));
var $newElem = $elem.prev();
$elem.remove();
return $newElem;
} // end reset()
$("#button").click(function () {
var $this = $(this);
$this.removeClass();
$this = reset($this);
$this.addClass("fadeInDown animated");
});
http://jsfiddle.net/jqm4vjLj/6/
This solution provides maximum responsiveness, since the old in-progress animation is automatically ended when the animating element is destroyed, and the newly-inserted element's animation begins immediately.
The simple solution for me is to start reflow process before adding class.
You can do it for example by "change" width of element. I think reflow process is faster for browser then node reinserting. And its simpler in terms of coding.
In jQuery:
$(this)
.removeClass('myAnimation')
.width('auto') // the magic
.addClass('myAnimation')
You need to remove class after the animation is complete,
but directly using removeClass wont work, because it will not let animation complete, instead use setTimeout
see this http://jsfiddle.net/jqm4vjLj/4/
$("#button").click(function () {
$("#button").addClass("fadeInDown animated");
setTimeout(function(){ $("#button").removeClass("fadeInDown animated");},100)
});
Problem is it immediately remove class and then add class so I just added new div with button2 id and on its click it just remove class and then click again on button div it would animate.
so problem is you need to do it after completion of animation.
$("#button1").click(function () {
$("#button").removeClass();
});
fiddle is here: http://jsfiddle.net/jqm4vjLj/5/
maybe this is good solution for your problem:
https://jsfiddle.net/d2c16fk7/3/
In your javascript the classes .animation and .fadeInDown were added to the #button and after that you had these classes and you couldn't add it again.
This works for me, from the animate.css github:
animateCss: function (animationName) {
var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';
$(this).addClass('animated ' + animationName).one(animationEnd, function() {
$(this).removeClass('animated ' + animationName);
});
}
To reset the element we have to make css recalculate right ? So what I did was hide and then show the element. What I did was removeClass() and then hide() and then show() and addClass()
$("#button").click(function () {
$("#button").removeClass("fadeInDown animated").hide();
$("#button").show().addClass("fadeInDown animated");
});
I have a balloon that when hovered, will expand n disappear (a popping-like animation). I made this in CSS but when the cursor moves, the balloon returned. I want the balloon to disappear forever until I refresh the page, so I guess it needs to be onclick, but that selector is not available in CSS.
Here's what I have in CSS
#keyframes pop
{
from{
opacity:1;
transform: translateZ(0) scale(1,1);
}
to{
opacity:0;
transform: translateZ(0) scale(1.5,1.5);
}
}
.balloon:hover
{
animation: pop 0.5s cubic-bezier(0.16, 0.87, 0.48, 0.99) forwards;
}
I saw another question that said the closest thing is :active but it requires the mouse to be held down. If I want it to be onclick, I need to use Javascript. But I don't know what I need to write to trigger the animation.
And is it also possible to make it so that when I pop 1 balloon, all the others will pop too automatically with a 1s delay inbetween? (There are 5 balloons).
You can add and remove the class of the animation with JS using classList.
Add:
object.classList.add('balloon');
Remove:
object.classList.remove('balloon');
Working example:
const add = () => {
document.getElementById('balloon').classList.add('animation')
}
const remove = () => {
document.getElementById('balloon').classList.remove('animation')
}
#keyframes pop {
from {
opacity: 1;
transform: translateZ(0) scale(1, 1);
}
to {
opacity: 0;
transform: translateZ(0) scale(1.5, 1.5);
}
}
.animation {
animation: pop 0.5s cubic-bezier(0.16, 0.87, 0.48, 0.99) forwards;
}
.balloon {
height: 125px;
width: 110px;
background-color: #FF6B6B;
border-radius: 50%;
}
.controls{
position: absolute;
bottom: 10px;
right: 10px;
}
<div id="balloon" class="balloon" onmouseover="add()"></div>
<div class="controls">
<button onClick="add()">Hide</button>
<button onClick="remove()">Show</button>
</div>
Here is a solution which makes balloons hiding one by one with interval .5s between them
var balloons = document.getElementsByClassName('balloon');
[...balloons].forEach( (e, i)=>{
e.onmouseover = function() {
this.classList.add('hidden');
setTimeout(hideAll, 500, balloons);
}
});
function hideAll(arg){
[...arg].forEach( (e, i)=>{
if ( ! e.classList.contains('hidden') ) {
e.style.animationDelay = i+'s';
e.classList.add('hidden');
}
});
}
#keyframes pop
{
from{
opacity:1;
transform: translateZ(0) scale(1,1);
}
to{
opacity:0;
transform: translateZ(0) scale(1.5,1.5);
}
}
.balloon.hidden
{
animation: pop .5s cubic-bezier(0.16, 0.87, 0.48, 0.99) forwards;
}
<div class="balloon">Balloon</div>
<div class="balloon">Balloon</div>
<div class="balloon">Balloon</div>
<div class="balloon">Balloon</div>
<div class="balloon">Balloon</div>
I want that whenever I click on the div, it first translate, then rotate and, finally scale. Further, I want to reverse it back in the same way when I click again
I have the following code:
$(() => {
$('div').on('click', () => {
$('div').toggleClass('clicked');
});
});
div.normal {
height: 20px;
width: 100px;
background: black;
transition: 2s all;
}
div.clicked {
transform: translate(100px, 100px) rotate(45deg) scale(2);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='normal'></div>
As you can see, all the transformations are occurring at the same time. But, I want them to occur separately. How do I achieve this?
Thanks in advance!
Decompose your animation using keyframes. Here is a minimally edited version of your code:
var $el = $('#to-animate')
var firstClick = true
$el.click(() => {
$el.toggleClass('clicked')
if (!firstClick) {
$el.toggleClass('unclicked')
}
firstClick = false
})
div.normal {
height: 20px;
width: 100px;
background: black;
}
div.clicked {
animation: transforms 2s forwards;
}
div.unclicked {
animation: transforms-2 2s reverse;
}
#keyframes transforms {
33% {
transform: translate(100px, 100px);
}
66% {
transform: translate(100px, 100px) rotate(45deg);
}
100% {
transform: translate(100px, 100px) rotate(45deg) scale(2);
}
}
#keyframes transforms-2 {
33% {
transform: translate(100px, 100px);
}
66% {
transform: translate(100px, 100px) rotate(45deg);
}
100% {
transform: translate(100px, 100px) rotate(45deg) scale(2);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="to-animate" class='normal'></div>
Edit updated to include ability to reverse the animation (making the code edit less minimal)
An idea is to rely on other properties to achieve the same effect and be able to apply different transition
$(() => {
$('div').on('click', () => {
$('div').toggleClass('clicked');
});
});
div.normal {
height: calc(20px * var(--s,1));
width: calc(100px * var(--s,1));
background: black;
position:relative;
top:0;
left:0;
transition: 2s top 4s,2s left 4s,2s width 2s,2s height 2s,2s transform;
}
div.clicked {
top:100px;
left:100px;
--s:2;
transform:rotate(45deg);
transition: 2s top,2s left,2s width 2s,2s height 2s,2s transform 4s;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='normal'></div>
I have a couple of links that shows a number of divs, depending on data-attribute. When I click one of the links, and there is more than one div that has the corresponding attribute, I want the class to be added sequential to the div, making them load one at a time. Just like it does in ready function but I've messed it up in some way and can't figure it out.
$('.filter_link').on('click', function(e) {
e.preventDefault();
var linkAttr = $(this).attr('data-related');
$('.blurb-content').each(function() {
$(this).removeClass('flip');
if($(this).attr('data-dest') == linkAttr) {
// this does not work
setTimeout(function() {
$(this).addClass('flip'); //add class with delay
}, 500);
}
});
});
fiddle
Two Issues:
Inside of a setTimeout(), this will no longer refer to your element. For this reason, you'll need to declare it outside the setTimeout() as a variable, and use that variable inside the setTimeout() instead of $(this). More information on this can be found here.
When you iterate through items using .each() they are all happening essentially simultaneously. Your setTimeout() isn't attaching "sequentially", so they will all delay 500 and then fire at the same time. Change the 500 to be multiplied by the iteration of your .each() by giving the each function a parameter i, and doing 500 * i just as you did in your first each.
EDIT: As you can see, I've re-factored your code quite a bit. It now uses the function showBlurbs() to simply fade each one in (taking the parameter as a list of jQuery objects). It resolves the need for so many .each() loops, and the code is quite a bit cleaner.
A few other notes:
You can get a data attribute by using .data("xxx") instead of .attr("data-xxx")
Rather than looping through all items and checking their data attribute, you can simply select only ones where it matches by doing:
var linkAttr = $(this).data('related');
$blurbsToShow = $('.blurb-content[data-dest=' + linkAttr + ']');
$(document).ready(function() {
//Show ALL blurbs at the beginning
var timeouts = [];
var $allBlurbs= $(".blurb-content");
showBlurbs($allBlurbs);
//On click, show related blurbs
$('.filter_link').on('click', function(e) {
e.preventDefault();
var linkAttr = $(this).data('related');
var $relatedBlurbs = $('.blurb-content[data-dest=' + linkAttr + ']');
showBlurbs($relatedBlurbs);
});
function showBlurbs(blurbs) {
//Clear all pending animations
for (var i = 0; i < timeouts.length; i++) {
clearTimeout(timeouts[i]);
}
//Reset everything to hidden
timeouts = [];
$(".blurb-content").removeClass("flip");
//Sequentially trigger the "fade-in" for each blurb
blurbs.each(function(i) {
var $self = $(this);
timeouts.push(setTimeout(function() {
$self.addClass("flip");
}, 500 * i));
});
}
});
.blurb-content {
opacity: 0;
display: none;
}
.flip {
display: block;
-webkit-animation: fade-in-bottom 0.6s cubic-bezier(0.390, 0.575, 0.565, 1.000) both;
animation: fade-in-bottom 0.6s cubic-bezier(0.390, 0.575, 0.565, 1.000) both;
}
#-webkit-keyframes fade-in-bottom {
0% {
-webkit-transform: translateY(50px);
transform: translateY(50px);
op.blurb-content {
opacity: 0;
display: none;
}
.flip {
display: block;
-webkit-animation: fade-in-bottom 0.6s cubic-bezier(0.390, 0.575, 0.565, 1.000) both;
animation: fade-in-bottom 0.6s cubic-bezier(0.390, 0.575, 0.565, 1.000) both;
}
#-webkit-keyframes fade-in-bottom {
0% {
-webkit-transform: translateY(50px);
transform: translateY(50px);
opacity: 0;
}
100% {
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
}
#keyframes fade-in-bottom {
0% {
-webkit-transform: translateY(50px);
transform: translateY(50px);
opacity: 0;
}
100% {
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
}
acity: 0;
}
100% {
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
}
#keyframes fade-in-bottom {
0% {
-webkit-transform: translateY(50px);
transform: translateY(50px);
opacity: 0;
}
100% {
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
link1
link2
link3
link4
<div class="blurb-content" data-dest="1">content 1</div>
<div class="blurb-content" data-dest="1">content 1</div>
<div class="blurb-content" data-dest="2">content 2</div>
<div class="blurb-content" data-dest="3">content 3</div>
$('.filter_link').on('click', function(e) {
e.preventDefault();
var linkAttr = $(this).attr('data-related');
$('.blurb-content').each(function(i) {
var thisBlurb = $(this);
thisBlurb.removeClass('flip');
if(thisBlurb.attr('data-dest') == linkAttr) {
// this work
setTimeout(function() {
thisBlurb.addClass('flip'); //add class with delay
}, 500 * i );
}
});});
Copy paste your code and changed the $(this) of .blurb to the var thisBlurb assigned at the start of each loop.
The JavaScript keyword "this" is scoped to the function it's in. setTimeout uses a function so any "this" inside the setTimeout is no longer the element .blurb. "this" is now the function used by setTimeout :)
I also added parameter "i" in the function that the "each" loop is using. "i" is the index of each loop and will increase by 1 per iteration of the loop. Multiplying this index to the setTimeout millisecond parameter will make give you the effect you want.