How Browser Rendering and React Fiber Work Together to Prevent Frame Drops

This article explains the fundamentals of browser rendering, frame lifecycle, and dropped‑frame issues, then demonstrates how React Fiber and the requestIdleCallback API can be used to split heavy tasks, improve concurrency, and keep UI interactions smooth.

ELab Team
ELab Team
ELab Team
How Browser Rendering and React Fiber Work Together to Prevent Frame Drops

Browser Rendering

To better understand React Fiber, we first review how the browser renderer works.

1. Rendering Frames

A frame is a single static image in an animation. Frame rate (frames per second) is the number of frames displayed each second. Frame duration is the time a frame stays on screen. A dropped frame occurs when a frame takes longer than the average duration.

Typical browser refresh rate is 60 Hz, so a frame must be rendered within 16.67 ms (1 s / 60).

If rendering exceeds this time, the user perceives stutter, i.e., a dropped frame.

1.2 Frame Lifecycle

Frame lifecycle diagram
Frame lifecycle diagram

Simple description of a frame's lifecycle:

1. Frame starts.
2. Main thread:
   - Event Handlers: UI events such as input, click, wheel.
   - RAF: Execute requestAnimationFrame callbacks.
   - DOM Tree: Parse HTML and build the DOM tree; changes trigger this flow again.
   - CSS Tree: Build CSS tree, producing the Render Tree.
   - Layout: Compute position and size for all elements.
   - Paint: Fill pixels (color, text, borders, etc.).
   - Composite: Send drawing commands to the compositor thread.
   - RequestIdleCallback: If the frame still has idle time, execute its callback.
3. Compositor thread:
   - Raster: Split work into chunks, send to raster thread, which creates bitmaps and notifies the GPU to refresh the frame.
4. Frame ends.

1.3 Dropped‑Frame Experiment

When a frame takes longer than 16 ms, the animation feels janky.

Demo: https://linjiayu6.github.io/FE-RequestIdleCallback-demo/

Example of a synchronous task that blocks the main thread:

const bindClick = id => {
  element(id).addEventListener('click', Work.onSyncUnit);
};

bindClick('btnA');
bindClick('btnB');
bindClick('btnC');

var Work = {
  unit: 10000,
  onOneUnit: function() { for (var i = 0; i <= 500000; i++) {} },
  onSyncUnit: function() {
    let _u = 0;
    while (_u < Work.unit) {
      Work.onOneUnit();
      _u++;
    }
  }
};

1.4 Solving Dropped Frames

JavaScript execution consumes rendering time. To keep animations smooth, we can:

Process low‑priority work during idle time using requestIdleCallback .

Break heavy tasks into smaller steps.

(Optionally) Use Web Workers.

window.requestIdleCallback() queues a function to run during the browser's idle periods, allowing background work without delaying high‑priority events like animation and input.

Idle‑time experiment:

const bindClick = id => {
  element(id).addEventListener('click', Work.onAsyncUnit);
};

bindClick('btnA');
bindClick('btnB');
bindClick('btnC');

var Work = {
  unit: 10000,
  onOneUnit: function() { for (var i = 0; i <= 500000; i++) {} },
  onAsyncUnit: function() {
    const FREE_TIME = 1;
    let _u = 0;
    function cb(deadline) {
      while (_u < Work.unit && deadline.timeRemaining() > FREE_TIME) {
        Work.onOneUnit();
        _u++;
      }
      if (_u >= Work.unit) return;
      window.requestIdleCallback(cb);
    }
    window.requestIdleCallback(cb);
  }
};

2. Limitations of React 15 Architecture

React 15 uses a recursive depth‑first traversal of the component tree, which blocks the main thread until the entire tree is processed.

class A extends React.Component {
  render() {
    return (
      <div id="app">
        <h1></h1>
        <p></p>
        <h2></h2>
        <h3></h3>
      </div>
    );
  }
}

This approach leads to dropped frames when the tree is large or when heavy synchronous work is performed.

2.1 Why It Happens

The problem is analogous to the frame‑dropping experiment: a long‑running task occupies the call stack, preventing higher‑priority work.

2.2 Process and Code Analysis

Key functions in the old reconciler:

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

function performUnitOfWork(unitOfWork) {
  let next = beginWork();
  if (next === null) {
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
}

3. Summary of Findings

React 15 processes updates via recursion, which monopolizes the main thread, causing visual stutter when the work exceeds the 16 ms frame budget.

4. Abstract Problem

When a low‑priority task runs, a higher‑priority task may be delayed, leading to inefficient scheduling.

5. Core Concerns

5.1 Concurrency & Scheduler

Concurrency means the ability to handle higher‑priority work while pausing lower‑priority tasks. The scheduler decides when to resume paused work.

5.2 Call Stack vs. Virtual Stack Frame

The traditional call stack cannot be paused; React implements a virtual stack frame (Fiber) to allow interruption and later resumption.

5.3 React 16+ Architecture

React 16 introduces Fiber, a data structure with three layers: instance properties, build properties (return, child, sibling), and work properties (effects, lanes).

5.4 Fiber Data Structure

Example of a simple Fiber node:

<div id="linjiayu">123</div>
<script type="text/babel">
  const App = () => {
    const [sum, setSum] = React.useState(0);
    return (
      <div id="app 1">
        <h1 id="2-1 h1">标题 h1</h1>
        <ul id="2-2 ul">
          <li id="3-1 li" onClick={() => setSum(d => d + 1)}>点击 h2</li>
          <li id="3-2 li">{sum}</li>
        </ul>
        <h3 id="2-3 h3">标题 h3</h3>
      </div>
    );
  };
  ReactDOM.render(<App />, document.getElementById('linjiayu'));
</script>

5.5 Work Loop

Two variants exist:

// Synchronous work loop
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

// Concurrent (interruptible) work loop
function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

The scheduler maintains a task queue, executes tasks until time runs out or a higher‑priority task arrives, then yields back to the browser.

6. Small Summary

To achieve concurrency, React rewrites the core using Fiber nodes.

The traditional call stack cannot be paused; Fiber provides virtual stack frames.

Fiber enables scheduling, allowing tasks to be split, paused, and resumed based on priority.

The scheduler orchestrates asynchronous, interruptible work.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performanceReactconcurrencyBrowser RenderingFiber
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.