Motion
Scroll root
A page that scrolls its own element, and the hook that finds it.
stablev0.1.0 Chromium 103 ui-scale safe
Install
Copies the file into
components/ds/, with its registry dependencies and npm packages. See Installation for the one-time setup.bunx shadcn@3 add @fastrp/scroll-rootExamples
Default
Aşağı kaydır.
Şehirler
Meslekler
Ekonomi
Çeteler
Emlak
Telefon
Etkinlikler
API
<ScrollRoot />Takes the props of what it wrapsA page that scrolls its own element instead of the window — the hub keeps `body` at `overflow: hidden` for the panel shell, and the NUI has no window scroll at all. Children find the element with `useScrollRoot()`; the motion components here use it as their viewport. It publishes its own height as `--scroll-root-h`, which the scroll-driven patterns use where they would say `100vh`: a screen of scroll is a screen of this box, and they work in a box as small as a gallery preview. `relative` is load-bearing: motion's `useScroll({ container })` measures offsets against it.
Dependencies
npm
- None
Registry
- @fastrp/utils
Source
components/ds/scroll-root.tsxShowHide
'use client';
import type * as React from 'react';
import { createContext, type RefObject, useContext, useEffect, useRef } from 'react';
import { cn } from '@/lib/utils';
// @fast-ds [email protected]
const ScrollRootContext = createContext<RefObject<HTMLElement | null> | undefined>(undefined);
/**
* A page that scrolls its own element instead of the window — the hub keeps `body` at
* `overflow: hidden` for the panel shell, and the NUI has no window scroll at all. Children find
* the element with `useScrollRoot()`; the motion components here use it as their viewport.
*
* It publishes its own height as `--scroll-root-h`, which the scroll-driven patterns use where they
* would say `100vh`: a screen of scroll is a screen of this box, and they work in a box as small
* as a gallery preview. `relative` is load-bearing: motion's `useScroll({ container })` measures
* offsets against it.
*/
function ScrollRoot({ className, children, ...props }: React.ComponentProps<'div'>) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const element = ref.current;
if (!element) return;
// A measured length in real pixels: the ui-scale pass has nothing to scale here.
const publish = () => element.style.setProperty('--scroll-root-h', `${element.clientHeight}px`);
publish();
const observer = new ResizeObserver(publish);
observer.observe(element);
return () => observer.disconnect();
}, []);
return (
<ScrollRootContext.Provider value={ref}>
<div
ref={ref}
data-slot="scroll-root"
className={cn('relative h-full overflow-y-auto overflow-x-hidden', className)}
{...props}
>
{children}
</div>
</ScrollRootContext.Provider>
);
}
/**
* The nearest `ScrollRoot`'s element, or `undefined` when the window scrolls — pass it straight to
* motion's `useScroll({ container })` or a `viewport.root`. Never substitute an empty ref for the
* window: motion waits for a pending ref to resolve, and one that never does never tracks.
*/
function useScrollRoot(): RefObject<HTMLElement | null> | undefined {
return useContext(ScrollRootContext);
}
export { ScrollRoot, useScrollRoot };