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.
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 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 have an element that i would like off screen to begin with, but then on click of a link, that element gets animated in (using animate.css). But, i'm not sure what css method to use to hide that element off screen so it can be animated in.
The js i'm using is:
$('.services-wrapper').on('click','.services-panel__cta',function(e){
e.preventDefault();
$('.services-panel__secondary').addClass('animated bounceInright');
})
And i have tried doing:
position: absolute;
left: 100%
and
left: -9999px
But i'm not sure that even makes sense to try tbh.
Any help really gratefully received!
With animate.css, you don't need to specify the position beforehand. You can hide it with display: none; and then add an additional class that adds display: block;.
JS Fiddle
CSS
.services-panel__secondary {
display: none;
}
.show {
display: block;
}
JS
$('.services-wrapper').on('click', '.services-panel__cta', function() {
$('.services-panel__secondary').addClass('show animated bounceInRight');
})
Or just use show() instead of adding the class:
JS Fiddle
$('.services-wrapper').on('click', '.services-panel__cta', function() {
$('.services-panel__secondary').show().addClass('animated bounceInRight');
});
And Lastly
If you can edit the html directly, you can add the animate.css classes directly and just show() the element:
JS Fiddle
Add classes in html and hide with display: block;
<div class="services-panel__secondary animated bounceInRight">
Bounce this in
</div>
JQuery- Simply show it and it will bounce in.
$('.services-wrapper').on('click', '.services-panel__cta', function() {
$('.services-panel__secondary').show();
})
IMPORTANT:
With animate.css, notice that "right" should have an uppercase "R" like bounceInRight
animate.css actually takes care of this for you with it's animations. Check out the source of bounceInRight (which you are using). As you can see, it moves the x-value around using transform: trasnslate3d(...). As mentioned by
#dwreck08 (+1), you only need to worry about hide/show.
#keyframes bounceInRight {
from, 60%, 75%, 90%, to {
-webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
}
from {
opacity: 0;
-webkit-transform: translate3d(3000px, 0, 0);
transform: translate3d(3000px, 0, 0);
}
60% {
opacity: 1;
-webkit-transform: translate3d(-25px, 0, 0);
transform: translate3d(-25px, 0, 0);
}
75% {
-webkit-transform: translate3d(10px, 0, 0);
transform: translate3d(10px, 0, 0);
}
90% {
-webkit-transform: translate3d(-5px, 0, 0);
transform: translate3d(-5px, 0, 0);
}
to {
-webkit-transform: none;
transform: none;
}
}
A solution which allows animating in and out
This example code comes from Animate.css's own documentation. I have expanded on it to include adding and removing a show class, which will maintain state once the animation is complete.
const animateCSS = (element, animation, prefix = 'animate__') => {
// Create a Promise and return it
new Promise((resolve, reject) => {
const animationName = `${prefix}${animation}`;
const node = document.querySelector(element);
// Add class to display element when animating in
if (animation.indexOf('In') >= 0)
node.classList.add('show');
node.classList.add(`${prefix}animated`, animationName);
// When the animation ends, we clean the classes and resolve the Promise
function handleAnimationEnd(event) {
event.stopPropagation();
// Remove class to display element when animating out
if (animation.indexOf('Out') >= 0)
node.classList.remove('show');
node.classList.remove(`${prefix}animated`, animationName);
resolve('Animation ended');
}
node.addEventListener('animationend', handleAnimationEnd, { once: true });
});
}
Set initial styles to display: none and create a show class with display: block. Then call the method we created with the following:
animateCSS('.services-panel__secondary', 'bounceInright');
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();
});