I have a very strange issue. I'm loading articles from JSON in jQuery and as they load, I'd like to add a class of 'animate' to each dynamic element.
$.each(jsonArticles, function (i, article) {
var $articleHTML = $(
'<article class="article">' +
'<img src="' + jsonObject.imagePath + article.reviewImage + '" alt="">' +
'<h1>' + article.reviewTitle + '</h1>' +
'<p>' + article.reviewSummary + '</p>' +
'</article>');
$articles
.append($articleHTML)
.find("article")
.addClass("animate");
});
All of this works great and checking in Firebug reveals that the class is successfully added to each article tag.
However, when trying to use a CSS transition on the article for the class that's added, it does not animate, but instead skips straight to the final style (opacity: 1).
.article {
opacity: 0;
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
}
.article.animate {
opacity: 1;
}
The animation doesn't happen, but the class is added and the article is successfully set to opacity: 1. It shows up instantly.
Anyone have any ideas about this? I cannot figure this one out at all.
On another point, which is rather interesting...if I change the .animate class to have a :hover, then the articles won't show until I hover and the animation does work. Why it would work for hover and not when it's simply added immediately, seems strange to me.
.article.animate:hover {
opacity: 1;
}
I'd appreciate any input.
Thanks,
Mikey.
Live Demo: http://jsfiddle.net/Pz5CD/
Notice how the articles just pop in at 100% opacity. No animation is seen.
Update:
It turns out the OP wants to fade in each element sequentially, which is beyond the scope of the original question. I'll leave my answer here as an answer to the original question.
CSS animation won't trigger on addClass in jQuery
The issue is that your new html is added to the page and the animate class is added before the css for that html has been applied. The browser will skip ahead like that for the sake of efficiency. For example, if you added a class, then removed it, and repeated that process a hundred times, there wouldn't be a visual difference. It would have just skipped to the result. For this reason, you have to force a redraw on the element so that all previous styles have applied before adding the class. I wrote a function to handle this that should work in every circumstance on every browser, though there's no way to guarantee the behavior of a reDraw. It probably will always work and it's nice to have!
Live demo here (click). You can tell the reDraw is making the difference by commenting it out and just leaving the addClass().
$('body').append($articleHTML);
$a = $('body').find("article");
reDraw($a).then(function() {
$a.addClass("animate");
});
function reDraw($element) {
var deferred = new $.Deferred();
setTimeout(function() {
var h = $element[0].offsetHeight;
var s = $element[0].getComputedStyle;
deferred.resolve();
},0);
return deferred.promise();
}
The best way to force a redraw is to either access the offsetHeight or getComputedStyle of an element. However, there have been cases where those have failed for force a redraw on certain mobile devices. To add some extra encouragement for a redraw, I added a setTimeout as well. Even a time of 0 on the timeout will work, but it throws off the call stack, so I use a promise to ensure the next operation (adding your class) will happen after the redraw. That just means you'll use the syntax I demonstrated above to add the class - redraw($element).then(function() { //your code
For fun, I made a little demo of flipping classes with and without reDraw. http://jsbin.com/EjArIrik/1/edit
You need to add the class to the element after it is rendered to the dom, a set timout might work
setTimeout(function(){
$articleHTML.addClass("animate");
}, i * 500 );
http://jsfiddle.net/Pz5CD/1/
Related
I'm having some major headache trying to apply CSS3 transitions to a slideshow trough JavaScript.
Basically the JavaScript gets all of the slides in the slideshow and applies CSS classes to the correct elements to give a nice animated effect, if there is no CSS3 transitions support it will just apply the styles without a transition.
Now, my 'little' problem. All works as expected, all slides get the correct styles, the code runs without bugs (so far). But the specified transitions do not work, even though the correct styles where applied. Also, styles and transitions work when I apply them myself trough the inspector.
Since I couldn't find a logical explanation myself I thought someone here could answer it, pretty please?
I've put together a little example of what the code is right now: http://g2f.nl/38rvma
Or use JSfiddle (no images): http://jsfiddle.net/5RgGV/1/
To make transition work, three things have to happen.
the element has to have the property explicitly defined, in this case: opacity: 0;
the element must have the transition defined: transition: opacity 2s;
the new property must be set: opacity: 1
If you are assigning 1 and 2 dynamically, like you are in your example, there needs to be a delay before 3 so the browser can process the request. The reason it works when you are debugging it is that you are creating this delay by stepping through it, giving the browser time to process. Give a delay to assigning .target-fadein:
window.setTimeout(function() {
slides[targetIndex].className += " target-fadein";
}, 100);
Or put .target-fadein-begin into your HTML directly so it's parsed on load and will be ready for the transition.
Adding transition to an element is not what triggers the animation, changing the property does.
// Works
document.getElementById('fade1').className += ' fade-in'
// Doesn't work
document.getElementById('fade2').className = 'fadeable'
document.getElementById('fade2').className += ' fade-in'
// Works
document.getElementById('fade3').className = 'fadeable'
window.setTimeout(function() {
document.getElementById('fade3').className += ' fade-in'
}, 50)
.fadeable {
opacity: 0;
}
.fade-in {
opacity: 1;
transition: opacity 2s;
}
<div id="fade1" class="fadeable">fade 1 - works</div>
<div id="fade2">fade 2 - doesn't work</div>
<div id="fade3">fade 3 - works</div>
Trick the layout engine!
function finalizeAndCleanUp (event) {
if (event.propertyName == 'opacity') {
this.style.opacity = '0'
this.removeEventListener('transitionend', finalizeAndCleanUp)
}
}
element.style.transition = 'opacity 1s'
element.style.opacity = '0'
element.addEventListener('transitionend', finalizeAndCleanUp)
// next line's important but there's no need to store the value
element.offsetHeight
element.style.opacity = '1'
As already mentioned, transitions work by interpolating from state A to state B. If your script makes changes in the same function, layout engine cannot separate where state A ends and B begins. Unless you give it a hint.
Since there is no official way to make the hint, you must rely on side effects of some functions. In this case .offsetHeight getter which implicitly makes the layout engine to stop, evaluate and calculate all properties that are set, and return a value. Typically, this should be avoided for performance implications, but in our case this is exactly what's needed: state consolidation.
Cleanup code added for completeness.
Some people have asked about why there is a delay. The standard wants to allow multiple transitions, known as a style change event, to happen at once (such as an element fading in at the same time it rotates into view). Unfortunately it does not define an explicit way to group which transitions you want to occur at the same time. Instead it lets the browsers arbitrarily choose which transitions occur at the same time by how far apart they are called. Most browsers seem to use their refresh rate to define this time.
Here is the standard if you want more details:
http://dev.w3.org/csswg/css-transitions/#starting
I'm trying to transition smoothly from one half-completed CSS animation to the next one and I can't find a way to do it without a slight stutter. I have an infinite animation at very high speed that should gently slow down to a stop on click. Right now, I'm always getting a slight hickup while switching animations, likely partially because I need to wait for the next requestAnimationFrame before starting the next animation. Are there other options? Here's approximately what I'm doing:
function onClick(){
// get current location of element
var content = $(".content");
var currentOffset = $(content[0]).css("top");
// set property to stop jumping back to the beginning of current animation
content.css({ top: currentOffset });
// stop the current animation
content.removeClass("anim-infinite");
// update content based on current location
// updateContent(currentOffset);
// setup ease-out animation
window.requestAnimationFrame(function() {
content.addClass("anim-ease-out");
content.css({ top: parseFloat(currentOffset) + 50 });
});
}
And here's the relevant CSS.
#keyframes "spin" {
from { top: 0 };
to { top: -200%; }
}
.anim-infinite{
animation: spin 1s linear infinite;
}
.anim-ease-out{
transition: all 0.25s ease-out;
}
The distances and timespans are reasonable to maintain constant speed between the two animations and I'm using the relevant browser prefixes.
I get the same stutter when I use a "linear" timing function for the second animation. I tried setting an animation-fill-mode:both, without success. It appears to only affect animations that complete.
The stutter gets worse when I try to update the content based on the location of the content - which is dependent on when the animation gets stopped.
While trying to work out a jsFiddle that demos the problem, I found the source of most of the stutter. Anything that happens between removing the anim-infinite class and in the requestAnimationFrame can have a big perf impact, especially if it modifies the DOM in the content and causes the content to reflow. That's obvious in retrospect but the minor DOM updates had a bigger impact than expected.
I still have a slight occasional stutter but it's "good enough" for now.
For reference, here's the fiddle.
I am using JavaScript to dynamically add an element to the DOM. I want to use CSS3 transitions to "fade in" the element as it is added.
I am using something like the following to achieve this:
function add(el) {
el.className += ' fader';
el.style.opacity = 0;
document.getElementById('parent-element').appendChild(el);
//setTimeout(function () { el.style.opacity = 1; }, 5);
el.style.opacity = 1;
}
And the CSS:
.fader {
-webkit-transition: opacity 0.5s;
}
This does not work as expected - the element does not fade in. If I replace the line el.style.opacity = 1; with setTimeout(function () { el.style.opacity = 1; }, 5);, as seen commented-out above, it does work as expected.
I am guessing that the first case does not work as there is some delay between adding the element and the appropriate CSS rules being applied to it. The 5ms delay created by the setTimeout in the second case gives enough time for these rules to be applied, therefore the fade takes place as expected.
Firstly, is this a correct assumption? Secondly, is there a better way to solve this? The setTimout feels like a hack. Is there perhaps some event that is fired once the element has had all its styles applied?
For a CSS3 transition to work, the object has to exist in a particular state and then you have to make a change to the object that triggers the transition.
For a variety of reasons, all of my experience with CSS3 transitions has shown me that a state that counts for this is only a state that it exists in when your javascript returns and the browser goes back to its event loop. It's as if, the only way you can tell the browser to loop at your object now and remember it's state for future transitions is to go back to the browser event loop. There are some programming reasons why this may be the case (so it's not trying to execute transitions as you're programmatically building your object and changing it), but those issues could have been solved a different way (like with a specific method call to codify the object now), but it wasn't done that way.
As such, your solution is the way I've found to do it. Create the object in it's initial state. Set a timer for a very short duration. Return from all your javascript so the object will get codified in its initial state and so the timer can fire. In the timer event, add a class to the object that triggers the CSS3 transition.
I don't honestly know if CSS3 transitions are specified this way in the specification, but my experience in Safari, Firefox and Chrome has been that this is how they work.
I am using animate.css and right now I have a CSS style for animated2500 which means it will take 2.5 seconds to animate. The style is:
.animated2500 {
-webkit-animation: 2500ms ease;
-moz-animation: 2500ms ease;
-ms-animation: 2500ms ease;
animation: 2500ms ease;
}
So in my HTML I would do:
<p class="animated2500 pulse">Takes 2.5 seconds to pulse</p>
There has to be an easier way to do this though, because I will want to specify how many seconds without creating a custom class for it each time.
Is there a way to use a custom data attribute like: <p data-delay="5000" class="fade">Fade in 5 econds</p> would that work?
How could I accomplish something like this?
Thanks!
I don't think you can use pure CSS for this, individual styles are the way to go.
If you really want to use a data-* attribute, since you've tagged your question jquery, I'll post a jQuery-specific answer although CSS animations and data attributes are not specific to jQuery (or even JavaScript):
jQuery(function($) {
$(".pulse[data-delay]").each(function() {
var value = parseInt(this.getAttribute("data-delay"), 10);
if (!isNaN(value)) {
value = value + "ms ease";
this.style["-webkit-animation"] = value;
this.style["-moz-animation"] = value;
this.style["-ms-animation"] = value;
this.style["animation"] = value;
}
});
});
That runs through the elements on DOM ready and applies the style directly. I don't like it for several reasons (not least that new elements added to the page post-load won't get handled), but if you really, really don't want to create specific classes...
maybe something like this will work? (untested)
$(document).ready(function() {
$("p[data-delay]").each(function(){
$(this).css('-webkit-animation',$(this).attr('data-delay')+'ms ease');
$(this).css('-moz-animation',$(this).attr('data-delay')+'ms ease');
$(this).css('-ms-animation',$(this).attr('data-delay')+'ms ease');
$(this).css('animation',$(this).attr('data-delay')+'ms ease');
}
);
});
$(document).ready(function() {
$("p[data-delay]").each(function(){
this.style.webkitAnimationDuration = $(this).attr('data-delay') + 's';
);
});
I am using jQuery and jQuery-ui and want to animate various attributes on various objects.
For the sake of explaining the issue here I've simplified it to one div that changes from blue to red when the user mouses over it.
I am able to get the behavior I want when using animate(), however when doing so the styles I am animating have to be in the animation code and so are separate from my style sheet. (see example 1)
An alternative is using addClass() and removeClass() but I have not been able to re-create the exact behavior that I can get with animate(). (see example 2)
Example 1
Let's take a look at the code I have with animate():
$('#someDiv')
.mouseover(function(){
$(this).stop().animate( {backgroundColor:'blue'}, {duration:500});
})
.mouseout(function(){
$(this).stop().animate( {backgroundColor:'red'}, {duration:500});
});
it displays all the behaviors I am looking for:
Animates smoothly between red and blue.
No animation 'overqueue-ing' when the user moves their mouse quickly in and out of the div.
If the user moves their mouse out/in while the animation is still playing it eases correctly between the current 'halfway' state and the new 'goal' state.
But since the style changes are defined in animate() I have to change the style values there, and can't just have it point to my stylesheet. This 'fragmenting' of where styles are defined is something that really bothers me.
Example 2
Here is my current best attempt using addClass() and removeClass (note that for the animation to work you need jQuery-ui):
//assume classes 'red' and 'blue' are defined
$('#someDiv')
.addClass('blue')
.mouseover(function(){
$(this).stop(true,false).removeAttr('style').addClass('red', {duration:500});
})
.mouseout(function(){
$(this).stop(true,false).removeAttr('style').removeClass('red', {duration:500});
});
This exhibits both property 1. and 2. of my original requirements, however 3 does not work.
I understand the reason for this:
When animating addClass() and removeClass() jQuery adds a temporary style to the element, and then increments the appropriate values until they reach the values of the provided class, and only then does it actually add/remove the class.
Because of this I have to remove the style attribute, otherwise if the animation is stopped halfway the style attribute would remain and would permanently overwrite any class values, since style attributes in a tag have higher importance than class styles.
However when the animation is halfway done it hasn't yet added the new class, and so with this solution the color jumps to the previous color when the user moves their mouse before the animation is completed.
What I want ideally is to be able to do something like this:
$('#someDiv')
.mouseover(function(){
$(this).stop().animate( getClassContent('blue'), {duration:500});
})
.mouseout(function(){
$(this).stop().animate( getClassContent('red'), {duration:500});
});
Where getClassContent would just return the contents of the provided class. The key point is that this way I don't have to keep my style definitions all over the place, but can keep them in classes in my stylesheet.
Since you are not worried about IE, why not just use css transitions to provide the animation and jQuery to change the classes. Live example: http://jsfiddle.net/tw16/JfK6N/
#someDiv{
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
}
Another solution (but it requires jQueryUI as pointed out by Richard Neil Ilagan in comments) :-
addClass, removeClass and toggleClass also accepts a second argument; the time duration to go from one state to the other.
$(this).addClass('abc',1000);
See jsfiddle:- http://jsfiddle.net/6hvZT/1/
You could use jquery ui's switchClass, Heres an example:
$( "selector" ).switchClass( "oldClass", "newClass", 1000, "easeInOutQuad" );
Or see this jsfiddle.
You just need the jQuery UI effects-core (13KB), to enable the duration of the adding (just like Omar Tariq it pointed out)
I was looking into this but wanted to have a different transition rate for in and out.
This is what I ended up doing:
//css
.addedClass {
background: #5eb4fc;
}
// js
function setParentTransition(id, prop, delay, style, callback) {
$(id).css({'-webkit-transition' : prop + ' ' + delay + ' ' + style});
$(id).css({'-moz-transition' : prop + ' ' + delay + ' ' + style});
$(id).css({'-o-transition' : prop + ' ' + delay + ' ' + style});
$(id).css({'transition' : prop + ' ' + delay + ' ' + style});
callback();
}
setParentTransition(id, 'background', '0s', 'ease', function() {
$('#elementID').addClass('addedClass');
});
setTimeout(function() {
setParentTransition(id, 'background', '2s', 'ease', function() {
$('#elementID').removeClass('addedClass');
});
});
This instantly turns the background color to #5eb4fc and then slowly fades back to normal over 2 seconds.
Here's a fiddle
Although, the question is fairly old, I'm adding info not present in other answers.
The OP is using stop() to stop the current animation as soon as the event completes. However, using the right mix of parameters with the function should help. eg. stop(true,true) or stop(true,false) as this affects the queued animations well.
The following link illustrates a demo that shows the different parameters available with stop() and how they differ from finish().
http://api.jquery.com/finish/
Although the OP had no issues using JqueryUI, this is for other users who may come across similar scenarios but cannot use JqueryUI/need to support IE7 and 8 too.