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");
});
Related
I have a rather simple fiddle here: http://jsfiddle.net/7aotzqmL/2/ that is supposed to shake a div with CSS animation upon clicking a link. It works, but only every other time (every second time). I'm stumped - can anybody help?
Other answers I've looked at mentioned how when using onmouseover you also need to attach onmouseout... but this is just a click so I'm not sure that's relevant. I just want the div to shake on every click rather than every other.
jsFiddle: https://jsfiddle.net/7aotzqmL/2/
CSS code:
$(function() {
$('a').click(function(ev) {
$('div').toggleClass('shaker');
ev.preventDefault();
});
});
div.shaker {
animation: shake 0.3s;
/* When the animation is finished, start again */
animation-iteration-count: 1; //single shake
}
#keyframes shake {
0% {
transform: translate(1px, 1px) rotate(0deg);
}
10% {
transform: translate(-1px, -2px) rotate(-1deg);
}
20% {
transform: translate(-3px, 0px) rotate(1deg);
}
30% {
transform: translate(3px, 2px) rotate(0deg);
}
40% {
transform: translate(1px, -1px) rotate(1deg);
}
50% {
transform: translate(-1px, 2px) rotate(-1deg);
}
60% {
transform: translate(-3px, 1px) rotate(0deg);
}
70% {
transform: translate(3px, 1px) rotate(-1deg);
}
80% {
transform: translate(-1px, -1px) rotate(1deg);
}
90% {
transform: translate(1px, 2px) rotate(0deg);
}
100% {
transform: translate(1px, -2px) rotate(-1deg);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
asd
<div>shake</div>
Try this will work
$(function(){
$('a').click(function(ev){
$('div').addClass('shaker');
setTimeout(function(){
$('div').removeClass('shaker');
},300);
ev.preventDefault();
});
});
Below code is removing class on even time click, and you animation working on adding class
$('div').toggleClass('shaker');
in above solution, div class will be removed after 0.3secs animation, it will add 'shaker' class every time you click and animation will happen
You are toggling the class so the first click adds it and the second removes it. Instead you could add the class on click and set a single use event listener to listen for the animation end and then remove the class.
$(function() {
const div = $('.container')
$('a').click(function(ev) {
div.addClass('shaker')
div.one('animationend', () => {
div.removeClass('shaker')
})
ev.preventDefault();
});
});
.container {
opacity: 1;
width: 100px;
height: 100px;
background: red;
}
.container.shaker {
/* Start the shake animation and make the animation last for 0.5 seconds */
animation: shake 0.3s;
/* When the animation is finished, start again */
animation-iteration-count: 1; //single shake
}
#keyframes shake {
0% {
transform: translate(1px, 1px) rotate(0deg);
}
10% {
transform: translate(-1px, -2px) rotate(-1deg);
}
20% {
transform: translate(-3px, 0px) rotate(1deg);
}
30% {
transform: translate(3px, 2px) rotate(0deg);
}
40% {
transform: translate(1px, -1px) rotate(1deg);
}
50% {
transform: translate(-1px, 2px) rotate(-1deg);
}
60% {
transform: translate(-3px, 1px) rotate(0deg);
}
70% {
transform: translate(3px, 1px) rotate(-1deg);
}
80% {
transform: translate(-1px, -1px) rotate(1deg);
}
90% {
transform: translate(1px, 2px) rotate(0deg);
}
100% {
transform: translate(1px, -2px) rotate(-1deg);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
asd
<div class="container"></div>
Well, for one, you're TOGGLING the class. so one click means "add" the next one means "remove". Also, "preventdefault" should appear first in order to prevent the default behavior of the anchor tag before you try to add or remove classes.
$(function(){
$('a').click(function(ev){
ev.preventDefault();
$('div').addClass('shaker');
setTimeout(()=> {
$('div').removeClass('shaker');
},300)
});
});
$(function(){
$('a').click(function(ev){
$('div').addClass('shaker');
setTimeout(()=>{
$('div').removeClass('shaker');
},300)
ev.preventDefault();
});
});
Use the above code. You were removing the shaker class alternatively.
How can I trigger CSS class to start my logo animation when scrolling/changing slide with fullpage.js?
I have this (it works alright) for animating my SVG wheel logo:
.logo-img:hover #wheel {
-webkit-animation: in 1s;
transform-origin: 49% 50%;
}
#wheel {
-webkit-animation: out 1s;
transform-origin: 49% 50%;
}
#-webkit-keyframes in {
from { -webkit-transform: rotate(0deg); }
to { -webkit-transform: rotate(360deg);}
}
#-webkit-keyframes out {
0% { -webkit-transform: rotate(360deg); }
100% { -webkit-transform: rotate(0deg); }
}
It's a simple animation of spinning wheel 360deg., now I want it to spin when scrolling and use "in/out" keyframes depending on sliding page up or down.
I'm using fullpage.js and jquery v2.2.4
I hope It makes sense.
Thanks
Use the fullpage.js state classes.
So you can do:
.fp-viewing-section1-slide1 .myItem{
/*Whatever */
}
See my video tutorial here:
https://www.youtube.com/watch?v=qiCVPpI9l3M&t=5s
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.
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
});
});
I'm going mad trying to get a spinner to appear. I've bound my heavy processing function to a button thus:
$(document).delegate("#clearread", "tap", onClearRead);
So on tap it calls this:
var onClearRead = function() {
setTimeout($.mobile.showPageLoadingMsg, 5);
// Civilised cleaning of saved status
var jStorIndex = $.jStorage.index();
for (var i = 0; i < jStorIndex.length; i++) {
if( jStorIndex[i] != "version" ) {
$.jStorage.deleteKey(jStorIndex[i]);
}
}
// Load articles afresh
loadArticles();
$.mobile.changePage("#choosearticle");
} //onClearRead
I find that the spinner does not appear during the clearing/loading of articles (about 10 secs) but only for a brief period while the #choosearticle page loads (0.5 secs).
What am I doing wrong?
I have the spinner working elsewhere in the app.
Thanks
Try this:
$(document).delegate("#clearread", "tap", onClearRead);
var onClearRead = function() {
$.mobile.showPageLoadingMsg();
setTimeout(function(){
//Your heavy processing
$.mobile.changePage("#choosearticle");
}, 5);
} //onClearRead
jQuery.show( [duration ] [, complete ] )
Putting the heavy processing in the "complete" function slot, ensures the object (with show called on it) is visible before the show happens.
Related SO Answers
https://stackoverflow.com/a/25207120/999943
jQuery whole HTML page load with spinner
Example using a CSS based spinner
CSS
#-moz-keyframes spin {
0% {
-moz-transform: rotate(0deg); }
100% {
-moz-transform: rotate(359deg); } }
#-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg); }
100% {
-webkit-transform: rotate(359deg); } }
#-o-keyframes spin {
0% {
-o-transform: rotate(0deg); }
100% {
-o-transform: rotate(359deg); } }
#-ms-keyframes spin {
0% {
-ms-transform: rotate(0deg); }
100% {
-ms-transform: rotate(359deg); } }
#keyframes spin {
0% {
transform: rotate(0deg); }
100% {
transform: rotate(359deg); } }
.icon-spin {
display: inline-block;
-moz-animation: spin 2s infinite linear;
-o-animation: spin 2s infinite linear;
-webkit-animation: spin 2s infinite linear;
animation: spin 2s infinite linear; }
Html using font awesome
<div id="spinner" data-bind="visible: isSpinning" style="padding: 10px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #cccccc; z-index: 1; filter: alpha(opacity=30);">
<i class="fa fa-spinner fa-spin fa-5x"></i>
</div>
Javascript
$('#spinner').show(100, function() {
// Once the spinner is visible then run the heavy function.
heavyProcessingFunction();
});