์Šค์™€์ดํ”„ ๐Ÿ‘‰ throttle๋กœ ์ด๋ฒคํŠธ ์ œ์–ดํ•˜๊ธฐ

์†์œค์ฃผยท2022๋…„ 7์›” 22์ผ
1
post-thumbnail

ํ˜„์žฌ ๊ท€๋†ํ•œ ์ฒญ๋…„ ํ˜น์€ ๋†์žฅ๊ด€๋ฆฌ์ž๋ฅผ ์œ„ํ•œ ๋†์žฅ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ ์„œ๋น„์Šค๋ฅผ ๊ตฌํ˜„ ์ค‘์— ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜์ธ ์‹œ์„ธํŽ˜์ด์ง€์—์„œ โ€œ๐Ÿ‘€ย ๋‚ด ์ž‘๋ฌผ ์‹œ์„ธ๋ฅผ ํ•œ ๋ˆˆ์—" ํƒญ์— ์‚ฌ์šฉ์ž๊ฐ€ ๋“ฑ๋กํ•ด๋†“์€ ์ž‘๋ฌผ ์ „์ฒด ์ฐจํŠธ๋ฅผ ์Šค์™€์ดํ”„๋กœ ๋„˜๊ธฐ๋ฉฐ ํ•œ ๋ˆˆ์— ๋ณด์—ฌ์ฃผ๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ ์ผ์ •์ƒ react-slick , swiper ์™€ ๊ฐ™์€ ์บ๋Ÿฌ์…€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋„์ž…๋„ ๊ณ ๋ คํ–ˆ์œผ๋‚˜ ์Šค์™€์ดํ”„์‹œ ์—ญ๋™์ ์ธ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋“ค์–ด๊ฐ€๋ฉด ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง€๊ณ , ์›ํ•˜๋Š” ๋ ˆ์ด์•„์›ƒ์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ณ ์ž ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—†์ด ์ง์ ‘ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •!

์ฝ”๋“œ ๊ตฌํ˜„

// MarketPrice.js

const MarketPrice = () => {

 const scrollRef = useRef(null);
 const [isDrag, setIsDrag] = useState(false);
 const [startX, setStartX] = useState();


 const onDragStart = (e) => {
    e.preventDefault();
    setIsDrag(true);
    setStartX(e.pageX + scrollRef.current.scrollLeft);
  };

  const onDragEnd = () => {
    setIsDrag(false);
  };

  const onDragMove = (e) => {
    if (isDrag) {
      const { scrollWidth, clientWidth, scrollLeft } = scrollRef.current;
      scrollRef.current.scrollLeft = startX - e.pageX;
      if (scrollLeft === 0) {
        setStartX(e.pageX);
      } else if (scrollWidth <= clientWidth + scrollLeft) {
        setStartX(e.pageX + scrollLeft);
      }
    }
  };

 return (
    ...
    <MyCropsChartWrap
       onMouseDown={onDragStart}
       onMouseUp={onDragEnd}
       onMouseMove={onDragMove}
       onMouseLeave={onDragEnd}
       ref={scrollRef}
     >
        <MyCropsMarketPriceCard checkedInputs={checkedInputs} />
     </MyCropsChartWrap>
     ...
 )
}

useRef ์‚ฌ์šฉํ•˜์—ฌ DOM ์ ‘๊ทผ

1๏ธโƒฃย ์ขŒ์šฐ ์Šฌ๋ผ์ด๋“œ ์Šคํฌ๋กค์˜ ์›€์ง์ž„์€ ํ•ด๋‹น DOM์˜ย scrollLeft๋กœ ์›€์ง์ž„. ย 
scrollLeft๋ฅผ ์–ป๊ธฐ ์œ„ ํ•ดย useRef๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DOM์— ์ ‘๊ทผ


์‚ฌ์šฉํ•œ ์ด๋ฒคํŠธ

1๏ธโƒฃ onMouseDown ๋งˆ์šฐ์Šค ์™ผ์ชฝ ๋ฒ„ํŠผ ๋ˆ„๋ฅด๊ณ  ์žˆ๋Š” ์ƒํƒœ

2๏ธโƒฃ onMouseUp ๋งˆ์šฐ์Šค ์™ผ์ชฝ ๋ฒ„ํŠผ ๋—€ ์ƒํƒœ

3๏ธโƒฃย onMouseMove ํด๋ฆญ ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด ๋งˆ์šฐ์Šค๋ฅผ ์›€์ง์ด๋Š” ์ƒํƒœ

4๏ธโƒฃ onMouseLeave DOM์—์„œ ๋งˆ์šฐ์Šค๊ฐ€ ๋ฒ—์–ด๋‚ฌ๋Š”์ง€ ์ฒดํฌํ•˜๋Š” ์ด๋ฒคํŠธ


์‚ฌ์šฉํ•œ ๋ณ€์ˆ˜

1๏ธโƒฃ DOM.scrollWidth์Šคํฌ๋กค ํ•  ์ˆ˜ ์žˆ๋Š” ์ด ๊ธธ์ด

2๏ธโƒฃ DOM.clientWidth์„ค์ •ํ•œ max width ( ํ™”๋ฉด์— ๋ณด์ด๋Š” ์Šคํฌ๋กค์˜ ๊ธธ์ด )

3๏ธโƒฃ DOM.scrollLeft์Šคํฌ๋กค ๊ฐ€์žฅ ์™ผ์ชฝ (DOM.scrollLeft = 0ย ) ๋ถ€ํ„ฐ ์ด๋™ํ•œ ์Šคํฌ๋กค ๊ธธ์ด,์ฆ‰ย DOM.scrollLeftย ๊ธธ์ด๋งŒํผ ์Šคํฌ๋กค ์ด๋™

4๏ธโƒฃ mouseEvent.pageXonMouseDown์‹œ x ์ขŒํ‘œ


๐Ÿค”ย ๋ฌธ์ œ์ƒํ™ฉ

๐Ÿ‘‰ ์ง€๋‚˜์น˜๊ฒŒ ๋งŽ์€ ๋ Œ๋”๋ง
๐Ÿ‘‰ padding-left ๋กœ ์ธํ•ด ๋งˆ์ง€๋ง‰์นด๋“œ๊ฐ€ ์งค๋ ค ๋ณด์ด๋Š” ๋ฌธ์ œ


๐Ÿ”ฅย ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์ง€๋‚˜์น˜๊ฒŒ ๋งŽ์€ ์ด๋ฒคํŠธ ๐Ÿ‘‰ Throttle ๋กœ ์ œ์–ด

onScroll ์ด๋ฒคํŠธ์™€ ๊ฐ™์ด onMouseMove ๋„ ์ˆ˜ ๋งŽ์€ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๊ณ ,
์ง€๋‚˜์น˜๊ฒŒ ๋งŽ์€ ์ด๋ฒคํŠธ๋ฅผ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•ด Throttle ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

Throttle ๊ณผ Debounce

Throttle

  • ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ์ผ์ • ์‹œ๊ฐ„ ๋™์•ˆ ํ•œ๋ฒˆ๋งŒ ์‹คํ–‰ ๋˜๋„๋ก ๋งŒ๋“œ๋Š” ๊ฐœ๋…
  • ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ์ ์— ์ผ์ • ์‹œ๊ฐ„ ๋™์•ˆ ๊ธฐ๋‹ค๋ฆฌ๊ณ , ์ด๋ฒคํŠธ ๋๋‚œ ํ›„ ์žฌ์ฐจ ๊ธฐ๋‹ค๋ฆฐ๋‹ค.

Debounce

  • ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ์—์„œ ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰ ์ด๋ฒคํŠธ ๋งŒ์„ ์‹คํ–‰ ํ•˜๋„๋ก ๋งŒ๋“œ๋Š” ๊ฐœ๋…
  • ๋งˆ์ง€๋ง‰ ์ด๋ฒคํŠธ์—์„œ ์ผ์ • ์‹œ๊ฐ„๋™์•ˆ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด, ๋˜ ์ผ์ • ์‹œ๊ฐ„์„ ๊ธฐ๋‹ค๋ฆฐ๋‹ค.

์„ฑ๋Šฅ ์ƒ์œผ๋กœ๋Š” ๋””๋ฐ”์šด์‹ฑ์ด ์œ ๋ฆฌํ•˜์ง€๋งŒ ๋””๋ฐ”์šด์‹ฑ์€ ์•„๋ฌด๋ฆฌ ๋งŽ์€ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ๋ชจ๋‘ ๋ฌด์‹œํ•˜๊ณ  ํŠน์ • ์‹œ๊ฐ„ ์‚ฌ์ด์— ์–ด๋–ค ์ด๋ฒคํŠธ๋„ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•˜์„ ๋•Œ ๋”ฑ ํ•œ๋ฒˆ๋งŒ ๋งˆ์ง€๋ง‰ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๊ธฐ๋ฒ• ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋””๋ฐ”์šด์‹ฑ์„ ์ ์šฉํ•œ ๊ฒฝ์šฐ, ์—ฌ๋Ÿฌ ์ด๋ฒคํŠธ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์Šค์™€์ดํ”„ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

์Šค๋กœํ‹€์€ ์ ์–ด๋„ X ๋ฐ€๋ฆฌ ์ดˆ๋งˆ๋‹ค ์ •๊ธฐ์ ์œผ๋กœ ๊ธฐ๋Šฅ ์‹คํ–‰์„ ๋ณด์žฅํ•˜๋ฏ€๋กœ ์Šค๋กœํ‹€์„ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
delay๋ฅผ ์ผ๋ฐ˜์ ์ธ 250ms๋กœ ์„ค์ •ํ•˜๋ฉด ๋Š๊ธฐ๋Š” ๋Š๋‚Œ์ด ๋“ค์–ด 100ms๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ฝ”๋“œ ๊ตฌํ˜„

// MarketPrice.js

const MarketPrice = () => {

 const scrollRef = useRef(null);
 const [isDrag, setIsDrag] = useState(false);
 const [startX, setStartX] = useState();


 const onDragStart = (e) => {
    e.preventDefault();
    setIsDrag(true);
    setStartX(e.pageX + scrollRef.current.scrollLeft);
  };

  const onDragEnd = () => {
    setIsDrag(false);
  };

  const onDragMove = (e) => {
    if (isDrag) {
      const { scrollWidth, clientWidth, scrollLeft } = scrollRef.current;
      scrollRef.current.scrollLeft = startX - e.pageX;
      if (scrollLeft === 0) {
        setStartX(e.pageX);
      } else if (scrollWidth <= clientWidth + scrollLeft) {
        setStartX(e.pageX + scrollLeft);
      }
    }
  };

// ๐Ÿ‘‡ ์—ฌ๊ธฐ์— ์Šค๋กœํ‹€ ์ถ”๊ฐ€!
const throttle = (func, ms) => {
    let throttled = false;
    return (...args) => {
      if (!throttled) {
        throttled = true;
        setTimeout(() => {
          func(...args);
          throttled = false;
        }, ms);
      }
    };
  };

  const delay = 100;
  const onThrottleDragMove = throttle(onDragMove, delay);

 return (
    ...
    <MyCropsChartWrap
       onMouseDown={onDragStart}
       onMouseUp={onDragEnd}
       onMouseMove={isDrag ? onThrottleDragMove : null}
       onMouseLeave={onDragEnd}
       ref={scrollRef}
     >
        <MyCropsMarketPriceCard checkedInputs={checkedInputs} />
     </MyCropsChartWrap>
     ...
 )
}

๐Ÿ‘‰ padding-left ๋กœ ์ธํ•ด ๋งˆ์ง€๋ง‰์นด๋“œ๊ฐ€ ์งค๋ ค ๋ณด์ด๋Š” ๋ฌธ์ œ

์Šค์™€์ดํ”„๋˜๋Š” ๋””์ž์ธ์ƒ ์นด๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ์‹ธ๋Š” div์— padding-left ๋ฅผ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋žฌ๋”๋‹ˆ padding-left ๊ธธ์ด๋งŒํผ ์งค๋ ค๋ณด์ด๋Š” ๋งˆ์ง€๋ง‰์นด๋“œ

clientWidth ๋ฅผ ๊ธฐ์ค€์œผ๋กœ scrollํ•œ ๊ธธ์ด ๋งŒํผ๋งŒ ์Šค์™€์ดํ”„๋˜๊ธฐ ๋•Œ๋ฌธ์— ์นด๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ์‹ธ๋Š” div์— padding-left ๊ธธ์ด๋งŒํผ ๋งˆ์ง€๋ง‰ ์นด๋“œ๊ฐ€ ์ž˜๋ ค๋ณด์ด๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์นด๋“œ ๋ฆฌ์ŠคํŠธ ๋ฐฐ์—ด ๋งˆ์ง€๋ง‰์— div๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ์›ํ•˜๋Š” ๊ธธ์ด๋งŒํผ ์Šค์™€์ดํ”„๋˜๋„๋ก ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ฝ”๋“œ ๊ตฌํ˜„

// MyCropsMarketPriceCard.js
const MyCropsMarketPriceCard = () => {

// 1๏ธโƒฃ ๊ธฐ์กด ๋ฐ์ดํ„ฐ ํ˜•์‹์— ๋นˆ๊ฐ’์„ ๋‹ด์€ ๊ฐ์ฒด๋ฅผ ๋ฐฐ์—ด์— ์ถ”๊ฐ€
const data = { id: 0, name: "", category: "", type: "" };
const usersNewCropList = [];
 userInfo !== undefined &&
   userInfo?.crops.map((list) => {
     return usersNewCropList.push(list);
   });
usersNewCropList.push(data);

return (
  usersNewCropList.map((list, index) => {
    return (
      <React.Fragment key={index}>

         // 2๏ธโƒฃ ์‚ผํ•ญ์—ฐ์‚ฐ์ž๋กœ list์˜ ๋งˆ์ง€๋ง‰ index ํˆฌ๋ช…๋ฐ•์Šค ์ ์šฉ
         {usersNewCropList.length !== index + 1 ? (
             <Wrap>
              ...
             </Wrap>
           )
         ) : (
           <EndWrap>
             ...
           </EndWrap>
         )}
       </React.Fragment>
      );
    })
   )
  })
}

๐Ÿ‘‡ย ํ•ด๊ฒฐ์— ๋„์›€์„ ์ค€ ์ž๋ฃŒ

๐Ÿง‘โ€๐Ÿ’ป ๋งˆ์šฐ์Šค ๋“œ๋ž˜๊ทธ๋กœ ์ขŒ์šฐ ์Šคํฌ๋กค ๊ตฌํ˜„
๐Ÿง‘โ€๐Ÿ’ป Debounce ์™€ Throttle ์ฐจ์ด

0๊ฐœ์˜ ๋Œ“๊ธ€