webf-async-rendering

from openwebf/webf

Bring JavaScript and Web Dev to Flutter

2.3K stars155 forksUpdated Jan 26, 2026
npx skills add https://github.com/openwebf/webf --skill webf-async-rendering

SKILL.md

WebF Async Rendering

Note: WebF development is nearly identical to web development - you use the same tools (Vite, npm, Vitest), same frameworks (React, Vue, Svelte), and same deployment services (Vercel, Netlify). This skill covers one of the 3 key differences: WebF's async rendering model. The other two differences are API compatibility and routing.

This is the #1 most important concept to understand when moving from browser development to WebF.

The Fundamental Difference

In Browsers (Synchronous Layout)

When you modify the DOM, the browser immediately performs layout calculations:

// Browser behavior
const div = document.createElement('div');
document.body.appendChild(div);
console.log(div.getBoundingClientRect()); // ✅ Returns real dimensions

Layout happens synchronously - you get dimensions right away, but this can cause performance issues (layout thrashing).

In WebF (Asynchronous Layout)

When you modify the DOM, WebF batches the changes and processes them in the next rendering frame:

// WebF behavior
const div = document.createElement('div');
document.body.appendChild(div);
console.log(div.getBoundingClientRect()); // ❌ Returns zeros! Not laid out yet.

Layout happens asynchronously - elements exist in the DOM tree but haven't been measured/positioned yet.

Why Async Rendering?

Performance: WebF's async rendering is 20x cheaper than browser synchronous layout!

  • DOM updates are batched together
  • Multiple changes processed in one optimized pass
  • Eliminates layout thrashing
  • No need for DocumentFragment optimizations

Trade-off: You must explicitly wait for layout to complete before measuring elements.

The Solution: onscreen/offscreen Events

WebF provides two non-standard events to handle the async lifecycle:

EventWhen It FiresPurpose
onscreenElement has been laid out and renderedSafe to measure dimensions, get computed styles
offscreenElement removed from render treeCleanup and resource management

Think of these like IntersectionObserver but for layout lifecycle, not viewport visibility.

How to Measure Elements Correctly

❌ WRONG: Measuring Immediately

// DON'T DO THIS - Will return 0 or incorrect values
const div = document.createElement('div');
div.textContent = 'Hello WebF';
document.body.appendChild(div);

const rect = div.getBoundingClientRect();  // ❌ Returns zeros!
console.log(rect.width);  // 0
console.log(rect.height); // 0

✅ CORRECT: Wait for onscreen Event

// DO THIS - Wait for layout to complete
const div = document.createElement('div');
div.textContent = 'Hello WebF';

div.addEventListener('onscreen', () => {
  // Element is now laid out - safe to measure!
  const rect = div.getBoundingClientRect();  // ✅ Real dimensions
  console.log(`Width: ${rect.width}, Height: ${rect.height}`);
});

document.body.appendChild(div);

React: useFlutterAttached Hook

For React developers, WebF provides a convenient hook:

❌ WRONG: Using useEffect

import { useEffect, useRef } from 'react';

function MyComponent() {
  const ref = useRef(null);

  useEffect(() => {
    // ❌ Element not laid out yet!
    const rect = ref.current.getBoundingClientRect();
    console.log(rect); // Will be zeros
  }, []);

  return <div ref={ref}>Content</div>;
}

✅ CORRECT: Using useFlutterAttached

import { useFlutterAttached } from '@openwebf/react-core-ui';

function MyComponent() {
  const ref = useFlutterAttached(
    () => {
      // ✅ onAttached callback - element is laid out!
      const rect = ref.current.getBoundingClientRect();
      console.log(`Width: ${rect.width}, Height: ${rect.height}`);
    },
    () => {
      // onDetached callback (optional)
      console.log('Component removed from render tree');
    }
  );

  return <div ref={ref}>Content</div>;
}

Layout-Dependent APIs

Only call these inside onscreen callback or useFlutterAttached:

  • element.getBoundingClientRect()
  • window.getComputedStyle(element)
  • element.offsetWidth / element.offsetHeight
  • element.clientWidth / element.clientHeight
  • element.scrollWidth / element.scrollHeight
  • element.offsetTop / element.offsetLeft
  • Any logic that depends on element position or size

Common Scenarios

Scenario 1: Measuring After Style Changes

const div = document.getElementById('myDiv');

// ❌ WRONG
div.style.width = '500px';
const rect = div.getBoundingClientRect(); // Old dimensions!

// ✅ CORRECT
div.style.width = '500px';
div.addEventListener('onscreen', () => {
  const rect = div.getBoundingClientRect(); // New dimensions!
}, { once: true }); // Use 'once' to remove listener after first call

Scenario 2: Positioning Tooltips/Popovers

function showTooltip(targetElement) {
  const tooltip = document.createElement('d

...
Read full content

Repository Stats

Stars2.3K
Forks155
LicenseGNU General Public License v3.0