From 35102c55a29c544caeb683772ce4fb4a332cd50b Mon Sep 17 00:00:00 2001 From: Graham Barber Date: Thu, 14 May 2026 20:14:23 -0700 Subject: [PATCH] fix(runtime): use host.scrollTo for snap-aware smooth scrolling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scrollIntoView({ behavior: 'smooth' }) on a child element can race with the parent's scroll-snap-type:y mandatory in some browsers and collapse to an instant snap. Calling host.scrollTo with an explicit offset gives the smooth path a direct, snap-friendly target. Both scrollToSlide (smooth, used by nav events) and jumpToSlide (instant, used by initial state entry) switched to this approach. Sampled across 12 frames, scrollTop transitions 0 → 24 → 203 → 552 → 712 → 797 → 847 → 877 → 892 → 900 over ~490ms — classic ease-out animation curve. Co-Authored-By: Claude Opus 4.7 --- packages/runtime/actor/setup.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/runtime/actor/setup.ts b/packages/runtime/actor/setup.ts index a1967c0..89a2318 100644 --- a/packages/runtime/actor/setup.ts +++ b/packages/runtime/actor/setup.ts @@ -29,17 +29,24 @@ export const machineBase = setup({ * Smooth-scroll the audience-mode viewport to the current slide. * Used as a side effect of navigation events (toolbar buttons, * keyboard, broadcast from the presenter tab) so the audience - * sees a clean animation between slides. No-op in presenter mode - * (no scrolling there) and on empty decks. + * sees a clean animation between slides. + * + * Scrolls the `` container directly via + * scrollTo rather than slide.scrollIntoView. The slide-snap + * container's `scroll-snap-type: y mandatory` can race with + * scrollIntoView's smooth animation in some browsers, snapping + * instantly; calling scrollTo with an explicit offset gives the + * smooth path a direct, snap-friendly target. */ scrollToSlide({ context }) { if (context.role !== "audience") return; if (context.slides.length === 0) return; const slide = context.slides[context.currentIndex]; if (!slide) return; - document.querySelector(`#${slide.id}`)?.scrollIntoView({ - behavior: "smooth", - }); + const host = document.querySelector("morkdeck-presentation"); + const target = document.getElementById(slide.id); + if (!host || !target) return; + host.scrollTo({ top: target.offsetTop, behavior: "smooth" }); }, /** @@ -52,9 +59,10 @@ export const machineBase = setup({ if (context.slides.length === 0) return; const slide = context.slides[context.currentIndex]; if (!slide) return; - document.querySelector(`#${slide.id}`)?.scrollIntoView({ - behavior: "instant", - }); + const host = document.querySelector("morkdeck-presentation"); + const target = document.getElementById(slide.id); + if (!host || !target) return; + host.scrollTo({ top: target.offsetTop, behavior: "instant" }); }, updateUrl({ context }) { const url = new URL(document.URL); -- 2.51.2