I have an animation and JS for alternating 2 divs and change their background images (from an array of a few dozens images), sort of interchangeable divs. Everything works just fine, however when the animation runs I can see that my CPU is at 100%. At first I thought it might be due to setInterval, however when I changed the code from alternating the images to just increase a number with each iteration and log it to console - I saw dramatic CPU overload decrease, of about 40-50%. So I understood it might be due to animation.
Here's my HTML code:
<div class="wallpaper wallpaper-1" id="wallpaper-1"></div>
<div class="wallpaper wallpaper-2" id="wallpaper-2"></div>
CSS:
.wallpaper {
width: 100%;
height: 100%;
position: absolute;
opacity: 0;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
-webkit-transform: translateZ(0);
-webkit-animation-timing-function: linear;
}
.animate {
-webkit-animation-name: fadeInOut;
-webkit-animation-duration: 6s;
}
#-webkit-keyframes fadeInOut {
0% {
opacity: 0;
-webkit-transform: scale(1);
}
16% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
-webkit-transform: scale(1.1);
}
}
And JS that makes it all tick:
Wallpapers.get().then(function(list) {
var wp1, wp2, divs = [], path, bgs = [], counterBgs = 0, bgsLength, currentId, doInterval;
wp1 = document.getElementById('wallpaper-1');
wp2 = document.getElementById('wallpaper-2');
divs = [ wp1, wp2 ];
path = 'assets/img/wallpapers/';
bgs = list.data;
bgsLength = bgs.length;
//Preload images
for(var i = 0; i < bgsLength-1; i++) {
var wp = new Image();
wp.src = path+list.data[i];
}
function manageBg() {
setInterval(function(){
doInterval();
}, 4000);
}
doInterval = function doInterval() {
currentId = counterBgs % bgsLength;
if (counterBgs % 2 === 0) {
wp1.style.backgroundImage = "url(" + path + bgs[currentId] + ")";
wp1.classList.add('animate');
wp1.style.zIndex = 1;
wp2.style.zIndex = 0;
setTimeout(function() {
wp1.classList.remove('animate');
}, 5950);
} else {
wp2.style.backgroundImage = "url(" + path + bgs[currentId] + ")";
wp2.classList.add('animate');
wp1.style.zIndex = 0;
wp2.style.zIndex = 1;
setTimeout(function() {
wp2.classList.remove('animate');
}, 5950);
}
counterBgs++;
};
doInterval();
manageBg();
});
Any ideas how to reduce the CPU overload?
The answer is will-change property of css.
will-change is a property that optimizes animations by letting the browser know which properties and elements are just about to be
manipulated, potentially increasing the performance of that particular
operation.
Article Source: will-change - css tricks
The will-change property will use Hardware Acceleration in order to reduce load of your CPU and allocating your CSS3 animation/transformation to the GPU.
The Old: The translateZ() or translate3d() Hack
For quite some time now, we’ve been using what has been known as the
translateZ() (or translate3d()) hack (sometimes also called the null
transform hack) to trick the browser into pushing our animations and
transforms into hardware acceleration. We’ve been doing that by adding
a simple 3D transformation to an element that will not be transforming
in three-dimensional space. For example, an element that’s animated in
two-dimensional space can be hardware-accelerated by adding this
simple rule to it:
transform: translate3d(0, 0, 0);
Hardware-accelerating an operation results in the creation of what is
known as a compositor layer that is uploaded to and composited by the
GPU. However, force-hacking layer creation may not always be the
solution to certain performance bottlenecks on a page. Layer creation
techniques can boost page speed, but they come with a cost: they take
up memory in system RAM and on the GPU (particularly limited on mobile
devices) and having lots of them can have a bad impact (especially on
mobile devices), so they must be used wisely and you need to make sure
that hardware-accelerating your operation will really help the
performance of your page, and that a performance bottleneck is not
being caused by another operation on your page.
In order to avoid layer-creation hacks, a new CSS property has been
introduced, that allows us to inform the browser ahead of time of what
kinds of changes we are likely to make to an element, thus allowing it
to optimize how it handles the element ahead of time, performing
potentially-expensive work preparing for an operation such as an
animation, for example, before the animation actually begins. This
property is the new will-change property.
The New: The Glorious will-change Property
The will-change property allows you to inform the browser ahead of
time of what kinds of changes you are likely to make to an element, so
that it can set up the appropriate optimizations before they’re
needed, therefore avoiding a non-trivial start-up cost which can have
a negative effect on the responsiveness of a page. The elements can be
changed and rendered faster, and the page will be able to update
snappily, resulting in a smoother experience.
For example, when using CSS 3D Transforms on an element, the element
and its contents might be promoted to a layer, as we mentioned
earlier, before they are composited in (drawn onto the screen) later.
However, setting up the element in a fresh layer is a relatively
expensive operation, which can delay the start of a transform
animation by a noticeable fraction of a second, causing that
noticeable “flicker”.
In order to avoid this delay, you can inform the browser about the
changes some time before they actually happen. That way, it will have
some time to prepare for these changes, so that when these changes
occur, the element’s layer will be ready and the transform animation
can be performed and then the element can be rendered and the page
updated in quickly.
Using will-change, hinting to the browser about an upcoming
transformation can be as simple as adding this rule to the element
that you’re expecting to be transformed:
will-change: transform;
You can also declare to the browser your intention to change an
element’s scroll position (the element’s position in the visible
scroll window and how much of it is visible within that window), its
contents, or one or more of its CSS property values by specifying the
name of the properties you’re expecting to change. If you expect or
plan to change multiple values/aspects of an element, you can provide
a list of comma-separated values. For example, if you’re expecting the
element to be animated and moved (its position changed), you can
declare that to the browser like so:
will-change: transform, opacity;
Specifying what exactly you want to change allows the browser to make
better decisions about the optimizations that it needs to make for
these particular changes. This is obviously a better way to achieve a
speed boost without resorting to hacks and forcing the browser into
layer creations that may or may not be necessary or useful.
Article Source - Everything You Need to Know About the CSS will-change Property
Your css will be
.wallpaper {
width: 100%;
height: 100%;
position: absolute;
opacity: 0;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
will-change: transform, opacity;
-webkit-animation-timing-function: linear;
}
.animate {
-webkit-animation-name: fadeInOut;
-webkit-animation-duration: 6s;
}
#-webkit-keyframes fadeInOut {
0% {
opacity: 0;
-webkit-transform: scale(1);
}
16% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
-webkit-transform: scale(1.1);
}
}
Other Useful Sources:
Everything You Need to Know About the CSS will-change Property
CSS-TRICKS Article
SNOOK.CA
W3C Editors Draft
Mozilla Developer Network
Browser Support
will-change: update browser support
Related
As an example, on a laptop that has a large screen, but no dedicated graphics card, the rule:
.menu-item:hover {
filter: blur(5px);
}
lags significantly, but it's not easy to target with just a #media (max-width: 600px) or #supports query.
Ideally, it would be amazing to have something like
#performance ("good enough to handle whatever it is in question") {}
or if the #supports media query took performance into account.
Does such a thing exist? Or is there any way to work out something analogous?
JavaScript could do something like requestAnimationFrame and time the FPS during animations (using event registration on transition/transitionEnd and animation/animationEnd) then if framerate deemed too low, it could apply a style such as:
*, *:before, *:after {
transition-property: none !important;
transform: none !important;
animation: none !important;
/* ... */
}
But a CSS-only option that targets low-power laptops... my recommendation would be to first try this trick:
-webkit-transform: translate3d(0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
That will make sure it ends up using the dedicated graphics card if available. It might be enough to get the performance up.
You may also want to look at #keyframes which will help when the framerate is inconsistent.
You can detect most browser features with Modernizr for example. Take a look at the documentation to see what you can detect.
However, there is no truly reliable way to detect the hardware via either CSS or JavaScript. You could try to detect FPS like illustrated here: calculate FPS in Canvas using requestAnimationFrame
I have found some code on GitHub that may work for you:
var canvas = document.createElement('canvas');
var gl;
var debugInfo;
var vendor;
var renderer;
try {
gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
} catch (e) {
}
if (gl) {
debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
}
// Sample output:
//
// » console.log(renderer);
// ATI Technologies Inc. AMD Radeon R9 M370X OpenGL Engine
It detects the graphics hardware vendor name via WebGL, so obviously it requires a browser with WebGL support. If that's no problem for you, this may well do what you need.
Regarding a CSS-only solution I'm afraid you're out of luck.
I have a carousel that takes a state of {pages: [...], currentPage: 0}. If I set currentPage = 1 I want the carousel to slide left. The same thing should happen if I increase the number again, and it should slide right I decrease it.
I can't work out how this should be done with immutable data. The animation shouldn't be represented in the state object (should it?), but storing a property on the React component removes its "pure" functional nature.
What's the best way to approach this problem?
Just show me an example
This doesn't use Immutable.js, but the current property is just a number, which is immutable in JS, so in some regards it's the same idea:
http://jsbin.com/ligejacolo/edit?js,output
IMO the best option is ReactCSSTransitionGroup
+1 for the unnecessarily snarky reference to ReactCSSTransitionGroup in the comments, if you can use that.
After that, let the browser manage state via CSS transitions
I would store the pages and currentPage as props, regardless of whether the values are Immutable.js instances (props are a stateful part of your UI, just like state, don't let the name fool you!). When they're stored as props, it provides a useful API to the users of your component.
If you're using CSS transitions then at any given moment you should be able to render the markup and classes based on this information.
For example, given the (over-simplified markup):
<div class=container>
<!-- set the class to something like "inner pane2" to go to the second pane -->
<div class=inner>
<span class=pane>...</span>
<span class=pane>...</span>
<span class=pane>...</span>
</div>
</div>
And some CSS like:
.container {
position: relative;
}
.inner {
position: absolute;
left: 0;
transition: left 0.3s ease-in-out;
}
.pane {
width: 100px;
}
.inner.pane2 {
left: -100px;
}
.inner.pane3 {
left: -200px;
}
The browser takes care of mid-transition changes to the properties.
Often times you'll need to know when the animation is done, which you can accomplish with a transitionend listener and isAnimating flag (which I would store in the state, not the props). Set isAnimating when currentPage changes and clear it on transitionend.
You'll also often need to know which direction you're transitioning, which you can accomplish with a previousPage prop.
Finally, if you're just using JavaScript
Keep a reference to the current transition in the state (possibly the jQuery selection used to start the animation, if that's what you're using). When the transition is done, remove the reference. If the currentPage changes mid-transition, call .stop() on the selection (or whatever API you're using).
i came across an issue today and it took me so long to debug, I couldn't find a solution anywhere online so I thought it would be useful to document
It seems that transitions do not work on Firefox if the parent's "overflow" property is changed together with the transition - ie:
.parent { overflow: hidden; }
.parent:hover { overflow: visible; }
.child { opacity: 1; transition: opacity 1s linear; }
.parent:hover .child { opacity: 0; }
The transitions will not work on the child. Remove the "overflow:visible" property from the hovered parent, and everything is ok. It seems that changing the overflow on the child itself does not cause any issues, which is weird.
Here's a js fiddle for this http://jsfiddle.net/qzMj9/13/
does anyone know why this happens? is it a ff bug or the correct functionality? it works on webkit!
This looks like https://bugzilla.mozilla.org/show_bug.cgi?id=625289 to me: the parent is having its CSS boxes reconstructed, which loses the old computed style on the child, which means no transition start, since that's triggered by computed style changes.
I have an absolute positioned div inside another absolute positioned div. The child div content is much bigger than the parent can contain. This is by design. I need the child div to spill out of its parent. It does so in every other browser except IE 8 (IE 7 looks OK, not sure) In IE8 the part of the child that is out of parent is clipped. It is there, but just not visible as can be verified by IE developer tools.
I tried z-index, tried explicitly setting overflow:visible, no luck at all.
UPDATE: I found out that the problem is caused by a filter defined in the parent div like this:
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#66C6DEA2,endColorstr=#66C6DEA2)";
Anyone has an idea how to work around that?
I solved it using this How do I stop internet explorer's propriety gradient filter from cutting off content that should overflow?
My solution is a little modified, just put an empty div with class "ie_rgba_fix" inside the container you want transparent, add this CSS someplace IE specific and the children will not clip anymore as with overflow: hidden
/* IE8 RGB A workaround */
div.ie_rgba_fix
{
position: absolute;
z-index: -1;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: transparent;
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#66C6DEA2,endColorstr=#66C6DEA2)";
}
Try making the elements inside the absolute positioned element position:relative, and/or add a wrapper around all the elements in that absolute positioned element and relative it.
i took a tip from the checked answer here & the linked question, but didn't want to use an empty DIV (especially because other browsers don't need it).
Instead, i set up IE8-specific CSS that uses the container DIV's :before pseudo-element.
However, pseudo-elements are styled content, not DOM objects, so the -ms-filter property is useless. To compromise, i use a PNG matching the original filter i wanted (actually a data: URL, but either works) as the background-image.
i force the pseudo-element to the full size of the container, absolute-position it, and ta-da, the child element is visible outside the parent, and the parent still gets a transparency background.
.container.ie8 {
background-color: transparent;
position: relative;
}
.container.ie8:before {
background-image: url("data:image/png;base64,...");
display: block;
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
}
CSS3 supports multiple background images, for example:
foo { background-image: url(/i/image1.jpg), url(/i/image2.jpg); }
I'd like to be able to add a secondary image to an element with a class though.
So for example, say you have a nav menu. And each item has a background image. When a nav item is selected you want to layer on another background image.
I do not see a way to 'add' a background image instead of redeclaring the whole background property. This is a pain because in order to do this with multi-backgrounds, you would have to write the base bg image over and over for each item if the items have unique images.
Ideally I'd be able to do something like this:
li { background: url(baseImage.jpg); }
li.selected { background: url(selectedIndicator.jpg); }
And have li.selected's end result appear the same if I did:
li.selected { background: url(baseImage.jpg), url(selectedIndicator.jpg); }
Update: I also tried the following with no luck (I believe backgrounds are not inherited..)
li { background: url(baseImage.jpg), none; }
li.selected { background: inherit, url(selectedIndicator.jpg); }
That is, in any case, not the way CSS inheritance works. inherit implies that an element should take on the attributes of it's parent element, not previous declarations affecting the same element.
What you want has been proposed as a way to make CSS more object-oriented, but the closest you will get is with a pre-processor like SASS.
For now you actually just have to re-state the first image along with the second.
I don't think this is possible, I think you'd have to redefine the whole rule every time.
For example, you could just add a "wrapper" around every item that has the initial background, with the actual item having a transparent background. Then add the background on the item itself when it's selected.
Additive CSS rules still aren't possible as far as I know.
You could try applying the second image to the ::after pseudo element:
li { background: url(baseImage.jpg); position: relative; }
li.selected::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
background: url(selectedIndicator.jpg);
}
I had the same need as you recently.
I finally thought about it and solved using css variables.
::root { --selectdropdown: url( '../elements/expand-dark.svg' ); }
select.gender.female { background-image: var(--selectdropdown), url( '../elements/female-dark.svg' ); }
When you resetting the attribute, just specify the variable again in the list!