Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions components/Demo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useEffect, useRef, useState } from "react";

/**
* Embeds a self-contained interactive demo (a static HTML file under
* `public/demos/`) inside an iframe. The demo reports its own content height
* via `postMessage({ [heightKey]: number })`, so the iframe grows to fit
* without an inner scrollbar. Theme (light/dark) syncs automatically: the demo
* reads `localStorage.theme`, which Nextra also writes, and both share the same
* origin.
*/
export function Demo({
src,
title,
caption,
heightKey = "rafDemoHeight",
minHeight = 400,
}) {
const iframeRef = useRef(null);
const [height, setHeight] = useState(minHeight);

useEffect(() => {
function handleMessage(event) {
if (event.source !== iframeRef.current?.contentWindow) return;
const value = event.data?.[heightKey];
if (typeof value === "number") {
setHeight(value + 24);
}
}
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, [heightKey]);

return (
<figure className="nx-mt-6 first:nx-mt-0">
<iframe
ref={iframeRef}
src={src}
title={title}
loading="lazy"
style={{
width: "100%",
height: `${height}px`,
border: "none",
borderRadius: "0.75rem",
display: "block",
}}
/>
{caption ? (
<figcaption className="nx-mt-2 nx-text-sm nx-text-gray-500 dark:nx-text-gray-400">
{caption}
</figcaption>
) : null}
</figure>
);
}
9 changes: 9 additions & 0 deletions pages/Interaction/Forced-Synchronous-Layout.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import snippet from '../../snippets/Interaction/Forced-Synchronous-Layout.js?raw'
import { Snippet } from '../../components/Snippet'
import { Demo } from '../../components/Demo'

# Forced Synchronous Layout Detector

Expand Down Expand Up @@ -29,6 +30,14 @@ requestAnimationFrame(() => { // 1st rAF: browser processes styles
});
```

Step through the render pipeline to see _why_ the double `requestAnimationFrame` avoids the forced layout:

<Demo
src="/demos/raf-pipeline.html"
title="Interactive visualization of the render pipeline and why two requestAnimationFrame calls are needed to avoid a Forced Synchronous Layout"
caption="Use the buttons to walk through the three scenarios step by step and see what happens in each phase of the frame."
/>

**What this snippet intercepts:**

Mutation sources — detected synchronously:
Expand Down
27 changes: 12 additions & 15 deletions pages/Interaction/LongTask.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import snippet from '../../snippets/Interaction/LongTask.js?raw'
import { Snippet } from '../../components/Snippet'
import { Demo } from '../../components/Demo'

# Long Tasks

Expand Down Expand Up @@ -87,22 +88,18 @@ flowchart LR
style Block fill:#FFE5E5,stroke:#FF4444,stroke-width:2px
```

**Severity Levels:**
### Breaking Up Long Tasks

```mermaid
flowchart TB
Task[Long Task Duration] --> Check{How long?}

Check -->|50-100ms| Low["🟢 Low Severity<br/><small>Minor delay</small>"]
Check -->|100-150ms| Medium["🟡 Medium Severity<br/><small>Noticeable lag</small>"]
Check -->|150-250ms| High["🟠 High Severity<br/><small>Poor responsiveness</small>"]
Check -->|> 250ms| Critical["🔴 Critical Severity<br/><small>Severe blocking</small>"]

style Low fill:#E5FFE5,stroke:#44AA44,stroke-width:2px
style Medium fill:#FFFBE5,stroke:#FFBB00,stroke-width:2px
style High fill:#FFF3E5,stroke:#FF8800,stroke-width:2px
style Critical fill:#FFE5E5,stroke:#FF4444,stroke-width:2px
```
Once a long task is detected, the fix is to _yield to the main thread_ so the browser can handle pending input between chunks of work. A common misconception is that `queueMicrotask` yields, it does not: microtasks run within the same task, so the main thread stays blocked. Only scheduling a new task (for example with `setTimeout`, or the newer `scheduler.yield()`) actually gives the browser a chance to respond.

Step through the event loop to compare the three approaches:

<Demo
src="/demos/yield-pipeline.html"
title="Interactive visualization of the event loop comparing no yield, queueMicrotask, and setTimeout, and how each affects whether the main thread stays blocked"
caption="Switch between the three scenarios and step through each to see when the browser can process input."
heightKey="yieldDemoHeight"
/>

### Limitations

Expand Down
Loading
Loading