๋ฐ˜์‘ํ˜•

๋ฌธ๋ฒ•


ํŠน์ • ์š”์†Œ๊ฐ€ ์œ„์น˜ํ•œ ๊ณณ๊นŒ์ง€ ์Šคํฌ๋กค์„ ์ด๋™ํ•˜๊ณ  ์‹ถ์„ ๋•Œ element.scrollIntoView ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด ๊ฐ„ํŽธํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค. scrollIntoView ๋ฉ”์„œ๋“œ๋Š” ์ด 3๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. (MDN)

element.scrollIntoView(align)

 

โถ ํŒŒ๋ผ๋ฏธํ„ฐ ์—†์Œ — element๊ฐ€ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด ๊ฐ€์žฅ ์œ„๋กœ ์˜ค๋„๋ก ์Šคํฌ๋กค(์ •๋ ฌ)

element.scrollIntoView(); // element.scrollIntoView(true)์™€ ๋™์ผ

 

โท boolean ํŒŒ๋ผ๋ฏธํ„ฐ

// { block: "start", inline: "nearest" } ์˜ต์…˜๊ณผ ๋™์ผ
element.scrollIntoView(true); 

// { block: "end", inline: "nearest" } ์˜ต์…˜๊ณผ ๋™์ผ
element.scrollIntoView(false);

 

  • true๋ฅผ ๋„˜๊ฒผ์„ ๋•Œ : element๊ฐ€ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด ๊ฐ€์žฅ ์œ„๋กœ ์˜ค๋„๋ก ์Šคํฌ๋กค(์ •๋ ฌ)
  • false๋ฅผ ๋„˜๊ฒผ์„ ๋•Œ : element๊ฐ€ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด ๊ฐ€์žฅ ์•„๋ž˜๋กœ ์˜ค๋„๋ก ์Šคํฌ๋กค(์ •๋ ฌ)

 

โธ ์˜ต์…˜ ๊ฐ์ฒด ํŒŒ๋ผ๋ฏธํ„ฐ

element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });

 

  • behavior : ์Šคํฌ๋กค ์ „ํ™˜ ํšจ๊ณผ (๊ธฐ๋ณธ๊ฐ’ auto)
    ์˜ต์…˜ : auto, smooth
  • block : ์ˆ˜์ง ์ •๋ ฌ (๊ธฐ๋ณธ๊ฐ’ start)
    ์˜ต์…˜ : start, end, center, nearest
  • inline : ์ˆ˜ํ‰ ์ •๋ ฌ (๊ธฐ๋ณธ๊ฐ’ nearest)
    ์˜ต์…˜ : start, end, center, nearest

 

๐Ÿ’ก block, inline ์˜ต์…˜๊ฐ’ ์„ค๋ช…

  • start : element๊ฐ€ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด ๊ฐ€์žฅ ์œ„๋กœ ์˜ค๋„๋ก ์Šคํฌ๋กค(์ •๋ ฌ)
  • end : element๊ฐ€ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด ๊ฐ€์žฅ ์•„๋ž˜๋กœ ์˜ค๋„๋ก ์Šคํฌ๋กค(์ •๋ ฌ)
  • center : element๊ฐ€ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด ์ค‘๊ฐ„์œผ๋กœ ์˜ค๋„๋ก ์Šคํฌ๋กค(์ •๋ ฌ)
  • nearest : element์™€ ๊ฐ€๊นŒ์šด ๊ณณ์œผ๋กœ ์Šคํฌ๋กค

 

ํ•จ์ˆ˜ ์‘์šฉ ์˜ˆ์‹œ


const moveToTop = () => document.body.scrollIntoView(true); // ์ƒ๋‹จ์œผ๋กœ ์ด๋™
const moveToBottom = () => document.body.scrollIntoView(false); // ํ•˜๋‹จ์œผ๋กœ ์ด๋™

 

์ปค์Šคํ…€ ํ›… ์‚ฌ์šฉ ์˜ˆ์‹œ


์ปดํฌ๋„ŒํŠธ โ–ผ

// ์ปดํฌ๋„ŒํŠธ ๋ณธ๋ฌธ (๋ฐฐ์—ด orders, Set ๊ฐ์ฒด selectedOrderIds๋ฅผ prop์œผ๋กœ ๋ฐ›์Œ)
const tableRowRefs = useRef(
  orders.map(({ order_id }) => ({
    id: order_id,
    element: createRef<HTMLTableRowElement>(), // ref ๊ฐ์ฒด ์—ฌ๋Ÿฌ๊ฐœ ์ƒ์„ฑ
  })),
);

useScrollIntoView({
  isActive: selectedOrderIds.size === 1, // true ์ผ๋•Œ๋งŒ ํ™œ์„ฑํ™”
  ref: tableRowRefs.current.find(({ id }) => id === [...selectedOrderIds][0])
    ?.element,
  align: { behavior: 'smooth', block: 'center' }, // align ์˜ต์…˜
});

return (
  <div>
    {orders.map((order, i, { length }) => (
      <div ref={tableRowRefs.current[i].element}>{/* ... */}</div>
    ))}
  </div>
);

 

์ปค์Šคํ…€ ํ›… โ–ผ

interface Props {
  isActive: boolean; // useScrollIntoView ํ›… ํ™œ์„ฑํ™” ์—ฌ๋ถ€
  ref: RefObject<HTMLElement> | undefined;
  align?: ScrollIntoViewOptions; // scrollIntoView ๋ฉ”์„œ๋“œ align ์˜ต์…˜
}

/** ์ฒซ ๋ Œ๋”๋ง์—๋งŒ ์„ ํƒํ•œ ์—˜๋ฆฌ๋จผํŠธ๊ฐ€ ์œ„์น˜ํ•œ ๊ณณ์œผ๋กœ ์Šคํฌ๋กคํ•˜๋Š” Hook */
export default function useScrollIntoView({ isActive, ref, align }: Props) {
  const isNavigated = useRef(false); // ์š”์†Œ๊ฐ€ ์žˆ๋Š”๊ณณ๊นŒ์ง€ ์Šคํฌ๋กค ํ–ˆ๋Š”์ง€ ์—ฌ๋ถ€

  useEffect(() => {
    if (isActive && !isNavigated.current) {
      ref?.current?.scrollIntoView(align);
      isNavigated.current = true; // ์ฒซ ๋ Œ๋”๋ง์—๋งŒ ์Šคํฌ๋กคํ•˜๊ธฐ ์œ„ํ•ด isNavigated ๊ฐ’ true๋กœ ๋ณ€๊ฒฝ
    }
  }, [align, isActive, ref]);
}

 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•