Intersection Observer patterns
Lazy sections, sticky headers and scroll spies out of one small hook.
Scroll listeners are where performance goes to die. Intersection Observer is the API that replaces most of them, and a single hook around it covers more ground than I expected.
The hook
useInView(ref, options) returns a boolean and, optionally, the intersection ratio. Internally it creates one observer per options object and shares it between callers, so fifty cards on a page do not mean fifty observers.
Three uses
Lazy sections: mount heavy components only once they are near the viewport. Sticky headers: watch a sentinel above the header and toggle a shadow when it leaves. Scroll spies: observe each heading and mark the last one that crossed the top as active.
Every scroll listener I have removed was replaced by an observer and a sentinel.
The one gotcha
Root margins are strings, and "0px 0px -40% 0px" reads like a typo until it does not. Comment them.