Skip to content

Commit b0ede23

Browse files
committed
✨ NEW: offset props, spy on particular div with parentScrollContainerRef prop
1 parent 559dc3d commit b0ede23

File tree

4 files changed

+78
-24
lines changed

4 files changed

+78
-24
lines changed

README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,15 @@ import ScrollSpy from "react-ui-scrollspy";
6868

6969
## 💡 Props
7070

71-
| Attributes | Type | Description | Default | Required |
72-
| :----------------- | :----------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- | :------ | :------- |
73-
| `scrollThrottle` | `number` | in `milliseconds` to throttle the `onscroll` event. Lower the number, better the response, higher the performance cost | 300 | no |
74-
| `children` | `ReactNode` | - | - | yes |
75-
| `navContainerRef` | `MutableRefObject<HTMLDivElement \| null>` | `ref` to your navigation container containing items with `data-to-scrollspy-id` attributes | - | no |
76-
| `onUpdateCallback` | `(id: string) => void` | Executes this function whenever you scroll to an `Element`, callback returns the `id` of that `Element` as well | - | no |
71+
| Attributes | Type | Description | Default | Required |
72+
| :------------------------- | :----------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- | :------ | :------- |
73+
| `children` | `ReactNode` | Each direct child `Element` should contain an `id` | - | yes |
74+
| `scrollThrottle` | `number` | in `milliseconds` to throttle the `onscroll` event. Lower the number, better the response, higher the performance cost | 300 | no |
75+
| `navContainerRef` | `MutableRefObject<HTMLDivElement \| null>` | `ref` to your navigation container containing items with `data-to-scrollspy-id` attributes | - | no |
76+
| `onUpdateCallback` | `(id: string) => void` | Executes this function whenever you scroll to an `Element`, callback returns the `id` of that `Element` as well | - | no |
77+
| `parentScrollContainerRef` | `MutableRefObject<HTMLDivElement \| null>` | If you want to spy only on a particular scrollable `container (Element)` then pass a ref of the same to this prop | - | no |
78+
| `offsetTop` | `number` | spy will be fired when it has been scrolled `offsetTop` beyond `50%` to the top of the containing element | 0 | no |
79+
| `offsetBottom` | `number` | spy will be fired when it has been scrolled `offsetBottom` beyond `50%` to the bottom of the containing element | 0 | no |
7780

7881
## 📝 Authors
7982

demo-app/src/components/src/ScrollSpy/ScrollSpy.tsx

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,21 @@ import { throttle } from "../utils/throttle";
1212
interface ScrollSpyProps {
1313
children: ReactNode;
1414
navContainerRef?: MutableRefObject<HTMLDivElement | null>;
15+
parentScrollContainerRef?: MutableRefObject<HTMLDivElement | null>;
1516
scrollThrottle?: number;
1617
onUpdateCallback?: (id: string) => void;
18+
offsetTop?: number;
19+
offsetBottom?: number;
1720
}
1821

1922
const ScrollSpy = ({
2023
children,
2124
navContainerRef,
25+
parentScrollContainerRef,
2226
onUpdateCallback,
2327
scrollThrottle = 300,
28+
offsetTop = 0,
29+
offsetBottom = 0,
2430
}: ScrollSpyProps) => {
2531
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
2632
const [navContainerItems, setNavContainerItems] = useState<NodeListOf<Element> | undefined>(); // prettier-ignore
@@ -58,12 +64,19 @@ const ScrollSpy = ({
5864
// loop over all children in scroll container
5965
for (let i = 0; i < scrollParentContainer.children.length; i++) {
6066
// get child element
61-
const child = scrollParentContainer.children.item(i);
62-
if (!child) continue;
63-
const useChild = child as HTMLDivElement;
67+
const useChild = scrollParentContainer.children.item(i) as HTMLDivElement;
68+
69+
const elementIsVisible = parentScrollContainerRef
70+
? isVisible(
71+
useChild,
72+
offsetTop,
73+
offsetBottom,
74+
parentScrollContainerRef?.current
75+
)
76+
: isVisible(useChild, offsetTop, offsetBottom);
6477

6578
// check if the element is in the viewport
66-
if (isVisible(useChild)) {
79+
if (elementIsVisible) {
6780
// if so, get its ID
6881
const changeHighlightedItemId = useChild.id;
6982

@@ -100,10 +113,18 @@ const ScrollSpy = ({
100113
}
101114
};
102115

103-
window.addEventListener(
104-
"scroll",
105-
throttle(checkAndUpdateActiveScrollSpy, scrollThrottle)
106-
);
116+
// listen for scroll event
117+
parentScrollContainerRef
118+
? // if ref for scrollable div is provided
119+
parentScrollContainerRef.current?.addEventListener(
120+
"scroll",
121+
throttle(checkAndUpdateActiveScrollSpy, scrollThrottle)
122+
)
123+
: // else listen for scroll in window
124+
window.addEventListener(
125+
"scroll",
126+
throttle(checkAndUpdateActiveScrollSpy, scrollThrottle)
127+
);
107128

108129
return <div ref={scrollContainerRef}>{children}</div>;
109130
};
Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1+
import { MutableRefObject } from "react";
2+
13
// to check if the element is in viewport
2-
export const isVisible = (el: HTMLElement) => {
4+
export const isVisible = (
5+
el: HTMLElement,
6+
offsetTop: number,
7+
offsetBottom: number,
8+
scrollableComponentRef?: HTMLDivElement | null
9+
) => {
310
const rectInView = el.getBoundingClientRect();
411

512
// this decides how much of the element should be visible
6-
const leniency = window.innerHeight * 0.5;
13+
const leniency = scrollableComponentRef
14+
? scrollableComponentRef.offsetHeight * 0.5
15+
: window.innerHeight * 0.5;
16+
17+
const useHeight = scrollableComponentRef
18+
? scrollableComponentRef.offsetHeight
19+
: window.innerHeight;
720

821
return (
9-
rectInView.top + leniency >= 0 &&
10-
rectInView.bottom - leniency <= window.innerHeight
22+
rectInView.top + leniency + offsetTop >= 0 &&
23+
rectInView.bottom - leniency - offsetBottom <= useHeight
1124
);
1225
};

dev.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,34 @@
22

33
make required updates in the `src` (root) directory.
44

5-
```
5+
```bash
66
npm run build
77
npm link
88
cd demo-app
99
npm link react-ui-scrollspy
1010
```
1111

12-
- [ ] callbacks
13-
- [ ] on scroll top and onscrollbottom fix
14-
- [ ] deploy test website on github pages
15-
- [ ] offset props
12+
## commit messages
13+
14+
https://github.com/ahmadawais/Emoji-Log
15+
16+
```bash
17+
git commit -m "✨ NEW: "
18+
git commit -m "👌 IMPROVE: "
19+
git commit -m "🐛 FIX: "
20+
git commit -m "📚 DOC: "
21+
git commit -m "🚀 RELEASE: "
22+
git commit -m "🤖 TEST: "
23+
git commit -m "‼️ BREAKING: "
24+
```
25+
26+
- [x] callbacks
27+
- [x] offset props
28+
- [x] relative positioning handled
1629
- [ ] customise data attributes
1730
- [ ] description, keywords
18-
- [ ] changelog
31+
- [ ] deploy test website on github pages
32+
- [x] update docs
33+
- [ ] deploy as version 2
34+
35+
- [ ] on scroll top and onscrollbottom fix

0 commit comments

Comments
 (0)