Fasterdom: Optimized Browser Rendering
Alexander ZinchukFasterdom is part of the Teact library, which is used as a foundation for Telegram Air (Web A) and MyTonWallet. It facilitates performance optimizations by properly leveraging the browser’s rendering cycle.
This article outlines several best practices for maximizing browser rendering efficiency and main-thread performance to deliver smooth, highly responsive UI behavior.
Why do we need Fasterdom?
We can divide DOM operations into mutations and measurements. A mutation happens when you change something in the DOM (such as assigning a class to an element), and a measurement happens when you read something from it (such as element's offsetWidth). It is important to separate these operations from each other because of the underlying browser rendering cycle.
The browser renders document updates in a cycle of animation frames, and there is a must-see explanation video of the whole process. The general takeaway is that the browser stakes all your mutation calls until the rendering moment, which is either the end of an animation frame or a forced reflow caused by a measurement call.
Forced reflows are bad (because the browser needs to recalculate layout and styles instead of leveraging computation cache), so the idea is to avoid measurements after mutations within a single animation frame.

Web frameworks such as React or Teact already respect this during virtual DOM operations. Still, there are cases when the developer needs to keep that in mind, particularly in component effects and event callbacks.
To achieve that, Fasterdom provides a requestMutation method that allows batching and postponing mutations to the end of the animation frame to make sure they always execute after all measurements.
Working with DOM from component effects
In most cases, you will be good just with savvy use of useEffect and useLayoutEffect:
useLayoutEffectis needed when modifying anything in the DOM. It is called immediately after the component render in DOM and is a safe and easy way to apply some additional mutations: add/remove class names, modifystyleanddatasetproperties, etc.- In all other cases, you would use
useEffect. It is called in the following animation frame after component render (when layout is already calculated and cached by the browser) and can be used for both measuring DOM properties (such asoffsetWidthandgetBoundingClientRect), attaching event listeners and non-DOM calls (such as updating component state or calling a global action).
It gets more interesting when you need to combine measuring and mutating, so let's consider some cases.
- You need to measure an element's width and then assign the same width to another element. In this case, use
useEffectfor the measurement part and addrequestMutationinside it to schedule the mutation part. - You need to add a class to an element and then calculate its width. Use
useLayoutEffectfor assigning the class and addrequestMeasureinside it to schedule the measurement part.
Working with DOM from event callbacks
Event callbacks are always called during the measurement phase, so you can freely read things such as offsetWidth or any other DOM parameters. However, if you need to apply some mutations to DOM, use requestMutation from event callbacks.
When a requested callback will be really called?
If you do requestMutation from the measurement phase, it will be called in the earliest mutation phase (the end of the current animation frame). In all other cases (i.e., requestMutation from the mutation phase or requestMeasure from the measurement phase), it will be called in the corresponding phase of the next animation frame).
Replacing requestAnimationFrame/fastRaf
Sometimes you just know that you need to schedule an execution to the next frame (e.g., for animation purposes). Earlier, you would use requestAnimationFrame or fastRaf, but now you need to choose between requestMeasure or requestMutation, depending on what you want to do.
Also, according to the previous paragraph, if you call requestMutation from the measurement phase, it will be called in the same animation frame, so if you really need to run mutations in the next frame, there is requestNextMutation.
Forced reflows
Sometimes you need to apply additional mutations based on the measurement of the current mutations.
Imagine you prepended a new slice to the message list, and you need to update the scroll position (synchronously so the user does not see a blink). You will need to read your anchor element's updated offsetTop to see how much the entire list is shifted, and then update the scroll position accordingly.
Fasterdom provides a way to do so with requestForcedReflow method. It allows running additional measurements in the current animation frame right after the mutation phase. Moreover, it allows you to return a callback containing the mutation calls, which will execute after those forced measurements.
Another reason for using requestForcedReflow is forcing CSS transitions with forceReflow (see examples in the code).
Try to avoid forced reflows. Although Fasterdom supports it, it still can be a performance bottleneck.