I am trying to get off javascript animations and use CSS animations instead. I am currently using jQuery's animation functionality.
I came across this website with a bunch of wonderful css animations. Such as:
.demo1 {
-webkit-transition:all .5s ease-out;
-moz-transition:all .5s ease-out;
-ms-transition:all .5s ease-out;
-o-transition:all .5s ease-out;
transition:all .5s ease-out;
}
.demo1:hover {
-webkit-transform:translate(0px,10px);
-moz-transform:translate(0px,-10px);
-ms-transform:translate(0px,-10px);
-o-transform:translate(0px,10px);
transform:translate(0px,-10px);
}
What I can't figure out is how to activate these animations programmatically. So for example, instead of on a hover, how can I translate the element by calling elem.translate()?
I may be misinterpreting what you're asking, but I think you may be under a mistaken impression of what "translate" is on an element. A DOM element has an attribute called "translate", as well as a css property "translate". However, the element attribute "translate" is just a boolean flag specifying whether the text in the element should be translated in the linguistic sense, it is not a callable function and has nothing to do with the css property.
That aside, there are still plenty of ways to translate an element programmatically. Some other people already gave a pretty good idea of how to do this with jQuery. If you don't wish to use jQuery, you can still add and remove classes manually (same goes for styles).
Here's an example I cooked up for class addition/removal. It's pretty straightforward, but here's the relevant code for class modification:
.translator {
-webkit-transform:translate(0px,100px);
-moz-transform:translate(0px,-100px);
-ms-transform:translate(0px,-100px);
-o-transform:translate(0px,100px);
transform:translate(0px,-100px);
}
...
function move_box() {
var the_box = document.getElementById("the-box");
if (the_box.classList.contains("translator")) {
the_box.classList.remove("translator");
} else {
the_box.classList.add("translator");
}
}
By applying the class, the animation will begin (and removing it will reverse it). This can happen as many times as you'd like.
One important note: for this example, I still have the style "transition:all .5s ease-out;" applied to the div before anything happens. This is just a universal default that governs how animation effects are applied to the element. There are a couple of different approaches to this, but for simplicities sake I'm going to just leave it like this.
Otherwise, you can add the styles directly, like so:
function move_box() {
var the_box = document.getElementById("the-box");
set_translate(the_box, 100);
}
function set_translate(e, pix) {
e.style["-webkit-transform"] = "translate(0px, "+ pix +"px)";
e.style["-moz-transform"] = "translate(0px, -" + pix +"px)";
e.style["-ms-transform"] = "translate(0px, -" + pix + "px)";
e.style["-o-transform"] = "translate(0px, " + pix + "px)";
e.style["transform"] = "translate(0px, -" + pix + "px)";
}
Nothing too complex here - it sets each relevant element directly by manipulating the styles on the element. As before, it relies on a separate class to specify the transition style.
Personally, I think the class addition/removal is far superior. Technically speaking, direct modification of styles is more flexible, but if that's what you're aiming for you probably should use a good library like jQuery transit (as mentioned in the comments). However, if you just want to be able to programmatically apply a few canned effects, modifying classes on the fly is a fine solution.
If you already have the CSS in place, you can trigger it in jquery by doing $('.demo1').trigger('hover'); to simulate a hover event, or change your css selector from .demo:hover to .class-name and just add that class using $('.demo').addClass('class-name');
You can use jQuery.css.fn to apply the css rules.
$('.demo1').click(function(){
$(this).css({
transform: 'translate(0px,-10px)'
});
});
Or add a class to the element:
$('.demo1').click(function(){
$(this).toggleClass('translate');
});
.translate {
-webkit-transform:translate(0px,10px);
-moz-transform:translate(0px,-10px);
-ms-transform:translate(0px,-10px);
-o-transform:translate(0px,10px);
transform:translate(0px,-10px);
}
Related
I have a function that loads html into a table with jQuery and subsequently adds a class to one of the rows with the callback. The function is triggered by various UI driven events on the page. I also have a css transition rule so the color should fade in (transition: background-color 1000ms linear). The function looks like this:
function load_tbody(row_id) {
$('tbody').load("load_tbody.php", function() {
$(row_id).addClass('green');
});
}
Once the html is loaded, the class successfully gets added and row color is set to green. However, my css transition rule seems to be ignored.
When I add a slight delay, even 10ms, it works fine:
function load_tbody(row_id) {
$('tbody').load("load_tbody.php", function() {
setTimeout(function() {
$(row_id).addClass('green');
}, 10);
});
}
The jQuery docs for .load() state:
If a "complete" callback is provided, it is executed after
post-processing and HTML insertion has been performed.
To me this would indicate the new elements have been loaded into the dom with existing styles applied and are ready for manipulation. Why does the transition fail in the first example but succeed in the second?
Here is a fully functional example page to demonstrate the behaviour in question:
http://so-37035335.dev.zuma-design.com/
While the example above links jQuery version 2.2.3 from cdn, actual page in question uses version 1.7.1. The same behavior can be observed across both versions.
UPDATE:
After considering some of the comments and answers offered below, I've stumbled upon something altogether more confusing. User #gdyrrahitis made a suggestion which lead me to do this:
function tbody_fade(row_id) {
$('tbody').load("load_tbody.php", function() {
$('tbody').fadeIn(0, function() {
$(this).find(row_id).addClass('green');
});
});
}
Adding the class inside the fadeIn() callback works, even with a duration of 0ms. So this had me wondering... if the element is theoretically there anyway, what background color does the browser think it has before I add that class. So I log the background-color:
console.log($(row_id).css('background-color'));
And do you know what? Simply getting the background-color color made everything work:
function tbody_get_style(row_id) {
$('tbody').load("load_tbody.php", function() {
$(row_id).css('background-color');
$(row_id).addClass('green');
});
}
Just adding the line $(row_id).css('background-color'); which seemingly does nothing at all causes the transition effect to work. Here's a demo:
http://so-37035335-b.dev.zuma-design.com/
I'm just dumbfounded by this. Why does this work? Is it merely adding a small delay or does jQuery getting the css property somehow have a substantial effect on the state of the newly added element?
jQuery load is intended to drop everything that is requested into the page.
You can leverage the power of jQuery Deferred objects by using $.get instead.
Take a look at this plunk.
Code snippet from plunk
function load_tbody(row_id) {
$.when($.get("load_tbody.html", function(response) {
$('tbody').hide().html(response).fadeIn(100, function() {
$(this).find(row_id).addClass('green');
});
}));
}
I am using the $.when which will run its callback as soon the $.get is resolved, meaning will fetch the HTML response. After the response is fetched, it is appended to the tbody, which is fadedIn (fadeIn method) and after it is shown, the .green class is added to the desired row.
Note that if you go and just append the html and then the class to the row_id, you won't be able to see the transition, because it is executed immediately. A little nice visual trick with the fadeIn can do the work.
Update
On newly added elements to the DOM, CSS3 transition is not going to be triggered. This mainly happens because of the internal browser engine that controls all animations. There are numerous articles with workarounds on the issue, as well as stackoverflow answers. Additional resources can be found there, which I believe can explain the topic much better than me.
This answer is about taking a step back and changing the piece of functionality that renders dynamic elements in DOM, without going to use setTimeout or requestAnimationFrame. This is just another way to achieve what you want to achieve, in clear and consistent way, as jQuery works across browsers. The fadeIn(100, ... is what is needed to catch up with the next available frame the browser is about to render. It could be much less, the value is just to satisfy visual aesthetics.
Another workaround is to not use transitions at all and use animation instead. But from my tests this fails in IE Edge, works well on Chrome, Firefox.
Please look at the following resources:
https://www.christianheilmann.com/2015/08/30/quicky-fading-in-a-newly-created-element-using-css/
Update 2
Take a look at the specification please, as interesting stuff lies there regarding CSS3 transitions.
...This processing of a set of simultaneous style changes is called a style change event. (Implementations typically have a style change event to correspond with their desired screen refresh rate, and when up-to-date computed style or layout information is needed for a script API that depends on it.)
Since this specification does not define when a style change event occurs, and thus what changes to computed values are considered simultaneous, authors should be aware that changing any of the transition properties a small amount of time after making a change that might transition can result in behavior that varies between implementations, since the changes might be considered simultaneous in some implementations but not others.
When a style change event occurs, implementations must start transitions based on the computed values that changed in that event. If an element is not in the document during that style change even or was not in the document during the previous style change event, then transitions are not started for that element in that style change event.
When element is added, reflow is needed. The same applies to adding the class. However when you do both in single javascript round, browser takes its chance to optimize out the first one. In that case, there is only single (initial and final at the same time) style value, so no transition is going to happen.
The setTimeout trick works, because it delays the class addition to another javascript round, so there are two values present to the rendering engine, that needs to be calculated, as there is point in time, when the first one is presented to the user.
There is another exception of the batching rule. Browser need to calculate the immediate value, if you are trying to access it. One of these values is offsetWidth. When you are accessing it, the reflow is triggered. Another one is done separately during the actual display. Again, we have two different style values, so we can interpolate them in time.
This is really one of very few occasion, when this behaviour is desirable. Most of the time accessing the reflow-causing properties in between DOM modifications can cause serious slowdown.
The preferred solution may vary from person to person, but for me, the access of offsetWidth (or getComputedStyle()) is the best. There are cases, when setTimeout is fired without styles recalculation in between. This is rare case, mostly on loaded sites, but it happens. Then you won't get your animation. By accessing any calculated style, you are forcing the browser to actually calculate it
Trigger CSS transition on appended element
Explanation For the last part
The .css() method is a convenient way to get a style property from the first matched element, especially in light of the different ways browsers access most of those properties (the getComputedStyle() method in standards-based browsers versus the currentStyle and runtimeStyle properties in Internet Explorer) and the different terms browsers use for certain properties.
In a way .css() is jquery equivalent of javascript function getComputedStyle() which explains why adding the css property before adding class made everything work
Jquery .css() documentation
// Does not animate
var $a = $('<div>')
.addClass('box a')
.appendTo('#wrapper');
$a.css('opacity');
$a.addClass('in');
// Check it's not just jQuery
// does not animate
var e = document.createElement('div');
e.className = 'box e';
document.getElementById('wrapper').appendChild(e);
window.getComputedStyle(e).opacity;
e.className += ' in';
.box {
opacity: 0;
-webkit-transition: all 2s;
-moz-transition: all 2s;
transition: all 2s;
background-color: red;
height: 100px;
width: 100px;
margin: 10px;
}
.box.in {
opacity: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<div id="wrapper"></div>
Here is the listed work arounds [SO Question]
css transitions on new elements [SO Question]
The main solution is understanding setTimeout(fn, 0) and its usages.
This is not related to CSS animations or jQuery load method.
This is a situation when you do multiple tasks into DOM.
And delay time is not important at all, the main concept is using setTimeout.
Useful answers and tutorials:
Why is setTimeout(fn, 0) sometimes useful? (stackoverflow answer)
Events and timing in-depth (real-life examples of setTimeout(fn, 0))
How JavaScript Timers Work (by jQuery Creator, John Resig)
function insertNoDelay() {
$('<tr><td>No Delay</td></tr>')
.appendTo('tbody')
.addClass('green');
}
function insertWithDelay() {
var $elem = $('<tr><td>With Delay</td></tr>')
.appendTo('tbody');
setTimeout(function () {
$elem.addClass('green');
}, 0);
}
tr { transition: background-color 1000ms linear; }
.green { background: green; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tbody>
<tr><td>Hello World</td></tr>
</tbody>
</table>
<button onclick="insertNoDelay()">No Delay</button>
<button onclick="insertWithDelay()">With Delay</button>
This is a common problem caused by browsers. Basically, when new element is inserted it is not inserted immediately. So when you add the class, it is still not in the DOM and how it will be rendered is calculated after the class is added. When the element is added to the DOM, it already has the green background, it never had a white background so there is no transition to do. There are workarounds to overcome this as suggested here and there. I suggest you use requestAnimationFrame like this:
function load_tbody(row_id) {
$('tbody').load("load_tbody.php", function() {
requestAnimationFrame(function() {
$(row_id).addClass('green');
});
});
}
Edit:
Seems like the above solution doesn't work for all cases. I found an interesting hack here which triggers an event when an element is really parsed and added to the DOM. If you change the background color after the element is really added, the problem will not occur. Fiddle
Edit: If you want to try this solution (Won't work below IE9):
Include this CSS:
#keyframes nodeInserted {
from {
outline-color: #fff;
}
to {
outline-color: #000;
}
}
#-moz-keyframes nodeInserted {
from {
outline-color: #fff;
}
to {
outline-color: #000;
}
}
#-webkit-keyframes nodeInserted {
from {
outline-color: #fff;
}
to {
outline-color: #000;
}
}
#-ms-keyframes nodeInserted {
from {
outline-color: #fff;
}
to {
outline-color: #000;
}
}
#-o-keyframes nodeInserted {
from {
outline-color: #fff;
}
to {
outline-color: #000;
}
}
.nodeInsertedTarget {
animation-duration: 0.01s;
-o-animation-duration: 0.01s;
-ms-animation-duration: 0.01s;
-moz-animation-duration: 0.01s;
-webkit-animation-duration: 0.01s;
animation-name: nodeInserted;
-o-animation-name: nodeInserted;
-ms-animation-name: nodeInserted;
-moz-animation-name: nodeInserted;
-webkit-animation-name: nodeInserted;
}
And use this javascript:
nodeInsertedEvent= function(event){
event = event||window.event;
if (event.animationName == 'nodeInserted'){
var target = $(event.target);
target.addClass("green");
}
}
document.addEventListener('animationstart', nodeInsertedEvent, false);
document.addEventListener('MSAnimationStart', nodeInsertedEvent, false);
document.addEventListener('webkitAnimationStart', nodeInsertedEvent, false);
function load_tbody(row_id) {
$('tbody').load("load_tbody.php", function() {
$(row_id).addClass('nodeInsertedTarget');
});
}
Can be made into a generic solution or library. This is just a fast solution.
Here's the jsfiddle that I've been trying to debug:
http://jsfiddle.net/Neoheurist/x57fkzng/
HTML
<div id="birthday">Surprise Party</div>
<p></p>
<button onclick="blue()">Blue</button>
<button onclick="red()">Red</button>
<script>
function blue()
{
element=document.getElementById("birthday");
element.innerHTML="Happy";
element.style.background="blue";
element.style.width="150px";
element.style.opacity="1.0";
element.style.transition="width 2s,background 15s,opacity 2s";
}
function red()
{
element=document.getElementById("birthday");
element.innerHTML="Birthday";
element.style.background="red";
element.style.width="300px";
element.style.opacity="0.0";
element.style.transition="width 2s,background 4s,opacity 6s";
}
</script>
CSS
div
{
width:100px;
height:50px;
background:blue;
transition:width 2s,opacity 2s,background 15s;
}
div:hover
{
width:200px;
background:green;
opacity:0.25;
transition-timing-function:linear;
transition:width 2s,background 4s,opacity 6s;
}
Questions:
Why does clicking a button disable div:hover?
How can I prevent this from happening?
It's because HTML style attributes override element selectors in a css file. Any property set directly in an HTML style attribute will automatically be used over any property set in any css selector declaration.
Style attributes are much more specific than tag selectors (that's why they aren't recommended for use in fact).
According to the inspector in webkit this also includes the :hover state, so any inline style will stop a hover state from working.
You could use important, tempting as it might be, but that's not a good idea, because it takes the current problem with specificity that you're having and amplifies it even further, leading to a specificity nightmare. The only way to over-ride !important is with more !important, further down the document, or by using more specific selectors (like IDs) or longer chains of selectors and !important and so on, you can see how this can be horrible to maintain. Also any js that adds style to the HTML directly won't work either.
The best solution is to use javascript to add and remove css classes to trigger your changes. This will solve your problem as all your classes will have manageable specificity.
#birthday.blue {
background: blue;
width: 150px;
opacity: 1.0;
transition: width 2s,background 15s,opacity 2s;
}
#birthday.red {
background: red;
width: 300px;
opacity: 0.0;
transition: width 2s,background 4s,opacity 6s;
}
Then make sure the hover state is defined for all the combinations, so any class will :hover. This is not possible with inline styles.
#birthday:hover,
#birthday.blue:hover,
#birthday.red:hover
{
width: 200px;
background: green;
opacity: 0.2;
transition-timing-function: linear;
transition: width 2s,background 4s,opacity 6s;
}
I've put together a jsfiddle that demos this. I've used JQuery, for the sake of getting a demo together quickly and their addClass() method is great. Good effort to use pure js, it's a good habit to get into; this question will elaborate on how to add and remove classes in javascript
Plus; as an added bonus, you'll also have all your style in your style file and all your functionality in your javascript, which is better separation of concerns and makes the site styling DRYer and easier to re-use elsewhere in the project (you don't have styles stuck is js that you can't easily add elsewhere, which you copy instead, then try to change in one place and not the other ... we all do it, or our colleagues do!).
[Seen as I've brought up the subject of specificity you might also be interested to know that IDs are also pretty bad for that and unnecessary in style files]
You could make the div:hover attributes all !important like so:
div:hover{
width:200px !important;
}
However I've heard you'd want to avoid !important if possible, but this does what you want...
You can also use
document.addEventListener('DOMContentLoaded', function () {
var storedStyles;
var elem = document.getElementById("birthday");
elem.addEventListener('mouseenter', function () {
if (elem.hasAttribute('style')) {
storedStyles = elem.attributes.style.value;
elem.removeAttribute('style');
}
});
elem.addEventListener('mouseleave', function () {
if (storedStyles) {
elem.setAttribute('style', storedStyles);
} else {
storedStyles = null;
}
});
});
and clear all styles on mouse enter restoring hover styles precendence and setting back your inline styles on mouse leave
$(function(){
$('#product .btn-purchase')
.mouseover(function(){
$(this).stop().animate($(this).addClass('.btn-purchase-hover'), {duration:500})
})
.mouseout(function(){
$(this).stop().animate($(this).removeClass('.btn-purchase-hover'), {duration:500})
});
});
.. not sure why this isn't work, what am I doing wrong?
The animate function predominately works on a numeric CSS property.
for details you can look here : http://api.jquery.com/animate/
EDIT:
I would suggest that you use the fadeIn / out method in jQuery instead. For instance , you could do something like this below. ( Code off the top of my head, assumes you have the div with the correct image after the .btn-purchase )
$(function(){
$('#product .btn-purchase')
.mouseover(function(){
var $this = $(this);
$this.fadeOut(function(){ $this.next().fadeIn(); });
})
.mouseout(function(){
$this.fadeOut(function(){ $this.prev().fadeIn(); });
});
});
I would also like to add that incase you are not supporting IE, then using CSS transitions may be of help.
Have a look at this answer animating addClass/removeClass with jquery since it is definately a better / more efficient method in my opinion
Shreyas N
I know, that this is not exactly an answer to your question. But as Jan commented before, you might think about implementing this in css.
Just to give you an idea what it might look like:
#product .btn-purchase{
background-color: blue;
transition: all 1s ease-in;
-webkit-transition: all 1s ease-in;
-moz-transition: all 1s ease-in;
}
#product .btn-purchase:hover{
background-color: red;
}
You need to use document.ready so your code runs after the DOM has completely loaded, something like this :
$(document).ready(function() {
$('#product .btn-purchase')
.mouseover(function(){
$(this).stop().animate($(this).addClass('.btn-purchase-hover'), {duration:500})
})
.mouseout(function(){
$(this).stop().animate($(this).removeClass('.btn-purchase-hover'), {duration:500})
});
});
JQuery animate is used to animate CSS properties and not classes. As this answer shows, a better way might be to use CSS transitions (if you're only supporting CSS3). Alternatively, if you want to animate lots of things you'll need to supply them as CSS properties in the .animation() method.
Hope that solves you're issue.
if i apply a style to an element and immdiatily afterwards add css transition styles, the transition is applied to the style preceeding. this might not always be the intention.
i found a solution by using settimeout (0), is there any cleaner/more correct approach known ?
http://jsfiddle.net/nicib83/XP9E7/
$("div").css("opacity", 1);
$("div").css("-webkit-transition", "all 0.35s");
/* Works
window.setTimeout(function () {
$("div").css("-webkit-transition", "all 0.35s");
}, 0);
*/
best regards
Edit:
i didn't mean how best to set css styling but how to sequentially set styles when the first style should be applied without the second being active at that time but only afterwards, i wan to add transition afterwards. settimeout fixes it, best solution ?
It's much better to pre-define a class that contains both of the properties you want to apply, and add that class programmatically to the element. Both of the properties will be applied together.
.myClass {
opacity: 1;
-webkit-transition: all 0.35s;
}
$("div").addClass("myClass");
You could take a page from the book of Twitter Bootstrap:
fade {
opacity: 0;
-webkit-transition: opacity 0.15s linear;
-moz-transition:opacity 0.15s linear;
-o-transition:opacity 0.15s linear;
transition:opacity 0.15s linear;
}
.fade.in{
opacity:1;
}
then programatically add the .in class when you want it to fade in:
$("div").addClass("in");
with your original div looking something like:
<div class="fade">Box</div>
I've been running up against this myself and also found the setTimeout solution. After some research the issue is how the browser handles scheduling. The JavaScript runs in its own thread separate from the threads dealing with the UI and the DOM (which is why issues like UI blocking happen).
In cases like this both JavaScript statements run before the document registers the first change and it ends up applying both classes at the same time. setTimeout(fn,0) effectively makes the function asynchronous and shunts the functions to run at the next available opportunity. This allows the UI thread to catch up before the next class is added.
I've been doing some searching on here and Google and I can't seem to find an answer that quite fits what I'm trying to do. I have a single div with some text in it that does a fade effect by transitioning to a different background image on mouse hover. What I want to do is to tile/repeat that same div dynamically so it fills the entire body (or parent div). Kind of like using background-repeat:repeat but with a div instead of a background image. I like to see what kind of cool visual effects I can achieve with elements across the entire page fading in and out as the mouse moves over them.
Of course I could just copy and paste the same div in the code a bunch of times but there must be a better solution. I'm thinking javascript is needed, but the only things I've been able to find about cloning divs look to be a bit over my head and I'm wondering if there is a more simple solution.
The CSS and HTML that I'm using as an example is from menu links on a site I'm working on. It may not be the best example but I'm a bit new to CSS. Basically I want to tile the below div across an entire page.
Here is the css:
#fadediv {
background-image:url(images/buttonback.png);
transition: background-image 0.5s linear;
-moz-transition: background-image 0.5s linear;
-webkit-transition: background-image 0.5s linear;
}
#fadediv:hover {
background-image:url(images/buttonback2.jpg);
}
.fadedivtext {
display:block;
width:320px;
height:138px;
float:left;
font-size:30px;
color:#FFF;
text-align:center;
line-height:138px;
}
And the HTML snippet:
<div id="fadediv" class="fadedivtext">about me</div>
EDIT: Looks like there's a PHP example here that could work, in addition to the javascript example given below.
I think clone should work well for you -- it's not that complicated, especially when you're talking about a basic div. Just make sure to target classes instead of IDs (you're not supposed to have multiple elements with the same ID).
Here's a basic example using JQuery's clone:
var numberOfClones = 20;
var el = $("#fadediv");
for (i=0 ; i<numberOfClones ; i++) {
var newEl = el.clone();
$("#container").append(newEl);
}
http://jsfiddle.net/9P7bY/2/
Edit
This is a comment left by aug:
Or if you want to for some reason give each clone a unique id you can access the attribute field and change the id to something else
newE1.attr("id", newId);