Layout being triggered on css transform - css

I have an element div which has the following styles —
height: '100%',
willChange: 'transform',
marginRight: '15px',
transform: 'translateX(-100%)'
On touch move event I am updating the elements style.transform as follows —
el.style.transform = `translateX(${100 * x - 100}%)`
Apparently as the the information here https://csstriggers.com/transform
These changes are not supposed to cause any Layout updates. But I still see a lot of Layout events on the timeline.

Css transforms is one of the property that if used through Javascript does cause browser to synchoronously calculate style and layout a.k.a reflow or Layout trashing. Have a look at this to learn about all possible elements that can cause Layout trashing https://gist.github.com/paulirish/5d52fb081b3570c81e3a But here is the thing, According to my understandings, Transform is one of the property which browser can animate very cheaply with ease. and
transforms are the best properties to animate because the GPU can
assist with the heavy lifting ref: HTML5rocks high performance animations
There is a nice example given on the topic of Avoid large, complex layouts and layout thrashing on developers.google, where at the bottom you could see a example of offsetWidth on reading writing DOM. I think you can try that with transform, i reckon it could be helpful.
EDIT: #Tushar could you provide js fiddle for your problem or advised how can i regenerate the exact probelem? I am raising this issue with csstriggers on git as well to learn more insight about your question. github.com/GoogleChrome/css-triggers/issues/23

Related

css animation equivalent of the SVG/SMIL begin="scroll" (stc) events (no javascript)

I am converting svg/smil animations to use svg/css to take advantage of some better animation capabilities in css in a situation where there is no javascript.
Svg/Smil animations have a begin attribute which can be a named event (https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/begin) which allows interaction events like click, scroll, loaded, resize and so on to trigger the start of the animation (these listed are my most common required cases).
I've poured over a few css psuedoselector lists and found little, but there are always interesting tricks and hacks people have devised for capturing properties without javascript that I'm hoping someone might point me in a direction here. Of particular interst is if the svg element is in view (e.g. scrolled in / unoccluded / made visible), or if the document (or the svg) has been interacted with (clicked/tapped).

Manually invalidating a composite layer in Chrome

Background
I'm working on a web app built in HTML (not WebGL/Canvas) which includes 2d viewport controls for panning and zooming the page content. Something like Figma, perhaps, but rendered entirely with DOM, which is a hard technical requirement.
To achieve the viewport functionality I've made extensive use of CSS transform to power all offsets and animations in order to reduce the work required to render changes to compositing, as much as possible. The "canvas" of my app contains many discrete items which can be moved and resized by the user, similar to any typical OS window manager. These 'widgets' may contain their own scrollable content.
For example, after panning 50px,25px and zooming to 1.5x, the DOM and transform values might look like this for a particular "canvas" which has a widget at (20, 100):
<div id="canvas" style="transform: scale(1.5, 1.5) translate(50px, 25px)">
<div id="widget-1" style="transform: translate(20px, 100px)" />
</div>
After a lot of experimentation I've discovered that the most efficient way to render these items across multiple browsers is to promote each individual 'widget' to its own layer by applying will-change: transform to the outermost element. This results in a pretty reasonable framerate, even with a lot of content in the frame while panning and zooming.
Webkit Misbehavior
However, there's one catch - on Webkit-based browsers, when zooming (which is applied via scale transform in CSS on the root canvas element), the contents of the widgets are not re-rasterized to accommodate the new scale value. At a zoom greater than 1x, this produces noticeable blurriness. Images with text, in particular, are basically unreadable.
Above, one image widget and another DOM text widget at 1x (native) scale.
And now, at 2x scale (you won't be able to tell the difference inline in this post, but you can see it at full resolution). Notice that the image is just as illegible as before, and the text is blurry.
For a live reproduction of this problem, see this CodeSandbox (leave "Animation" unchecked).
Side note: this only happens on Chrome, Safari, and Edge - so it seems like an artifact of Webkit's rendering behavior. Firefox actually scales everything quite nicely, and with a faster framerate to boot.
However, the performance of this approach is desirable. After trying some other configurations of layering, I decided the best approach would be to try to force the browser to re-rasterize the widgets once a zoom change animation was completed.
The Hack
The intended goal is to allow the old rasterized textures to persist during the zoom animation to make it as smooth as possible (so the blurriness seen above will be present while the viewport scales up/down), but to trigger a re-rasterization at the final scale once the animation is complete - re-draw all the widgets, taking current scale into account so that their contents are sharp and legible even at 2x zoom.
To correct this problem, I have implemented what feels like a "hack": after the end of each zoom animation, I'm toggling the will-change on all the widgets from transform to initial for 1 frame, and then back again:
const rerasterize = () => {
requestAnimationFrame(() => {
element.style.willChange = 'initial';
requestAnimationFrame(() => {
element.style.willChange = 'transform';
});
});
};
This actually works. Now when I zoom in to 2x, the image text is legible and the DOM text is nice and sharp:
(In my app, you can even see the moment when the zoom animation "settles" and text "pops" into high-resolution.)
However, if I understand correctly, the code above is actually forcing the browser to dispose of the composite layer for each widget for 1 frame, and then recreate it. While the performance seems acceptable in practice, I would much prefer to just ask the browser to invalidate the layer which it has already constructed.
The Question
So, with all that context aside, the question is simply: is there a way to manually trigger an invalidation of a composite layer without trashing it? A magic CSS incantation, perhaps?
I'm also open to alternative approaches with respect to layer grouping which might improve behavior without harming render performance.
Other Stuff I Tried
One thing I noticed when creating the reproduction CodeSandbox is that if I add a transition property to the "canvas" element (which is being transformed to achieve the viewport changes), even if widgets are composited in different layers, it appears to fix the blurriness. You can see this by checking "Animation" in the demo. However, my animations are currently done via JS, so adding a secondary CSS transition on top of this doesn't seem like a great plan.
I tried ripping out JS animations entirely and relying solely on transition, but surprisingly this did not seem to help. Panning and zooming felt noticeably choppier (some of this might come down to native easing feeling less natural than JS spring-based easing), but more concerningly the GPU memory usage and dropped frames were notably worse than without transition - which leads me to believe that transition might be causing a lot more work than I really want on the GPU for my use case (perhaps invalidating layers frequently during animations, when I would prefer them to remain intact until the transition ends).

Are offscreen infinitely-looped CSS animations optimized by the browser?

Are there any in-browser optimizations for off-screen infinitely-running CSS animations (infinite animations which are applied to Elements that are present/displayed within the Document, just not in-view at a certain time)?
For the sake of a more straight-forward discussion, I will refer to CSS animations that involve only composition: using only transforms and/or opacity.
If we have a CSS animation, that has animation-timing-function: infinite;, how is it handled if the animated element is not actually on the screen / in-view?
There are some known scenarios regarding animations, which are irrelevant to this use case:
The animation will pause if the browser tab is pushed into the background. MDN
The animation is ignored if the element is removed from the Document, with something like display: none; (can't find a quotable source atm);
If the element is simply 'beyond the scroll', and the user can reach it at anytime, during the time the element is off-screen :
Does the animation run as normal, and has the same consumption, regardless if the element is visible or not?
Does it run as normal, but because it is not painted, the process itself becomes less expensive? - would make more sense.
Is the animation paused entirely? - would assume not, as the browser must know what the 'current progress' would have been at the moment the element needs to be painted again.
Or are there any other in-browser processes which optimize this, when these conditions are met?
Any documented answer, or any redirect to some resource, which could shed some light on the matter would be greatly appreciated.
This discussion would be relevant in determining if : manually removing infinite animations (for elements that are off-screen) would be a thing to consider, to achieve better performance.
TLDR
As long as the properties we want to animate do not trigger reflow/repaint, the animation can be optimized by the browser, which means better performance.
See off main thread animation.

Is there a way to use CSS -transform and not affect children elements

I created a mockup to demonstrate my problem. I fear the solution falls in what I did with the first example (box1).
Just not sure why I can't apply a css transform to a parent element and avoid applying it to the child element or at least override it.
Let me know if there is a way to get the effect of the first example using the transform property. I don't want the second image to be scaled as well. Just the parent div.
Note
I'm trying to use this property to enable GPU acceleration.
Have not played with this in any other browser but the chrome.
But it looks like overflow hidden set on parent element implies resizing inheritance on image.
So if you set overflow auto on the boxes to which animation is applied, it should fix the resizing inheritance.
http://jsfiddle.net/gnrlbzik/5Q8EC/ tests the overflow set to auto, that keeps image dimensions in tact.
To be semantic I don't think you should be able to stop it from transforming a child element being you are transforming/scaling it's container, not resizing it. A transform is not just animating the width like the example on box 1, it scales the x.
I believe this question is about trying to gain some performance boost by using features that use the GPU to process animations. This is possible, I believe, by effectively treating the elements as an image that the GPU then animates. Thus you are not able to have an element inside that dynamically keeps its size/shape during an animation. Maybe there is a way to counter the animation by transforming the element within that you would like to keep static, but this will probably not be efficient nor effective.
Note: I am not well versed in the actual technology webkit uses to render these transformations. So sorry if the finer details of the rendering are not quite accurate. I will update this if anyone comments with a better description of how transform works.
I wanted to use -transform to get the GPU performance perk. I guess I'll continue to use the width animation: http://jsfiddle.net/Vyaf3/22/ but with applying a css3 property that would enable the GPU acceleration.

Is there a proper way to dynamically update parts of transforms and transitions programmatically? e.g. transform-origin or duration

I am building an iOS Safari touch-based app and find CSS transitions and transforms work great.
But I have two things I can't seem to achieve using just JavaScript and CSS.
Usually I want the element to translate with a duration of 0.2s. But in code I occasionally want to instantly translate (initial positioning). If I update the duration to 0 or remove the transition style entirely, it doesn't seem to have an effect (acts as if the 0.2s is immutable)
When zooming I want to update the transform-origin property. This also does not seem to work, and seems stuck at my original stylesheet-set value. Specifically I am trying to do this on the gesturestart and gestureend events
Hopefully there is an approach to making this work. Maybe setTimeout async processing?
Update:
I have a js fiddle example to better illustrate my problem in #1, and it turns out that setTimeout fixes it, but it's a strange solution that I'd be interested in improving:
http://jsfiddle.net/w9E7t/
It seems like I'm unable to do these steps synchronously:
set appropriate classes for an instant transition
apply transition style
reset classes to their default (with transition) state
You can accomplish this by using two CSS classes, one which sets the timing-duration to 0s and the other which sets it to 200ms and then applying the classes programmatically in JS. Take a look at this JSFiddle for an example.
One of Web development's best practices is to separate your document's parts into structure/content (HTML), presentation (CSS), and interaction/behavior (JS). In the example above, the presentation of the content (a timed translation) stays defined in CSS while JS is used only to respond to an interaction (a MouseClick event).
You should be able to change an element's transform-origin using the WebkitTransformOrigin style property in JS. Here is an example JSFiddle. I tested this on my iPhone4 and it correctly logged the new transform-origins in the console. Again, this can also be achieved by using JS only to listen for the gesture events and updating the element's class, while keeping the style rules of the class defined in your presentation logic (CSS).
Note well: In my examples, I am updating the element's .className. Since it is possible that your elements already have many classes, you may need to implement addClass/removeClass functions to properly set the correct classes, several examples of which can found on the Web.
Update:
Sorry for the delay... There are two ways you can approach this problem and the first you have already discovered.
Another way to handle switching back the class name is to use the webkitTransitionEnd property. This fires whenever a transition on the element finishes. It would look like this:
document.getElementById('puck').addEventListener('webkitTransitionEnd', function() {
puck
.removeClass('without_transition')
.addClass('with_transition')
}, false);
Unfortunately, when the transition-duration property is set to 0, this event is not fired :( I'm not sure if that is by design or a bug, but that's just how it's currently implemented (though I'm guessing it's by design since at this point the browser is not really doing a transition but rather just applying the transformation). The workaround in this approach is to set the transition-duration to 1ms (which will essentially look instant).
While the setTimeout approach looks hackish, many mobile framework groups use it throughout their code since the function will fire after the transition that occurs from switching classes (similar to transitionEnd). Take a look at Sencha Touch and you will find it numerous times.
I've forked your JSfiddle to show my example here.

Resources