← Projects

Astro View Transitions

Active

Fixing WebGL context loss in Astro core

Safari destroys WebGL2 rendering context when canvas elements are briefly detached from the DOM during Astro View Transitions page swaps. This patch lifts transition:persist elements to <html> before the body swap so they're never disconnected. Also integrated the moveBefore() DOM API (Chrome 133+) for zero-detachment atomic node repositioning — the first use of this API in Astro's View Transitions system.

scope
Astro core
downloads
5M+/week
type
Bug fix + API integration
TypeScript Astro WebGL2 View Transitions API moveBefore()

// THE PROBLEM

This site runs a GPU-accelerated physarum simulation as a persistent background canvas across all pages. Astro's View Transitions swap the <body> during navigation, which means any element in the body — including a persist canvas — gets briefly detached and reattached. In Chrome this is invisible. In Safari, detaching a canvas from the DOM destroys its WebGL2 rendering context permanently. The simulation dies on first navigation.

// BEFORE: THE DETACHMENT PROBLEM

During body swap, persist elements are disconnected from the DOM for a few milliseconds. Chrome tolerates this. Safari does not — it garbage-collects the WebGL context on detach.

The detachment window is milliseconds, but Safari's WebGL garbage collection is aggressive.

// AFTER: LIFT BEFORE SWAP

The fix lifts persist elements to <html> (the document element) before the body swap. Since <html> is never replaced, the canvas stays connected throughout. After the swap, elements are moved back into their new positions.

moveBefore() is the ideal path — atomic repositioning with no detach event. The appendChild fallback covers Safari and older browsers.

// WHAT CHANGED IN ASTRO CORE

  • swapBodyElement() in swap-functions.ts — the function that handles body replacement during View Transitions
  • Before replaceWith(): all [data-astro-transition-persist] elements are lifted to document.documentElement
  • moveBefore() feature detection — uses the new DOM API when available (Chrome 133+) for zero-detachment moves
  • Fallback to appendChild/replaceWith for browsers without moveBefore() (Safari, Firefox)
  • Orphan handling — persist elements without a matching target in the new page are left in the old body to be discarded naturally
  • Two new e2e tests: canvas pixel data survives navigation, orphaned persist elements are cleaned up

// MOVEBEFORE(): THE NEW DOM PRIMITIVE

moveBefore() is a new DOM API (Chrome 133+, March 2025) that moves a node to a new position in the tree without triggering disconnection callbacks. Unlike appendChild — which fires disconnectedCallback, pauses animations, and resets form state — moveBefore() is an atomic operation. The node never enters a 'detached' intermediate state. This is exactly what View Transitions need: reposition persist elements without any side effects. This PR is the first integration of moveBefore() into Astro's View Transitions system.

// IMPACT

  • Merged into Astro core — withastro/astro#15728
  • Affects all Astro sites using transition:persist with canvas, WebGL, or stateful elements
  • Astro has 5M+ weekly npm downloads — this fix ships to every new install
  • First use of moveBefore() in Astro's View Transitions, establishing the pattern for future persist improvements
  • Documentation discussion opened: withastro/docs#13346