Summary
When a child block with its own animation is placed inside a parent block that uses a load-time transform animation such as slideInDown, the child can remain invisible after the page loads until the visitor scrolls.
Expected behavior: the parent animation runs on load, then the child animation runs according to its configured delay.
Actual behavior: the child remains hidden after hard-refreshing the frontend and only appears after a scroll event.
Impact: nested animated block content can be missing from the initial page view for visitors using this animation combination.
Customer context
- Product / area: Blocks Animation: CSS Animations for Gutenberg Blocks, frontend nested block animations
- Version: 3.1.11 reported; repository plugin header also shows 3.1.11
- Environment: WordPress 7.0 reported
- Integration / third party: Gutenberg core blocks, specifically Cover and Heading in the reported steps
- Reported error / symptom: Child Heading with delayed animation is invisible on load inside a Cover using
slideInDown; appears after first scroll
- Impact: Initially hidden content on the published frontend for the reported nested animation setup
Reproduction notes
Reported steps:
- Add a Cover block and set its animation to
slideInDown.
- Add a Heading inside the Cover and set its animation to
fadeInLeft with delay-1s.
- Publish the page.
- Hard-refresh the frontend and do not scroll.
- Observe that the Cover animates, but the Heading remains invisible until the first scroll.
Reproduction status: not runtime-reproduced in this session. The repository inspection supports the reported flow and failure mode.
Missing details: browser, theme, viewport size, and whether the same behavior occurs with other transform-based parent animations are unknown from available evidence.
Diagnosis
Conclusion
The report is confirmed by repository inspection. On window.load, the frontend script processes every .animated element and classifies elements as in-viewport or scroll-triggered. The viewport helper reads getBoundingClientRect().top, which reflects current visual transforms in browsers; with a parent transform animation active at load, a nested child can be measured at the transformed position. For the inspected path, a child classified as outside the viewport has its animation/delay classes removed, gets hidden-animated, and only replays from the scroll listener.
Where this likely occurs
- User-visible surface: Blocks Animation frontend output for nested Gutenberg blocks where both parent and child are animated and the parent uses a load-time transform animation such as
slideInDown.
plugins/blocks-animation/blocks-animation.php — plugin header approx. lines 10–13 confirms the standalone product and version 3.1.11 for the reported plugin.
inc/class-blocks-animation.php — Blocks_Animation::frontend_load() lines 151–180 registers/enqueues build/animation/frontend.js and injects the frontend hiding style when block content contains animated.
inc/class-blocks-animation.php — Blocks_Animation::add_frontend_anim_inline_style() lines 222–228 outputs .animated:not(.o-anim-ready) with visibility: hidden, paused animation state, and animation: none !important until frontend processing marks elements ready.
src/animation/frontend.js — animateElements() lines 281–290 queries all .animated elements on load and sends each through processElement().
src/animation/frontend.js — processElement() lines 163–203 calls isElementInViewport(), removes animation/delay/speed classes from elements deemed outside the viewport, adds hidden-animated, then adds o-anim-ready.
src/animation/frontend.js — processElement() lines 232–238 stores deferred animation state in elementsScroll for elements that had animation classes removed.
src/animation/frontend.js — attachScrollListener() lines 246–267 restores stored animation classes and removes hidden-animated only after a scroll-triggered visibility check passes.
src/animation/frontend.js — isElementInViewport() lines 308–328 bases visibility on el.getBoundingClientRect().top + scroll + offset and el.clientHeight; this is the measurement point implicated by the reported parent transform scenario.
src/animation/frontend.js — load registration line 448 runs animateElements on window.load.
- Git history:
git blame shows the current processElement() scroll-deferral flow came from f0e644ec9 (refactor: enhance animation handling for scroll and lightbox elements), released after tag v3.1.4 and before tag v3.1.5; the getBoundingClientRect() viewport helper dates back to earlier animation handling, with line 312 introduced by 168b69433.
Engineering notes
The code path is shared by Otter Blocks and the standalone Blocks Animation sister plugin. The standalone plugin source is under plugins/blocks-animation/, while the frontend source is shared from src/animation/frontend.js and mapped to build/animation/frontend.js by webpack.config.js and webpack.config.plugins.js.
The failure depends on timing and visual geometry during page load. The reported slideInDown parent starts with a vertical transform, while the child has its own animation and delay. If window.load fires while the parent transform affects the child’s visual bounding box, the child enters scroll-trigger mode instead of load-trigger mode. After that point, the scroll listener is the observed route that removes hidden-animated.
The workspace does not include WordPress core source, so the exact browser/core block rendering behavior was not runtime-verified here. The browser behavior of getBoundingClientRect() including transforms is treated as an external platform assumption consistent with the report.
The shipped plugins/blocks-animation/build/animation/frontend.js artifact was not present in this checkout, but the PHP enqueue points to that build output and both webpack configs list src/animation/frontend.js as its source entry.
Test coverage status
Relevant frontend coverage appears incomplete for this path. src/blocks/test/e2e/blocks/animations.spec.js lines 35–64 checks editor-side class assignment for a simple animation, and lines 11–33 checks typing animation UI behavior. No relevant coverage was found during inspection for published frontend load behavior with nested animated blocks, parent transform animations, delayed child animations, or scroll-trigger deferral.
What to verify or explore next
- Reproduce on a local published page with a Cover using
slideInDown and a nested Heading using fadeInLeft plus delay-1s, then hard-refresh without scrolling.
- During reproduction, inspect whether the child receives
hidden-animated, loses fadeInLeft/delay classes on load, and remains in that state until scroll.
- May be worth verifying the same nested pattern with other parent transform animations and with a non-transform parent animation for comparison.
- May be worth checking whether the behavior differs between standalone Blocks Animation and the Otter Blocks bundled module using the same source.
- If treated as a regression, compare
v3.1.4 against v3.1.5 and current v3.1.11, focusing on commit f0e644ec9 and frontend scroll-deferral behavior.
Unknowns / follow-up
- Runtime reproduction was not executed in this session.
- The exact viewport dimensions, theme layout, and browser used by the reporter are not provided.
- The exact built artifact was not available in the workspace; the analysis uses the shared source entry and PHP enqueue mapping.
Confidence
Confidence: 88/100
The ticket's reported symptom matches the inspected frontend flow: load-time processing uses transformed viewport geometry, then hides and defers elements classified as off-screen until scroll.
Source: HelpScout #3368238906
Generated by bug-report-triage (ID: bug-report-triage_6a3e85f74a5c83.15592705)
Summary
When a child block with its own animation is placed inside a parent block that uses a load-time transform animation such as
slideInDown, the child can remain invisible after the page loads until the visitor scrolls.Expected behavior: the parent animation runs on load, then the child animation runs according to its configured delay.
Actual behavior: the child remains hidden after hard-refreshing the frontend and only appears after a scroll event.
Impact: nested animated block content can be missing from the initial page view for visitors using this animation combination.
Customer context
slideInDown; appears after first scrollReproduction notes
Reported steps:
slideInDown.fadeInLeftwithdelay-1s.Reproduction status: not runtime-reproduced in this session. The repository inspection supports the reported flow and failure mode.
Missing details: browser, theme, viewport size, and whether the same behavior occurs with other transform-based parent animations are unknown from available evidence.
Diagnosis
Conclusion
The report is confirmed by repository inspection. On
window.load, the frontend script processes every.animatedelement and classifies elements as in-viewport or scroll-triggered. The viewport helper readsgetBoundingClientRect().top, which reflects current visual transforms in browsers; with a parent transform animation active at load, a nested child can be measured at the transformed position. For the inspected path, a child classified as outside the viewport has its animation/delay classes removed, getshidden-animated, and only replays from the scroll listener.Where this likely occurs
slideInDown.plugins/blocks-animation/blocks-animation.php— plugin header approx. lines 10–13 confirms the standalone product and version3.1.11for the reported plugin.inc/class-blocks-animation.php—Blocks_Animation::frontend_load()lines 151–180 registers/enqueuesbuild/animation/frontend.jsand injects the frontend hiding style when block content containsanimated.inc/class-blocks-animation.php—Blocks_Animation::add_frontend_anim_inline_style()lines 222–228 outputs.animated:not(.o-anim-ready)withvisibility: hidden, paused animation state, andanimation: none !importantuntil frontend processing marks elements ready.src/animation/frontend.js—animateElements()lines 281–290 queries all.animatedelements on load and sends each throughprocessElement().src/animation/frontend.js—processElement()lines 163–203 callsisElementInViewport(), removes animation/delay/speed classes from elements deemed outside the viewport, addshidden-animated, then addso-anim-ready.src/animation/frontend.js—processElement()lines 232–238 stores deferred animation state inelementsScrollfor elements that had animation classes removed.src/animation/frontend.js—attachScrollListener()lines 246–267 restores stored animation classes and removeshidden-animatedonly after a scroll-triggered visibility check passes.src/animation/frontend.js—isElementInViewport()lines 308–328 bases visibility onel.getBoundingClientRect().top + scroll + offsetandel.clientHeight; this is the measurement point implicated by the reported parent transform scenario.src/animation/frontend.js— load registration line 448 runsanimateElementsonwindow.load.git blameshows the currentprocessElement()scroll-deferral flow came fromf0e644ec9(refactor: enhance animation handling for scroll and lightbox elements), released after tagv3.1.4and before tagv3.1.5; thegetBoundingClientRect()viewport helper dates back to earlier animation handling, with line 312 introduced by168b69433.Engineering notes
The code path is shared by Otter Blocks and the standalone Blocks Animation sister plugin. The standalone plugin source is under
plugins/blocks-animation/, while the frontend source is shared fromsrc/animation/frontend.jsand mapped tobuild/animation/frontend.jsbywebpack.config.jsandwebpack.config.plugins.js.The failure depends on timing and visual geometry during page load. The reported
slideInDownparent starts with a vertical transform, while the child has its own animation and delay. Ifwindow.loadfires while the parent transform affects the child’s visual bounding box, the child enters scroll-trigger mode instead of load-trigger mode. After that point, the scroll listener is the observed route that removeshidden-animated.The workspace does not include WordPress core source, so the exact browser/core block rendering behavior was not runtime-verified here. The browser behavior of
getBoundingClientRect()including transforms is treated as an external platform assumption consistent with the report.The shipped
plugins/blocks-animation/build/animation/frontend.jsartifact was not present in this checkout, but the PHP enqueue points to that build output and both webpack configs listsrc/animation/frontend.jsas its source entry.Test coverage status
Relevant frontend coverage appears incomplete for this path.
src/blocks/test/e2e/blocks/animations.spec.jslines 35–64 checks editor-side class assignment for a simple animation, and lines 11–33 checks typing animation UI behavior. No relevant coverage was found during inspection for published frontend load behavior with nested animated blocks, parent transform animations, delayed child animations, or scroll-trigger deferral.What to verify or explore next
slideInDownand a nested Heading usingfadeInLeftplusdelay-1s, then hard-refresh without scrolling.hidden-animated, losesfadeInLeft/delay classes on load, and remains in that state until scroll.v3.1.4againstv3.1.5and currentv3.1.11, focusing on commitf0e644ec9and frontend scroll-deferral behavior.Unknowns / follow-up
Confidence
Confidence: 88/100
The ticket's reported symptom matches the inspected frontend flow: load-time processing uses transformed viewport geometry, then hides and defers elements classified as off-screen until scroll.
Source: HelpScout #3368238906
Generated by bug-report-triage (ID: bug-report-triage_6a3e85f74a5c83.15592705)