[React/CSS] instagram ์Šคํ† ๋ฆฌ์ฒ˜๋Ÿผ ๋“œ๋ž˜๊ทธ๋กœ ์š”์†Œ ๋„˜๊ธฐ๊ธฐ

๐Ÿค” ์„œ๋ก 

ํ˜„์žฌ ๋‚˜๋งŒ๋ฌด ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ ์ค‘์ธ๋ฐ, ์ตœ๊ทผ ๊ฒŒ์‹œ๋ฌผ์„ ์–ด๋–ป๊ฒŒ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์„๊นŒ ๊ณ ๋ฏผ์„ ํ•˜๋‹ค๊ฐ€ ์ธ์Šคํƒ€๊ทธ๋žจ์˜ ์Šคํ† ๋ฆฌ ๊ธฐ๋Šฅ์ด ์ƒ๊ฐ๋‚ฌ๊ณ  ํ•ด๋‹น ๋ถ€๋ถ„์˜ ์ธํ„ฐ๋ž™์…˜์„ ์ฐจ์šฉํ•˜๋ ค ํ•œ๋‹ค.

๐Ÿฅ… ๋ชฉํ‘œ


์ธ์Šคํƒ€๊ทธ๋žจ ์ƒ๋‹จ์—๋Š” ์ตœ๊ทผ ์Šคํ† ๋ฆฌ๋ฅผ ์˜ฌ๋ฆฐ ์‚ฌ๋žŒ์˜ ํ”„๋กœํ•„์ด ๋œฌ๋‹ค. ํ„ฐ์น˜ํ•ด์„œ ์ขŒ์šฐ๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์›€์ง์ผ ์ˆ˜ ์žˆ๋‹ค.(๋ชจ๋ฐ”์ผ ๊ธฐ์ค€) ํ•ด๋‹น ์ธํ„ฐ๋ž™์…˜์ฒ˜๋Ÿผ ๋“œ๋ž˜๊ทธ๋กœ ํ”„๋กœํ•„์„ ์›€์ง์ด๋Š” ๊ฒƒ์„ ๊ตฌํ˜„ํ•˜๋ ค ํ–ˆ๋‹ค.

๐Ÿ‘ฉ๐Ÿป‍๐Ÿ’ป ๊ตฌํ˜„

์ „์ฒด ์ฝ”๋“œ

const getFriends = async () => {
  ...
  const [isMouseDown, setIsMouseDown] = useState<boolean>(false);
  const [startX, setStartX] = useState<number>(0);
  const [scrollLeft, setScrollLeft] = useState<number>(0);
  ...
  
  const handleMouseDown = (
    e: React.MouseEvent<HTMLUListElement, MouseEvent>
  ) => {
    setIsMouseDown(true);
    setStartX(e.clientX - (listRef.current?.offsetLeft || 0));
    setScrollLeft(listRef.current?.scrollLeft || 0);
  };

  const handleMouseLeave = () => {
    setIsMouseDown(false);
  };

  const handleMouseMove = (
    e: React.MouseEvent<HTMLUListElement, MouseEvent>
  ) => {
    if (!isMouseDown) return;
    e.preventDefault();
    const x = e.clientX - (listRef.current?.offsetLeft || 0);
    const walk = x - startX; // ์—ฌ๊ธฐ์„œ walk ๊ฐ’์œผ๋กœ ์Šคํฌ๋กค ์–‘์„ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค
    if (listRef.current) {
      listRef.current.scrollLeft = scrollLeft - walk;
    }
  };

  ...

  return (
    <div>
      <p className="text-3xl font-semibold">์นœ๊ตฌ</p>
      <ul
        ref={listRef}
        className="flex justify-start py-5 overflow-x-hidden overflow-y-hidden scrolling-touch select-none w-full cursor-grab"
        onMouseDown={handleMouseDown}
        onMouseLeave={handleMouseLeave}
        onMouseUp={handleMouseLeave}
        onMouseMove={handleMouseMove}
      >
        {friends &&
          friends.map((f) => {
            return (
              <li
                key={f.id}
                className="m-3 p-1 flex-shrink-0 bg-gradient-to-tr from-violet-600 to-yellow-300 rounded-full"
              >
                <img
                  src={f.image}
                  className="w-20 h-20 rounded-full transform transition hover:-rotate-6 cursor-pointer"
                  alt="profile"
                />
              </li>
            );
          })}
      </ul>
	 ...
    </div>
  );
}

๋ถ€๊ฐ€ ์„ค๋ช…

  const handleMouseDown = (
    e: React.MouseEvent<HTMLUListElement, MouseEvent>
  ) => {
    setIsMouseDown(true);
    setStartX(e.clientX - (listRef.current?.offsetLeft || 0));
    setScrollLeft(listRef.current?.scrollLeft || 0);
  };

  const handleMouseLeave = () => {
    setIsMouseDown(false);
  };

  const handleMouseMove = (
    e: React.MouseEvent<HTMLUListElement, MouseEvent>
  ) => {
    if (!isMouseDown) return;
    e.preventDefault();
    const x = e.clientX - (listRef.current?.offsetLeft || 0);
    const walk = x - startX;
    if (listRef.current) {
      listRef.current.scrollLeft = scrollLeft - walk;
    }
  };
  • handleMouseDown
    ์‚ฌ์šฉ์ž๊ฐ€ ๋งˆ์šฐ์Šค ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ณ  ์žˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜๋กœ, setIsMouseDown(true)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋งˆ์šฐ์Šค๋ฅผ ๋ˆ„๋ฅด๊ณ  ์žˆ์Œ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. e.clientX๋Š” ๋ธŒ๋ผ์šฐ์ € ๋ทฐํฌํŠธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋งˆ์šฐ์Šค ํฌ์ธํ„ฐ์˜ ์ˆ˜ํ‰ ์œ„์น˜(x์ขŒํ‘œ)๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , listRef.current?.offsetLeft๋Š” ul ์š”์†Œ์˜ ์™ผ์ชฝ ๊ฒฝ๊ณ„๊ฐ€ ๋ทฐํฌํŠธ์˜ ์™ผ์ชฝ ๊ฒฝ๊ณ„๋กœ๋ถ€ํ„ฐ ์–ผ๋งˆ๋‚˜ ๋–จ์–ด์ ธ์žˆ๋Š”์ง€(ํ”ฝ์…€ ์ˆ˜)๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • || 0์€ ๊ฐ’์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๋•Œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ค๋ฅ˜๋ฅผ ๋ฐฉ์ง€ํ•œ๋‹ค.

๐Ÿค” ๋ธŒ๋ผ์šฐ์ € ๋ทฐํฌํŠธ(viewport)๋ž€?
์›น ๋ธŒ๋ผ์šฐ์ € ์ฐฝ ๋‚ด์—์„œ ์‹ค์ œ๋กœ ์›น ํŽ˜์ด์ง€์˜ ๋‚ด์šฉ์ด ํ‘œ์‹œ๋˜๋Š” ์˜์—ญ

  • handleMouseMove
    ๋งˆ์šฐ์Šค๊ฐ€ ul ์š”์†Œ ์œ„์—์„œ ์›€์ง์ผ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜์ด๋‹ค. e.preventDefault()๋Š” ๊ธฐ๋ณธ ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ๋ฅผ ๋ง‰์•„ ์‚ฌ์šฉ์ž ์ •์˜ ๋“œ๋ž˜๊ทธ ๋™์ž‘๋งŒ ๋ฐœ์ƒํ•œ๋‹ค. ์ฒ˜์Œ ๋งˆ์šฐ์Šค๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ ์ €์žฅํ•œ ๋งˆ์šฐ์Šค ์œ„์น˜์ธ startX๋กœ๋ถ€ํ„ฐ ๋–จ์–ด์ง„ ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ„์‚ฐํ•ด walk์— ์ €์žฅํ•œ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ listRef.current.scrollLeft๋ฅผ ์—…๋ฐ์ดํŠธํ•˜์—ฌ ul ์š”์†Œ๋ฅผ ์Šคํฌ๋กคํ•œ๋‹ค. scrollleft๋Š” ์ฒ˜์Œ ๋งˆ์šฐ์Šค๋ฅผ ๋ˆŒ๋ €์„ ๋•Œ ์Šคํฌ๋กค์ด ์–ด๋””์— ์žˆ์—ˆ๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๊ณ , walk๋Š” ๋งˆ์šฐ์Šค๋ฅผ ์›€์ง์ธ ๊ฑฐ๋ฆฌ๋ฅผ ๋‚˜ํƒ€๋‚ด์–ด, scrollLeft-walk๋Š” ์ƒˆ๋กœ์šด ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  listRef.current.scrollLeft๋ฅผ ์„ค์ •ํ•˜์—ฌ ์Šคํฌ๋กค์ด ์›€์ง์ด๊ฒŒ ๋œ๋‹ค.

๐Ÿค” Element.scrollLeft
์š”์†Œ์˜ ์ฝ˜ํ…์ธ ๊ฐ€ ์™ผ์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ์—์„œ ์Šคํฌ๋กค๋˜๋Š” ํ”ฝ์…€ ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜ ์„ค์ •ํ•˜๋Š” ์†์„ฑ

๐Ÿค” ์™œ ๋งˆ์ง€๋ง‰ ์ค„์ด scrollLeft + walk์ด ์•„๋‹ˆ๋ผ scrollLeft - walk์ธ๊ฐ€?
scrollLeft๋Š” ์š”์†Œ์˜ ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ ์™ผ์ชฝ ๋์—์„œ ์‹œ์ž‘ํ•˜์—ฌ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๊ฐˆ์ˆ˜๋ก ์ฆ๊ฐ€ํ•œ๋‹ค. walk์€ ๋งˆ์šฐ์Šค๋ฅผ ๋ˆ„๋ฅธ ์œ„์น˜์™€ ํ˜„์žฌ ๋งˆ์šฐ์Šค์˜ ์œ„์น˜ ์‚ฌ์ด์˜ ์ฐจ์ด๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค. ๋งŒ์•ฝ ์‚ฌ์šฉ์ž๊ฐ€ ๋งˆ์šฐ์Šค๋ฅผ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๋“œ๋ž˜๊ทธํ•˜๋ฉด walk๋Š” ์–‘์ˆ˜๊ฐ€ ๋œ๋‹ค. ํ•˜์ง€๋งŒ ๋งˆ์šฐ์Šค๋ฅผ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๋“œ๋ž˜๊ทธํ•˜๋ฉด ์Šคํฌ๋กค์ด ์™ผ์ชฝ์œผ๋กœ ์ด๋™ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— scrollLeft๊ฐ’์„ ๊ฐ์†Œ์‹œ์ผœ์•ผ ํ•œ๋‹ค. ์ฆ‰, ์Šคํฌ๋กค ๋ฐฉํ–ฅ๊ณผ ๋งˆ์šฐ์Šค ๋“œ๋ž˜๊ทธ ๋ฐฉํ–ฅ์ด ๋ฐ˜๋Œ€์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

<ul
  ref={listRef}
  className="flex justify-start py-5 overflow-x-hidden overflow-y-hidden scrolling-touch w-full cursor-grab"
  onMouseDown={handleMouseDown}
  onMouseLeave={handleMouseLeave}
  onMouseUp={handleMouseLeave}
  onMouseMove={handleMouseMove}
>
  • w-full๋กœ ๊ฐ€๋กœ ๊ธธ์ด๋ฅผ ์ •ํ•˜๊ณ  overflow-x-hidden์œผ๋กœ ์ •ํ•ด์ง„ ๊ฐ€๋กœ ๊ธธ์ด๋ณด๋‹ค ๋‚ด์šฉ์ด ๋„˜์น˜๋ฉด ์ˆจ๊น๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์š”์†Œ์˜ ๊ฒฝ๊ณ„ ์•ˆ์— ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ์— ์ปค์„œ ๋ชจ์–‘์„ grab์œผ๋กœ ์„ค์ •ํ•œ๋‹ค.
  • onMouseDown, onMouseLeave, onMouseUp, onMouseMove๋Š” ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ์ด๋‹ค.
<img
  src={f.image}
  className="w-20 h-20 rounded-full transform transition hover:-rotate-6 cursor-pointer"
  alt="profile"
/>
  • hover:-rotate-6์œผ๋กœ ์ด๋ฏธ์ง€์— hover(์ปค์„œ๋ฅผ ๊ฐ€์ ธ๋‹ค ๋Œ”์„ ๋•Œ)ํ–ˆ์„ ๋•Œ ์ด๋ฏธ์ง€๊ฐ€ ํšŒ์ „ํ•œ๋‹ค.
  • cursor-pointer๋ฅผ ์„ค์ •ํ•˜์—ฌ img์˜ ๊ฒฝ๊ณ„ ์•ˆ์— ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ ์ปค์„œ ๋ชจ์–‘์ด pointer๊ฐ€ ๋œ๋‹ค.

โœจ ๊ฒฐ๊ณผ

์ฐธ๊ณ ์ž๋ฃŒ