Skip to content

Commit

Permalink
observer cache improvement for using different observers on the same …
Browse files Browse the repository at this point in the history
…element
  • Loading branch information
onderonur committed Feb 28, 2023
1 parent 3010199 commit 4a90bd5
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 36 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# react-intersection-observer-hook

![Build status](https://img.shields.io/github/actions/workflow/status/onderonur/react-intersection-observer-hook/main.yml)
![License](https://img.shields.io/npm/l/react-intersection-observer-hook)
![Version](https://img.shields.io/npm/v/react-intersection-observer-hook)

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->

[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)

<!-- ALL-CONTRIBUTORS-BADGE:END -->
![Build status](https://img.shields.io/github/workflow/status/onderonur/react-intersection-observer-hook/CI)
![License](https://img.shields.io/npm/l/react-intersection-observer-hook)
![Version](https://img.shields.io/npm/v/react-intersection-observer-hook)

This is a small React hook package to use [Insersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) declaratively. By using this hook, you can easily track if a component is visible or not, create lazy loading images, trigger animations on entering or leaving the screen etc.

Expand Down Expand Up @@ -128,4 +132,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
2 changes: 1 addition & 1 deletion example/components/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function Message({ label, isVisible }: MessageProps) {
<MessageLabel>{label}:</MessageLabel>
{isVisible
? '(づ。◕‿‿◕。)づ You have found it!'
: "¯\\_(ツ)_/¯ I don't know where the green ball is. Use scroll to find it."}
: `¯\\_(ツ)_/¯ I don't know where it is. Use scroll to find it.`}
</MessageContent>
);
}
Expand Down
48 changes: 36 additions & 12 deletions example/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ const Content = styled.div`
height: 3000px;
`;

const Ball = styled.div`
const Ball = styled.div<{ color: string }>`
width: 100px;
height: 100px;
border-radius: 50%;
background-color: #1db954;
background-color: ${({ color }) => color};
margin-left: auto;
margin-right: auto;
margin-top: 50%;
Expand All @@ -57,29 +57,49 @@ enum ParentType {
function App() {
const [isContentVisible, setIsContentVisible] = React.useState(true);
const [mode, setMode] = React.useState(ParentType.DOCUMENT);
const [ref, { isVisible, rootRef }] = useTrackVisibility();
const [
ref2,
{ isVisible: isVisible2, rootRef: rootRef2 },
firstBallRef,
{ isVisible: isFirstBallVisible, rootRef: firstBallRootRef },
] = useTrackVisibility();

const [
secondBallRef1,
{ isVisible: isSecondBallVisible1, rootRef: secondBallRootRef1 },
] = useTrackVisibility();

const [
secondBallRef2,
{ isVisible: isSecondBallVisible2, rootRef: secondBallRootRef2 },
] = useTrackVisibility({
threshold: 0.5,
});

const secondBallRef = React.useCallback(
(node: HTMLDivElement) => {
secondBallRef1(node);
secondBallRef2(node);
},
[secondBallRef1, secondBallRef2],
);

const content = (
<>
<Content>
<Ball ref={ref} />
<Ball ref={firstBallRef} color="#1db954" />
</Content>
<Content>
<Ball ref={ref2} />
<Ball ref={secondBallRef} color="#f20404" />
</Content>
</>
);

const rootCallback = React.useCallback(
(node) => {
rootRef(node);
rootRef2(node);
firstBallRootRef(node);
secondBallRootRef1(node);
secondBallRootRef2(node);
},
[rootRef, rootRef2],
[firstBallRootRef, secondBallRootRef1, secondBallRootRef2],
);

return (
Expand Down Expand Up @@ -107,8 +127,12 @@ function App() {
/>
Show Content
</Label>
<Message label="First ball" isVisible={isVisible} />
<Message label="Second ball" isVisible={isVisible2} />
<Message label="Green ball" isVisible={isFirstBallVisible} />
<Message label="Red ball" isVisible={isSecondBallVisible1} />
<Message
label="More than half of red ball"
isVisible={isSecondBallVisible2}
/>
</Top>
{isContentVisible && (
<div>
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-intersection-observer-hook",
"version": "2.1.0",
"version": "2.1.1",
"license": "MIT",
"author": "onderonur",
"main": "dist/index.js",
Expand Down
38 changes: 22 additions & 16 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
type ObserverCache = Map<string, IntersectionObserver>;
type EntryCallback = (entry: IntersectionObserverEntry) => void;

type CachedObserver = {
observer: IntersectionObserver;
entryCallbacks: Map<Element, EntryCallback>;
};

type ObserverCache = Map<string, CachedObserver>;

type ObserverCachesByRoot = Map<
IntersectionObserverInit['root'],
Expand All @@ -16,28 +23,25 @@ export type CachedIntersectionObserver = {
export function createObserverCache() {
const cachesByRoot: ObserverCachesByRoot = new Map();

const entryCallbacks = new Map<
Element,
(entry: IntersectionObserverEntry) => void
>();

function getObserver({
root,
rootMargin,
threshold,
}: IntersectionObserverInit): CachedIntersectionObserver {
let cacheByRoot: ObserverCache | undefined = cachesByRoot.get(root);
let cacheByRoot = cachesByRoot.get(root);

if (!cacheByRoot) {
cacheByRoot = new Map<string, IntersectionObserver>();
cacheByRoot = new Map();
cachesByRoot.set(root, cacheByRoot);
}

const cacheKey = JSON.stringify({ rootMargin, threshold });
let observer = cacheByRoot.get(cacheKey);
let cachedObserver = cacheByRoot.get(cacheKey);

if (!cachedObserver) {
const entryCallbacks = new Map<Element, EntryCallback>();

if (!observer) {
observer = new IntersectionObserver(
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
const callback = entryCallbacks.get(entry.target);
Expand All @@ -47,20 +51,22 @@ export function createObserverCache() {
{ root, rootMargin, threshold },
);

cacheByRoot.set(cacheKey, observer);
cachedObserver = { observer, entryCallbacks };

cacheByRoot.set(cacheKey, cachedObserver);
}

return {
observe: (
node: Element,
callback: (entry: IntersectionObserverEntry) => void,
) => {
entryCallbacks.set(node, callback);
observer?.observe(node);
cachedObserver?.entryCallbacks.set(node, callback);
cachedObserver?.observer.observe(node);
},
unobserve: (node: Element) => {
entryCallbacks.delete(node);
observer?.unobserve(node);
cachedObserver?.entryCallbacks.delete(node);
cachedObserver?.observer.unobserve(node);
},
};
}
Expand Down

0 comments on commit 4a90bd5

Please sign in to comment.