I have a script that applies certain css styles to an element, then adds a transition style to the element, and then applies another css style to that element. What I'm trying to do is have the element get styles applied to it instantly, and then animate the next change. The code is basic, just set styles, then set transition styles, then set the final styles. But I'm experiencing that the first property being changed (the one without the transition) is having a transition applied to it, even though I do not set the transition property until afterwards. I have double checked that the element does not already have a transition property applied to it. Why is this?
Also, If I leave a 50 millisecond delay between applying the first styles and the transition, it works as expected.
You have to force a relayout after the first styles are applied (so they are processed without transitions) and then you can apply the styles that lead to a transition. The way you are doing it now, all the opeartions are being collapsed into one operation and thus everything is undergoing the transitions.
The simplest way to get the relayout is to apply the first CSS properties, then do a setTimeout(fn, 1) to apply the second set of properties in the timer callback. There are also other ways to force a relayout by requesting certain properties that trigger a relayout. I don't remember exactly which properties those are off the top of my head (would take some research).
I haven't tried this myself, but I think requesting a size property on your element such as .offsetHeight will force the relayout. The browser realizes that there are pending style changes and that those pending style changes might affect the size request so it does a relayout synchronously before returning the .offsetHeight value, thus solving your issue.
A somewhat similar question and answer: "Force Reflow" in CSS transitions in Bootstrap
Related
The problem context
I need to resolve the height of the content of an iframe after loading it (in order to adapt the height of the iframe element itself). The problem is that the iframe could be in a hidden state (one of its containers/parents set to display:none), when the loading is done.
I can't find a way to get the correct height of the iframe content as long as I don't display it. Using jQuery.height() returns 0 on Firefox.
An example demo here:
https://codepen.io/anon/pen/gKBQeP?editors=1111
(you'll notice how the height is reported differently in case you immediately click on the Tab3, where the iframe is, making that visible, or if you wait a couple of seconds after loading and then click on the Tab3)
Cannot write height on the element, right after displaying it.
Moreover, after making it visible again I still cannot get the real height of the content; it still returns 0 like it is hidden. I assume because the iframe-content is still in the process of getting rendered, even if the DOM tree of the iframe has been shown already.
If I setTimeout few milliseconds after making it visible then I can get the correct height (that doesn't make much sense to me....🤔).
I really don't like to set a timeout in order to read the content height.
What is a reliable, cross-browser, way to get the height of a hidden element, even when this is hidden (or in the process of becoming visible)?
My solution
At the moment I:
trigger the read/write of the height right after I know the element is visible again.
use setTimeout() to wait half-second (feels sluggish 😒) before reading/writing the height of the element.
Note (the actual question)
I am trying to find less hacky as possible solutions; so I want to avoid:
displaying (or cloning) the element quickly (taking care saving+restoring css properties, making them persistent and inline; or taking care of avoiding flickering in the page), to read the dimensions and quickly set it back to hidden (😖).
using setTimeout to wait the element dimensions being restored (and readable/writeable correctly) in order to work on them immediately after showing the element itself.
It's a bit hacky but rather than display:none (I assume that's how it's being hidden) you could set something like:
top: -10000px;
left: -10000px;
position: absolute;
It's "hidden" since it won't be visible, but you will still be able to get its height. Then after you get the height you can remove these styles to make it visible.
Introductory information:
I've made a fixed menu button to show the navigation menu when using a mobile device. For this application I'm using the Headroom.js script to make the button smaller when scrolling downwards to ensure that it doesn't block too much of the content. The animation/transition is applied by adding a class with the given changes.
In the original method i changed the size and look of the button by changing height/width of the parent element and padding of the child element with CSS (and css transition).
The new method, which I've read could/should be better according to various sites, is changing the size of the button by using transform: scale(). Note that i'm also moving the element a bit by also applying translate3d(20px,20px,0) in this method. However, it feels a bit smoother when scrolling using the transform: scale() method (could be a placebo effect though), but using chrome dev tools' timeline gives me seemingly inconclusive results.
Therefore a part of my question is also how I should evaluate the best method. Is timeline in Chrome Dev tools the best option, or is there a better way to do it? And which elements of the timeline should I base my choices on? and the other thing is, based on your interpretation of the images and/or tests combined with your knowledge, which method performs the best (or should perform the best in theory)?
Beneath you can see two examples of the timeline with each method.
Changing height/width and padding (original method):
Method using transform: scale() to change the size:
Also you can try the different methods in fiddles here:
link: Original method changing height/width and padding
link to new method: using transform:scale
Please ignore the poor layout of everything; especially the button. The ugly image inside the menu button is just to show, that there's an image included in the layout on my own page and to take that into performance considerations. The images in the back is also included since it's a webshop with a lot of images which could influence performance.
CSS for added class that makes the changes in the original method:
.mobile-nav.headroom--unpinned {
height: 40px;
width: 40px;
}
.headroom--unpinned .mobile-content{
padding-top:4px;
}
CSS for the added class using transform:scale():
.mobile-nav.headroom--unpinned {
transform:scale(.5) translate3d(20px,20px,0);
}
So to summarize my questions:
How do I evaluate which methods has the best performance, and which method would you say performs the best?
A final note: I know that the methods are different (animating different things and more elements in the original method) but these are the 2 options which i prefer as it is right now.
I believe you are missing the point, Chris: the reason why no other property but transform and opacity should ever be animated is because they don't trigger a repaint in anything else, even if the element is in the document flow (and because you can basically do anything with these two alone in like 95% of the cases).
From the "hit-on-performance" point of view, there are two types of animations:
those that trigger a repaint in other elements than the animated element
those that do not.
That's the main reason behind recommending animations by transform, opacity or position:relative;left|right|top|left. Because they don't actually move the element in flow, thus not triggering a repaint to every single other element in flow after the one being animated.
Now, if the said parent was positioned absolute (which I assume to be the case), it wouldn't have triggered a repaint to the rest of DOM anyway so the differences between that method and transform would have been minor. Inconclusive, as you put it. In theory, repainting two elements instead of one should be slower.
If you need to test, make 10k clones and trigger animation on all of them, with each method.
That will be conclusive.
If you really want to min-max this (as in spend absurd amounts of time on hardly noticeable improvements, as I do) you will find plenty of resources that will recommend:
replacing any .animate() with .velocity()
never animating anything but transform or opacity, although Velocity claims they animate anything without a hit on performance (i find that debatable/arguable, at best) - but it's a net improvement over .animate()
sticking to CSS transitions, if possible (basically if you don't need chains)
using Web Animations API
Personal advice: never count on synced CSS animations, especially when you have many of them. If you change tabs or the system does something extremely resource heavy for a while, your animations will be way off. If you need chains, chain.
I have a div and I want it to make a transition from font-size 20px to font-size 40px and also change its color. I know I can make this with maaany other alternativas, but I want to explore the usage of "transition" and I think something is wrong.
If I do this:
$("#xxx").css({transition:"all 1500ms ease-out 0ms",color:"#00FFFF", fontSize:"40px"});
The jQuery should first set the "transition", and after that set the color and font-size AND THE BROWSER should make the transition. But it's not the case, the font-size and color are applied immediatly.
What is even strange is that if I first set the transition and after 2 seconds set the font-size and color, then the transition will happen smoothly. Why?
Check the example below. If you open the link the browser will apply the color and font-size immediatly, instead of making the transition smootly. Why?
https://jsfiddle.net/hw33bghm/
The reason why this isn't behaving in the way that you expect is due to how reflow works in the DOM. Reflow is the web browser process for re-calculating the positions and geometries of elements in the DOM, and can be triggered by many things, including in this case, adjusting the CSS properties of an element.
When you make the $("#xxx").css({...}); call with multiple CSS properties you are giving the browser a batch of layout operations to perform and it will perform all of them in a user-blocking manner. Your styles are being applied immediately because there is no transition property until the operation is done and your new DOM is rendered.
Further, all modern browsers have optimizations to minimize reflow (again, it's a blocking operation and as such can affect performance), so if you simply separated setting your properties:
$("#xxx").css({transition:"all 1500ms ease-out 0ms"});
$("#xxx").css({color:"#00FFFF", fontSize:"40px"});
You might expect it to set the transition property, reflow, then set the color and fontSize, triggering the transition and working the way you intended. It won't though, those operations will be batched by the browser and again everything will happen in a single reflow.
This is also why it does work when you wait a second before setting the color and fontSize. Those layout operations are no longer batched together and so this time you have a transition property when the CSS for the other two are set.
For a little more information check out: https://developers.google.com/speed/articles/reflow#guidelines
I have noticed that (using jQuery in Chrome 43) transitions are disabled when the element has display: none. Is this standarized behavior on all browsers, a feature of jQuery, or is it something that cannot be relied on in production?
The transition is enabled when the CSS statements to be animated are changed in a deferred function. Take a look at this JSFiddle. Uncomment line 3 or 6 to see it for yourself.
SOLUTION:
This behavior cannot be relied upon in production as it seems to be a product of optimization/design choices rather than specification (as per #Andriy Horens answer). Instead you should turn on and off the animation (transition-property: none) with a class. Failing to use a class rendered it unreliable for me in Chrome 43. Chrome did also require separate frames (defer the code with a timeout of 0) to update some CSS statements. Just defer anything related to animations if you want to save development time.
According to MDN:
Display
In addition to the many different display box types, the value none lets you turn off the display of an element; when you use none, all descendant elements also have their display turned off. The document is rendered as though the element doesn't exist in the document tree.
So i think elements with display set to none will not be rendered at all across all browsers and therefore transition or any other visual effect won't be applied.
You can also test yourself by subscribing to transitionend event:
$(element).on("transitionend", function () {
console.log("transition ended");
});
Update:
It is also per w3c specification:
And some values (such as display:none, display: contents, and box-suppress: discard) cause the element and/or its descendants to not generate any boxes at all.
Where boxes are visual representations of element. And transition is definitely a part of visual representation as it also can affect layout e.g. when you change relative position of element with transition applied.
Here is one more example of how different are animations of elements with display : none and visibility : hidden in other words of rendered element and not-rendered one.
JSFiddle DEMO
I am rewriting CSS (don't ask why) in a way that I'm changing the style attributes via HTMLElement.setAttribute method. The problem sometimes occurs when I have computed styles that make no sense. For example (which happens most frequently), div parent has height value smaller than his child div element, in the computed styles. This makes some problems with the desired layout of the page. It is not easy, actually it is very hard, for me to change the algorithm that rewrites CSS, so does anybody know what is the cause of the problem and what would be the most appropriate way to solve it.
My assumption is that somehow some of the changes are not applied, thus I should force the css parser to recompute the values. Did anybody encounter such problem?
Assuming you are still trying to display your container at the height that you specify, even if it is smaller than the content that is inside it, you should use the overflow property.
overflow: hidden will cause the extra content to disappear.
overflow: auto will add a scrollbar to the container to allow you to scroll in the container.