I love little scrollspies, particularly notion's. While they're simple as anything, I wanted to make my own.
npm install parchmentFirst off the bat: we're using Context, so you'll need to wrap any parchment fellas with the ParchmentProvider.
The scroll, which will have sections that can be scrolled to. Can accept any ReactNode children.
Only ParchmentSections are observed by our parchment scrollspy.
import { Parchment } from 'parchment';
interface ParchmentOptions {
/**
* Sets the intersection threshold for all parchment sections.
*
* @default 0.5
*/
intersectionThreshold?: number;
/**
* Sets options for the `scrollIntoView` method, when a ParchmentButton is clicked.
*
* @default { behavior: 'smooth', block: 'center' }
*/
scrollIntoViewOptions?: ScrollIntoViewOptions;
/**
* Whether to enable scroll-snapping on the parchment container.
*
* @default false
*/
snap?: boolean;
}
return (
<Parchment snap={true} scrollIntoViewOptions={{ block: 'start' }} intersectionThreshold={0.3}>
{...sections}
</Parchment>
)Any 'scrollable' sections should be wrapped in a ParchmentSection component. This will register the section with the context.
import { Parchment, ParchmentSection } from 'parchment';
function MyScrollableSections() {
return (
<Parchment>
<ParchmentSection section="my-section">
{/* Your content here */}
</ParchmentSection>
</Parchment>
);
}Parchment buttons are wired up to know when their associated section is in view. An 'active' class is applied to the button, or passed as a boolean argument to a function.
import ParchmentButton from 'parchment';
/*
* .parchment-button.active {
* color: rebeccapurple;
* }
*/
function MyButton() {
return (
<ParchmentButton section="my-section">
My Section Name
</ParchmentButton>
);
}/**
* If the `ParchmentButton` child is a function, it is called
* with a boolean reflecting whether the section is in view.
*/
function MyButton() {
return (
<ParchmentButton section="my-section">
{(active) => (
<div style={{ color: active ? 'rebeccapurple' : 'blue' }}>
My Section Name
</div>
)}
</ParchmentButton>
);
}So, at the end of the day, we're up and away with the following:
import { Parchment, ParchmentSection, ParchmentButton } from 'parchment';
function MyScrollableSections() {
return (
<ParchmentProvider>
<ParchmentButton section="my-section">
My Section Name
</ParchmentButton>
<Parchment>
<ParchmentSection section="my-section">
{/* Your content here */}
</ParchmentSection>
</Parchment>
</ParchmentProvider>
);
}The useParchment hook provides a few useful functions for interacting with the Parchment context.
Uses the window's native scrollIntoView method to scroll to a section by key, but with options loaded in the parchment context.
Returns the key of the section currently in viewport, e.g.:
import { useParchment } from 'parchment';
function MyComponent() {
const { scrollTo, inView } = useParchment();
console.log(inView); // 'my-section'
return (
<button onClick={() => scrollTo('my-section')}>
Scroll to my section
</button>
);
}Parchment accepts the global ScrollIntoViewOptions object as a prop.
Feel free to contribute. Has the default vite scripts, so to get up and running after cloning just run npm run dev.
- Add tests
- Add more features
- Add horizontal/vertical option
- Add
asChildprop toParchmentButtonto allow for custom components - Add
thresholdprop toParchmentButtonto allow for custom scroll thresholds - Add
percentInViewoption? - Review from performance perspective - what needs to be memoised?