You have probably used a website that felt sluggish. You click a button and the page freezes for a moment. You scroll and everything jerks. The animations look choppy. Nothing is broken exactly — it just feels bad.
In most cases the cause is the same: the JavaScript is making the browser do too much layout work at the wrong time. The code is correct but unoptimised, and the browser is constantly being asked to recalculate the position and size of elements on screen.
This guide explains what is actually happening inside the browser when this occurs, why some DOM operations are much more expensive than others, and the practical techniques you can use to fix it — all in plain, beginner-friendly English.
How the Browser Renders a Page
Before we talk about problems, you need a rough picture of how the browser turns your HTML and CSS into pixels on screen. It goes through these steps every time something changes:
- Parse — the browser reads your HTML and builds the DOM tree (a list of all your elements and how they nest)
- Style — it figures out which CSS rules apply to each element
- Layout — it calculates the exact size and position of every element on the page
- Paint — it fills in the pixels, drawing backgrounds, text, borders and images
- Composite — it combines all the layers and sends the final image to the screen
The important thing to understand is this: Layout and Paint are expensive steps. They take real time. When your JavaScript forces the browser to repeat these steps over and over, the page feels slow and janky.
What Is a Reflow
A reflow (also called layout) happens when the browser has to recalculate the size and position of elements on the page. It is the expensive step.
Think of it like rearranging furniture in a room. If you move the sofa, you might also have to move the coffee table to make room, and then shift the rug, and then the lamp no longer fits where it was. One change causes a chain of recalculations. That is what a reflow is.
The worst part about reflows is that they are synchronous by default. When you trigger a reflow, the browser stops everything it was doing, recalculates the whole layout, then continues. Your JavaScript waits. Your animation freezes. Your user notices.
What Is a Repaint
A repaint happens when something visual changes but the layout does not. For example: changing a colour, changing a background image, or changing the visibility of an element. The browser does not need to recalculate positions — it just needs to redraw the pixels.
Repaints are cheaper than reflows but they are still not free. Every repaint takes time, and many repaints per second add up quickly.
What Triggers Reflows and Repaints
Here are the most common things that trigger a reflow. Knowing this list is the first step to avoiding them:
Read Then Write — Never Mix Them
The single most important rule in DOM optimisation is this: do all your reads first, then do all your writes. Never mix reading and writing layout properties in the same loop.
Here is why this matters so much. When you write a layout property, the browser marks the layout as "dirty" — meaning it needs to be recalculated. It does not recalculate immediately though. It waits until it needs to. But the moment you read a layout property, the browser is forced to recalculate right now so it can give you the correct current value. If you keep alternating between reads and writes, you are triggering a full layout recalculation on every single line.
Batching DOM Updates
When you need to add many elements to the page at once — like rendering a list of 100 items — doing it one element at a time is extremely slow. Each appendChild triggers a reflow and a repaint. That is 100 reflows for 100 items.
The solution is to build all your elements in memory first, then add them to the page in one single operation. The browser only reflows once.
DocumentFragment
A DocumentFragment is like a temporary invisible container that lives in memory, not in the actual page. You can add as many elements to it as you like without triggering any reflows. Then when you are done, you attach the whole fragment to the page in one go.
Building with innerHTML in One Shot
Another very effective pattern is building your entire HTML as a string and setting innerHTML once. This causes a single parse and a single reflow regardless of how many elements are in the string.
Change CSS Classes, Not Individual Styles
If you need to apply several style changes to an element at once, do not set each one separately. Every el.style.something = value is another potential reflow trigger. Instead, define a CSS class with all those styles, then just toggle the class name.
Layout Thrashing — The Worst Offender
Layout thrashing is when you mix reads and writes in a loop. It forces the browser to do a full layout recalculation on every single iteration. This is the most common performance mistake beginners make.
requestAnimationFrame — Do Visual Work at the Right Time
requestAnimationFrame (usually shortened to rAF) tells the browser: "run this code right before you paint the next frame." This is the right place to do any DOM changes that need to animate smoothly, because the browser can optimise and batch them properly.
Think of it like a chef at a restaurant. Instead of running to the customer with each ingredient as you prepare it, you wait until the whole dish is ready and bring everything out at once. rAF is the "one trip" approach for DOM updates.
Use transform and opacity for All Animations
This is the most impactful rule for smooth animations. When you animate transform or opacity, the browser can handle it entirely on the GPU without touching the Layout or Paint steps at all. Everything else — top, left, margin, width, height — causes a reflow or repaint on every frame.
The will-change CSS Hint
You can give the browser a heads-up about which elements are going to animate. When the browser knows an element will be transformed, it can promote it to its own GPU layer in advance so the animation is completely isolated from the rest of the page.
Quick Reference Table
| Operation | Cost | Why |
|---|---|---|
| Animate with transform | None | Handled by GPU, no layout step |
| Animate opacity | None | Handled by GPU, no layout step |
| Change color or background | Repaint | Pixels redraw but layout stays same |
| Change width, height or padding | Reflow | Browser recalculates all positions |
| Animate left or top | Reflow | Triggers full layout on every frame |
| Read offsetWidth in a loop | Reflow | Forces layout sync before each read |
| classList.add one class | One reflow | Browser batches all rule changes |
| appendChild in a loop | Many reflows | Each insert triggers layout |
| DocumentFragment then append | One reflow | All changes land at once |
- The browser renders pages in steps: Style, Layout, Paint, Composite. Layout and Paint are expensive. Anything that triggers them costs time.
- A reflow happens when the browser must recalculate element sizes and positions. It is the most expensive operation and always followed by a repaint.
- A repaint happens when visuals change but layout stays the same (like a colour change). It is cheaper than a reflow but still has a cost.
- Read all layout properties first, then write. Never mix reads and writes in a loop. This single rule eliminates most layout thrashing.
- When adding many elements, use DocumentFragment or build an HTML string and set innerHTML once. This reduces many reflows down to one.
- Toggle CSS class names instead of setting individual style properties one by one. The browser applies all changes at once.
- Use transform and opacity for all animations. They run entirely on the GPU and cause zero reflows or repaints.
- Use requestAnimationFrame for any visual updates. It syncs your code with the browser paint cycle and pauses automatically when the tab is not visible.
- Add will-change: transform on elements that are about to animate, but remove it straight after to free up GPU memory.
