diff --git a/resources/js/carousel.js b/resources/js/carousel.js index f02e0fb2..734fdf37 100644 --- a/resources/js/carousel.js +++ b/resources/js/carousel.js @@ -23,9 +23,10 @@ * records. A ResizeObserver does the same for a new width. RTL is read when measuring: * scroll offsets are negative there and shifts mirror, as Compose's `translationX` does. * - * Under reduced motion the buttons and keys scroll instantly, and the content stays pinned to - * the mask's leading edge instead of sliding inside it: the frame still opens and closes with - * the scroll the user makes, but nothing moves on its own. + * Under reduced motion the buttons and keys scroll instantly and nothing is masked at all: M3 + * says the parallax goes and items "should no longer expand as they come into view — all items + * are the same size", so every item stays at the strategy's large size and the keylines only + * decide where the row snaps. * * --------------------------------------------------------------------------------------- * Keyline maths ported from androidx (https://github.com/androidx/androidx), commit @@ -872,7 +873,6 @@ document.addEventListener('alpine:init', () => { .map((element) => ({ element, surface: element.querySelector('[data-material-carousel-surface]'), - content: element.querySelector('[data-material-carousel-content]'), label: element.querySelector('[data-material-carousel-label]'), labelWidth: 0, written: '', @@ -963,7 +963,13 @@ document.addEventListener('alpine:init', () => { const scroll = this.scrollOffset() const keylines = keylinesForScrollOffset(strategy, scroll, state.maxScroll) const size = strategy.itemSize - const pinned = state.reducedMotion.matches + + // M3: "When reduced motion settings are turned on, the parallax effect should be + // removed and carousel items should no longer expand as they come into view. All + // items are the same size" (docs/reference/m3/styles.md § Motion → Accessibility + // requirements). The keylines still decide where the row snaps; nothing is masked, + // moved or faded, so every item stays at strategy.itemSize. + const still = state.reducedMotion.matches state.items.forEach((item, index) => { const center = index * (size + strategy.itemSpacing) + size / 2 - scroll @@ -978,11 +984,10 @@ document.addEventListener('alpine:init', () => { translation += (center - keyline.unadjustedOffset) / keyline.size } - const inset = clamp((size - keyline.size) / 2, 0, size / 2) - const shift = state.rtl ? -translation : translation - const pin = pinned ? (state.rtl ? -inset : inset) : 0 - const opacity = item.labelWidth > 0 ? clamp((item.labelWidth - size + keyline.size) / item.labelWidth, 0, 1) : 1 - const written = `${inset.toFixed(2)}|${shift.toFixed(2)}|${pin.toFixed(2)}|${opacity.toFixed(3)}` + const inset = still ? 0 : clamp((size - keyline.size) / 2, 0, size / 2) + const shift = still ? 0 : state.rtl ? -translation : translation + const opacity = still || item.labelWidth === 0 ? 1 : clamp((item.labelWidth - size + keyline.size) / item.labelWidth, 0, 1) + const written = `${inset.toFixed(2)}|${shift.toFixed(2)}|${opacity.toFixed(3)}` if (written === item.written) { return @@ -993,7 +998,6 @@ document.addEventListener('alpine:init', () => { item.written = written item.surface.style.setProperty('--material-carousel-inset', `${inset.toFixed(2)}px`) item.surface.style.setProperty('--material-carousel-shift', `${shift.toFixed(2)}px`) - item.surface.style.setProperty('--material-carousel-pin', `${pin.toFixed(2)}px`) item.surface.style.setProperty('--material-carousel-label-shift', `${(state.rtl ? -inset : inset).toFixed(2)}px`) item.surface.style.setProperty('--material-carousel-label', opacity.toFixed(3)) }) diff --git a/resources/views/components/carousel-item.blade.php b/resources/views/components/carousel-item.blade.php index 81b2f05d..3d5eb0b8 100644 --- a/resources/views/components/carousel-item.blade.php +++ b/resources/views/components/carousel-item.blade.php @@ -34,7 +34,7 @@ data-material-carousel-surface class="relative size-full overflow-hidden rounded-corner-xl bg-surface-container-highest text-on-surface translate-x-(--material-carousel-shift) [clip-path:inset(0_var(--material-carousel-inset,0px)_round_var(--md-sys-shape-corner-xl))]" > -