Skip to content

Rosevilleage/my-portfolio

Repository files navigation

jang chanhee Portfolio

next js와 tailwind를 사용해 제작한 mac os 컨셉의 포트폴리오 입니다. 각 프로젝트에 관한 내용을 browser 처럼 열어서 확인 가능하며, 마우스를 통해 움직이거나 크기를 조정하는 동작이 가능합니다.


개발 기간

2023.07.28~2023.08.14


📚 STACKS




🔗 Deploy

Vercel을 통한 배포 ▶︎ 바로 가기



🔧 주요 기능

drag

state를 통해 위치와 크기값이 동적으로 정해지는 전체적인 뼈대가 될 div와 mouse event를 통해 left와 top 값을 변화시킬 div를 만들어 구현했습니다. 기능 구현은 bepyan님 블로그를 참고하였습니다.
function Browser() {
  const [browserConfig, setBrowserConfig] = useState({
    x: 0,
    y: 0,
    w: 0,
    h: 0,
  });

  return (
    <div className="browser-container">
      <div
        className="browser-mover browser-topbar"
        {...dragMouseDown((intercalX, intervalY) => {
          setBrowserConfig({
            x: x + intervalX,
            t: y + intervalY,
            w,
            h,
          });
        })}
      ></div>
    </div>
  );
}

onMouseDown 객체를 반환하는 utill 함수를 통해 mouseMove의 시작 위치과 실시간 위치를 빼어 움직인 거리를 인자로 받는 콜백함수에 전달하여 원하는 동작이 가능하도록 구현했습니다.

function dragMouseDown(
  onDrag: (intervalX: number, intervalY: number) => void,
  stopPropagation?: boolean
) {
  if (stopPropagation) e.stopPropagation();

  const mouseMoveHandler = (mouseE: MouseEvent) => {
    const intervalX = mouseE.screenX - e.screenX;
    const intervalY = mouseE.screenY - e.screenY;
    onDrag(intervalX, intervalY);
  };

  const mouseUpHandler = () => {
    document.removeEventListener("mousemove", mouseMoveHandler);
  };

  document.addEventListener("mousemove", mouseMoveHandler);
  document.addEventListener("mouseup", mouseUpHandler, { once: true });
}

resize

resize 기능은 state를 변경시키는 투명한 position absolute의 div들을 각 모서리와 테두리에 위치시켜 구현했습니다.

resize의 경우 x,y 값이 변화하는 만큼 w,h을 증가시키거나 감소시켜 반대편 테두리의 위치를 고정했습니다.

function Browser() {
  const [browserConfig, setBrowserConfig] = useState({
    x: 0,
    y: 0,
    w: 0,
    h: 0,
  });

  const inrange = (n: number, min: number, max: number) => {
    if (n < min) return min;
    if (n > max) return max;
    return n;
  };

  return (
    <div className="browser-container">
      <div
        className="browser-resizer n"
        {...dragMouseDown((intercalX, intervalY) => {
          setBrowserConfig({
            x,
            y: inrange(y + interval.y, 0, y + h - MIN_H),
            w,
            h: inrange(h - interval.y, MIN_H, y + h),
          });
        })}
      ></div> {/* x8 (n,s,w,e,nw,ne,sw,se)*/}
    </div>
  );
}

image slide

이미지가 첨부된 contents에 반응형 slide를 구현했습니다. browser의 크기에 따라 slider의 너비와 높이가 변화하고, drag, click 을 통해 이미지를 넘길 수 있습니다. resize observer를 통해 상위 컴포넌트의 크기를 측정하고, custom hook을 통해 mouse event, click event를 정의하여서 동작하도록 구현했습니다.

export default function CarouserlSlide({ images, boundary }: CarouselProps) {
  const [size, setSize] = useState({ w: 500, h: 308 });

  const slideList = [images.at(-1), ...images, images.at(0)] as string[];

  const {
    transX,
    animation,
    currentIndex,
    dotHandler,
    nextChangeHandler,
    prevChangeHandler,
    onCarouselDrag,
    onTransitionEnd,
  } = useCarousel(size.w, slideList.length);

  const observer = new ResizeObserver((entries) => {
    const ent = entries[0];
    const { width, height } = ent.contentRect;

    setSize({
      w: width,
      h: height,
    });
  });

  useLayoutEffect(() => {
    if (boundary) {
      observer.observe(boundary);
    }
  }, [boundary]);

  return (
    <>
      <div>
        <div
          style={{
            transform: `translateX(${-currentIndex * size.w + transX}px)`,
            transition: `transform ${animation ? 300 : 0}ms ease-in-out 0s`,
          }}
          onTransitionEnd={onTransitionEnd}
          {...onCarouselDrag()}
        >
          {slideList.map((img, i) => (
            <Image
              key={i + img}
              src={img}
              alt={`image${i}`}
              width={size.w}
              height={size.h}
              priority
              draggable={false}
            />
          ))}
        </div>
        <CarouselButton
          buttonMaterials={{ nextChangeHandler, prevChangeHandler }}
        />
        <CarouselDot dotMaterials={{ images, dotHandler, currentIndex }} />
      </div>
    </>
  );
}