# WebKit nested composite-transform frame pacing reproduction This is a dependency-free reproduction for an iOS WebKit frame-pacing issue involving two nested animation clocks: - A route-sized parent surface whose `translate3d()` progress is updated by `requestAnimationFrame`. - A child with an infinite CSS `rotate()` animation, drawn as a rounded border spinner. On the originating device, spinner durations within part of the `0.5s`–`1.1s` range produced regular 33–34 ms rAF gaps, while durations outside that band were smooth. The workload per frame is effectively unchanged, making the duration-dependent behavior unexpected. ## Run Open `index.html` directly. The reproduction is a single, self-contained HTML file and works from a `file://` URL without a server. To test it through a local HTTP server instead, run this from the repository root: ```sh npx vite external/bugs/composite-transform ``` This directory is independent of the Vue demo and has no build step or runtime dependencies. The CSS, test scene, controls, and measurement code are all in `index.html`, so that file can be attached directly to a browser bug report. ## Protocol 1. Do not scroll or touch the page while a sample is running. 2. Run **Sweep 0.5–1.1 s** with **rAF-driven** selected and **Spinner enabled** checked. 3. Repeat with **Static translate3d parent**. 4. Repeat with **No parent transform**. 5. Clear **Spinner enabled** and run the sweep as a control. Each duration runs for 1.8 seconds. In the rAF-parent case, the incoming route moves for 700 ms and then remains stationary for the rest of the measurement. ## Expected Changing only `animation-duration` should alter angular velocity, not cause a stable 60 Hz page to begin missing display deadlines. The rAF stream should remain close to 16.7 ms throughout the sweep on a 60 Hz device. ## Actual observation in the source application The problematic sample showed: - A 16 ms baseline. - Repeated 33–34 ms gaps during the route transition. - The same gaps continuing while the parent was stationary and the spinner remained visible. - Normal pacing after the spinner was removed from the DOM. - No route-loading delay, JavaScript long task, or layout shift correlated with the gaps. The reproduction intentionally contains no Vue, Vue Router, Suspense, application store, network request, or service-worker caching logic in its measured path.