Repository navigation
fix(core): mount parked scenes with layout so mount-time scripts don't reorder text - #7341
Open
miguel-heygen wants to merge 1 commit into
Open
miguel-heygen wants to merge 1 commit into
miguel-heygen wants to merge 1 commit into
Conversation
…t reorder text A scene parked at boot mounts into a host that takes its stand-in's style, including the display:none the visibility pass gave the empty stand-in. The scene's scripts then ran with the host undisplayed. GSAP's CSSPlugin reads a transform from an element without layout by moving it to <html> and putting it back before its nextElementSibling, so a tween on inline word spans moved each one past the whitespace text node after it, and the preview showed "in the" as "inthe" until a later remount. The mount now gives the host back its own display and hides it with visibility:hidden !important while its scripts run; the visibility pass decides how to hide or show it once the scene settles.
Contributor
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The preview mounts a parked scene with its host laid out (hidden by
visibility:hidden !important) while the scene's scripts run, not underdisplay:none. Mount-time tweens on inline word spans no longer move the spaces between words.Why
In a user project, a karaoke line written as
in the ocean of yourshowed in Studio as "inthe ocean ofyour" on first load, after a seek into the scene, and when playing into it. Seeking away and back fixed it. Renders were fine. The bug has been there since 0.8.200 and is still in 0.9.19.Root cause, from #6189 (70187a1):
display:none(andvisibility:hidden !important).mountSceneunparks the scene into a host that copies the stand-in's attributes, so the host gets thatdisplay:nonetoo.moveDisplayHidecarries the record over, but nothing restores the display until the scene is visible. The scene's scripts (runSceneScripts) then run on a host without layout.gsap.fromTo(span, { y, scale }, …)renders its start values right away. GSAP's CSSPlugin (_getMatrix, same in 3.14.2 and 3.15.0) handles an element with nooffsetParentand no width by appending it to<html>to measure it, then putting it back withparent.insertBefore(target, target.nextElementSibling). That anchor is the next element, not the next node, so the whitespace text node after the span ends up in front of it:[in " " the]becomes[" " in the]. The moved space collapses and the words run together.display:noneand GSAP never moves anything. That is why a seek "fixed" it.Any author script that measures on mount (
offsetWidth,getBoundingClientRect, text fitting) also read zeros there.Related work
Refs #6189, which added the scene window. I found no open issue or PR for this.
How
In
mountScene(packages/core/src/runtime/init.ts), right afterunparkandmoveDisplayHide, the host gets back the display it had before the visibility pass hid the stand-in (restoreDisplay). It is then hidden withvisibility:hidden !important, which is the state a remount already starts in. The scripts run with the host laid out but not visible. Once the scene settles (settleChangedScenes), the visibility pass hides or shows it as usual, and it may still hide an in-flow leaf host by display.mountScene: the scene a seek or scrub lands in, the next one mounted ahead while playing or paused, and scenes Studio or the inspector hold. All of them get the fix. Swapping an edited scene that is already mounted imports a fresh host that never had the stand-in's style, so that path didn't have the bug.display:noneincluded. A scene far from the playhead still getscontent-visibility: hiddenfrom the far-scene skip once it settles. I checked in Chrome thatcontent-visibility: hiddenstill givesoffsetParentand a width to a script that asks, so it doesn't hit GSAP's fallback.data-hf-scene-no-swapneeded.Test plan
init.swapScenes.test.tstest: "lays a parked scene out while its script runs, so a transform tween keeps the spaces between words". A parked scene with<div class="row"><span class="w"><span>a</span> <span>b</span></span> <span class="w">c</span></div>runsgsap.fromTo('.w', { y: 10 }, { y: 0 })with core's real gsap. jsdom gives nothing anoffsetParent, so the test defines one that, as in a browser, is null only underdisplay:none. The test asserts that the row's child nodes keep their order (w, " ", w), that its text staysa b c, and that the row had layout while the script ran.expected [ ' ', 'w', 'w' ] to deeply equal [ 'w', ' ', 'w' ]. This is GSAP's reorder, reproduced.display:nonereaching the mounted host. The fix stops that, so the scene now stays in flow through an!importantstylesheet rule, which lets the visibility pass hide the mounted host by display. The test still covers the retire-then-remount handoff: withmoveDisplayHide(host, placeholder)removed fromretireScene, it fails.bun run testinpackages/core: 277 files and 5874 tests pass, thepageBuildgoldens included (none changed).bun run typecheckandlint:runtime-preview-guardspass, and so do oxlint/oxfmt,check-comment-citationsandcomment-ratchet.hyperframes preview, headless Chrome). For each row I compared the live child-node order with the authored markup:#c1-r3[" " in the] " " [ocean] " " [" " of your][in " " the] " " [ocean] " " [of " " your](authored)#v2-l28t" " No " " mistakes, " " my [heart] " " " " is beatingNo " " mistakes, " " my " " [heart] " " is " " beating(authored)