Skip to content

Commit

Permalink
fix(useElementRect): prop structure
Browse files Browse the repository at this point in the history
  • Loading branch information
andrre-ls committed Nov 5, 2024
1 parent 0e73e43 commit dec3cce
Show file tree
Hide file tree
Showing 6 changed files with 28 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export function HorizontalScrollSection() {
const root = useRef(null);
const content = useRef(null);
const parentRef = useParentRef(root);
const { width } = useElementRect({ ref: parentRef });
const { width: contentWidth } = useElementRect({ ref: content });
const { width } = useElementRect(parentRef);
const { width: contentWidth } = useElementRect(content);

const { scrollYProgress } = useScroll({
target: root,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { useElementRect } from 'hooks/foundations/useElementRect';
export function UseElementRectCallback() {
const ref = useRef<HTMLDivElement | null>(null);

useElementRect({
ref,
useElementRect(ref, {
onResize: (rect) => {
console.log('Element dimensions:', rect);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useElementRect } from 'hooks/foundations/useElementRect';

export function UseElementRectState() {
const ref = useRef<HTMLDivElement | null>(null);
const { width, height, x, y } = useElementRect({ ref });
const { width, height, x, y } = useElementRect(ref);

return (
<div ref={ref} className="absolute inset-0 flex items-center justify-center">
Expand Down
30 changes: 13 additions & 17 deletions src/hooks/foundations/useElementRect/useElementRect.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { RefObject } from 'react';
import { useEffect, useState } from 'react';

import { debounce as debounceFn } from 'lib/utils/debounce';
import { useEffect, useState, type RefObject } from 'react';
import { debounce } from 'lib/utils/debounce';

type ElementRect = {
width: number;
Expand All @@ -10,17 +8,15 @@ type ElementRect = {
y: number;
};

type UseElementRectOptions<T extends HTMLElement = HTMLElement> = {
ref: RefObject<T>;
type UseElementRectOptions = {
debounce?: boolean | number;
onResize?: (rect: ElementRect) => void;
};

export const useElementRect = ({
ref,
debounce = 32,
onResize
}: UseElementRectOptions): ElementRect => {
export function useElementRect<T extends HTMLElement = HTMLElement>(
ref: RefObject<T>,
options: UseElementRectOptions = {}
): ElementRect {
const [rect, setRect] = useState<ElementRect>({ width: 0, height: 0, x: 0, y: 0 });

useEffect(() => {
Expand All @@ -39,17 +35,17 @@ export const useElementRect = ({

const currentRect = { width, height, x, y };

if (onResize) {
onResize(currentRect);
if (options.onResize) {
options.onResize(currentRect);
} else {
setRect(currentRect);
}
};

if (debounce) {
onResizeObserver = debounceFn(
onResizeObserver = debounce(
onResizeObserver,
typeof debounce === 'boolean' ? 32 : debounce
typeof options.debounce === 'boolean' ? 32 : options.debounce
);
}

Expand All @@ -59,7 +55,7 @@ export const useElementRect = ({
return () => {
observer.disconnect();
};
}, [debounce, onResize, ref]);
}, [options, ref]);

return rect;
};
}
7 changes: 4 additions & 3 deletions src/pages/hooks/use-element-rect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ Custom hook that provides the dimensions and position of a DOM element, updating
- **Parameters:**

- `ref`: A `RefObject` pointing to the DOM element for which dimensions are to be tracked.
- `debounce` (optional): A boolean or number that determines whether to debounce the resize events. If a number is provided, it specifies the debounce delay in milliseconds. Defaults to `32`.
- `onResize` (optional): A callback function that receives the current rectangle dimensions and position when the element resizes. **If set, the hook will not update the returned state.**
- `options`:
- `debounce` (optional): A boolean or number that determines whether to debounce the resize events. If a number is provided, it specifies the debounce delay in milliseconds. Defaults to `32`.
- `onResize` (optional): A callback function that receives the current rectangle dimensions and position when the element resizes. **If set, the hook will not update the returned state.**

- **Returns:**
- Returns an object containing the `width`, `height`, `x`, and `y` properties of the tracked element.
Expand All @@ -34,7 +35,7 @@ import { useElementRect } from '[path]/useElementRect';

function Component() {
const ref = useRef<HTMLDivElement | null>(null);
const rect = useElementRect({ ref });
const rect = useElementRect(ref);

// ...
}```
Expand Down
14 changes: 7 additions & 7 deletions src/pages/recipes/horizontal-scroll-section.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import { HorizontalScrollSection } from 'components/foundations/HorizontalScroll

# Horizontal Scroll Section

In the context of this recipe, a horizontal scroll section allows the user to scroll vertically down the page, while the scroll interaction is temporarily mapped to horizontal movement along the x-axis. This setup creates a smooth horizontal animation experience without requiring the user to scroll sideways manually.
A horizontal scroll section allows the user to scroll vertically down the page, while the scroll interaction is temporarily mapped to horizontal movement along the x-axis. This setup creates a smooth horizontal animation experience without requiring the user to scroll sideways manually.

<HorizontalScrollSection />

## Layout

A horizontal scroll section requires three key DOM elements. First, **content that overflows horizontally**, which will be the element we apply a `translate` to, simulating the horizontal scroll movement. Second, a **view container** that sets the dimensions of our “window” into this content. This element’s dimensions should be `width: 100vw` and `height: 100vh` to cover the full viewport and have `overflow: hidden` to prevent the content from overflowing onto the page.

Third, we’ll add a **root element** to define the vertical scroll height of the section. Although the content scrolls horizontally, the user is still scrolling vertically, so we need a DOM element that “reserves” space for this interaction. For now, we can set the height of this element to `height: 300vh` as a placeholder, and we’ll come back to this value later.
Third, we’ll add a **root element** to define the vertical scroll height of the section. Although the content scrolls horizontally, the user is still scrolling vertically, so we need a DOM element that “reserves” space for this interaction. For now, we can set the height of this element to `height: 300vh` as a placeholder, but we’ll come back to this value later.

Finally, to ensure the **view container** remains in place while scrolling through the section, set `position: sticky` and `top: 0` on the view container so it stays fixed as the root element is scrolled by.

In summary, your template should structured as follows:
In summary, your template should be structured as follows:

```tsx copy
<section className="h-[300vh]">
Expand All @@ -26,11 +26,11 @@ In summary, your template should structured as follows:

## Animation

The next step is to apply a horizontal transform to our content, moving it from its starting position to where its right edge aligns with the right edge of the viewport. We can calculate this total translation directly in CSS with `calc(100vw - 100%)`. Then, we multiply this value by a normalized scroll value that indicates how far we’ve scrolled through our **root** element. This can be applied in CSS as `calc((-100% + 100vw) * var(--scroll-progress))`.
The next step is to apply a horizontal transform to our content, moving it from its starting position to where its right edge aligns with the right edge of the viewport. We can calculate this total translation directly in CSS with `calc(-100% + 100vw)`. Then, we multiply this by a normalized scroll value that indicates how far we’ve scrolled through our **root** element. Let's define it as `calc((-100% + 100vw) * var(--scroll-progress))`.

All we need now is to measure our scroll progress value, which starts at `0` when the top of the section meets the top of the viewport and reaches `1` when the bottom of the section aligns with the bottom of the viewport. This progress value will then be passed directly into our CSS.

For simplicity, we’ll use Framer Motion’s `useScroll` hook to obtain this scroll progress value directly. Since `framer-motion` already uses an internal `requestAnimationFrame` ticker to perform style updates, we don’t need to worry about it here. However, if you write your own scroll progress implementation be sure to use a `requestAnimationFrame` to update your scroll progress value in CSS for the best performance ([read more about it here](https://significa.co/blog/master-javascript-web-animations)).
For simplicity, we’ll use [Framer Motion](https://www.framer.com/motion/use-scroll/)’s `useScroll` hook to obtain this scroll progress value directly. Since `framer-motion` already uses an internal `requestAnimationFrame` ticker to perform style updates, we don’t need to worry about it here. However, if you write your own scroll progress implementation be sure to use a `requestAnimationFrame` to update your scroll progress value in CSS for the best performance ([read more about it here](https://significa.co/blog/master-javascript-web-animations)).

```tsx copy
import { useRef } from 'react';
Expand Down Expand Up @@ -75,7 +75,7 @@ export const HorizontalScrollSection = () => {
const root = useRef(null);
const content = useRef(null);

const { width: contentWidth } = useElementRect({ ref: content });
const { width: contentWidth } = useElementRect(content);
const { scrollYProgress } = useScroll({ target: root });

useMotionValueEvent(scrollYProgress, 'change', (value) => {
Expand Down Expand Up @@ -122,7 +122,7 @@ const HorizontalScrollSectionPointer = () => {
const root = useRef(null);
const content = useRef(null);

const { width: contentWidth } = useElementRect({ ref: content });
const { width: contentWidth } = useElementRect(content);
const { scrollYProgress } = useScroll({ target: root });

useMotionValueEvent(scrollYProgress, 'change', (value) => {
Expand Down

0 comments on commit dec3cce

Please sign in to comment.