Zoom 이미지 확대 기능 구현 (마우스 움직임)

CHAENG·2024년 7월 25일
0

FrontEnd

목록 보기
8/10

업로드한 이미지에 마우스를 갖다대면, 해당 부분을 확대해서 보여주는 기능을 구현하고자 했다.

기능 구현은 완료했지만 깔끔하게 완성하지는 못한 것 같아서 일단 해당 글을 정리하고 부족한 부분을 추가로 수정해갈 예정이다.

---> 해당 기능은 사용 안하게 되었지만.. 그래도 끝까지 완성했다는 것에 의의를 두기로 했다!

참고링크

https://velog.io/@eenaree/react-image-zoom-in

https://prod.velog.io/@oneook/Image-Zoom-on-Hover-바닐라-자바스크립트로-만들기

https://felog.tistory.com/8

https://lts0606.tistory.com/514


구현 할 기능

  • 마우스 움직임으로 원하는 곳을 확대해서 볼 수 있는 기능

구현 조건

  • 이미지에 마우스를 갖다대면, 확대할 영역을 설정할 박스 → Scanner
    • 해당 박스(=Scanner)는 이미지 안에서만 움직일 수 있음
  • 이미지위에서 마우스를 움직일 때, 마우스 커서는 항상 Scanner의 가운데에 위치해야 함
  • Scanner가 가르키는 이미지 영역을 확대해서 보여주는 영역 → View
    • Scanner가 움직일 때 마다 보여지는 이미지가 달라져야 함

✅ MouseEvent (마우스 움직임 감지)

  • MouseEnter와 MouseOver는 이미지에 마우스를 처음 갖다댄 순간만 실행된다.
  • 따라서 이미지 위에서 움직이는 마우스 움직임을 감지하기 위해서는
    • 마우스가 움직일때 마다 실행되는 MouseMove 이벤트를 활용해야한다.
const onMouseMove = (e) => {
		// 이벤트 처리 내용 작성
}

...

<div onMouseMove={onMouseMove}>
      <img
        src={imagePreview}
        alt="image"
        onLoad={handleImageLoad}
        ref={imageRef}
      />
      
...
  
</div>  

MouseMove 이벤트는 img요소에 부착하면 안된다.
Scannerimg요소와 동일한 자식요소로 추가해야 이벤트 버블링이 발생해, 박스를 커서 아래에 두고 움직일 수 있기 때문이다. → 따라서 부모요소 div 에 이벤트 핸들러를 작성한다.


✅ Scanner 생성

  • Scanner는 위치가 고정되어 있지 않고, 이미지 위에서 마우스 커서가 움직일 때마다 위치가 변경되어야 한다.
  • 따라서 동적으로 위치를 설정해야한다.
  • position absolute 속성을 적용하여 top, left 값에 offset 값을 적용하여 동적으로 위치를 설정한다.
    • relative는 페이지 상단이 아닌, Scanner의 부모요소에 지정한다.
const [scannerPosition, setScannerPosition] = useState({
    left: 0,
    top: 0,
}); // 마우스커서 위치에 따라 변경되는 Scanner Offset 값

...

<div 
  className="relative flex justify-center items-center z-0 w-10/12 h-480 min-w-640"
	onMouseMove={onMouseMove}
>
      <img
        src={imagePreview}
        alt="image"
        onLoad={handleImageLoad}
        ref={imageRef}
      />
      
      // Scanner
      <span
				className="absolute w-256 h-256 z-0 "
				style={{
					top: `${scannerPosition.top}px`,
					left: `${scannerPosition.left}px`,
				}}
			/>
</div>  

✅ Scanner 범위 설정 (이미지 안에서만 움직이는 Scanner)

  • 이미지 안에서 마우스를 움직였을 때, Scanner의 모서리가 닿아야 하는 부분을 고려해야한다.

💡 이미지 안에서만 Scanner가 존재하기 위한 조건

- Scanner의 왼쪽 위 모서리 좌표

  • 왼쪽에서부터 최대 (scannerWidth / 2)px
  • 위에서부터 최대 (ScannerHeight / 2)px

- 이 외의 값을 갖게 되면 이미지 요소를 벗어나게 된다.

// 입력 이미지 크기
const imageWidth = imageRect.width;
const imageHeight = imageRect.height;
// Scanner 크기
const scannerWidth = 256;
const scannerHeight = 256;

// 마우스 커서 Scanner 가운데에 위치하도록 설정
const scannerPosLeft = e.clientX - scannerWidth / 2 - imageRect.x;
const scannerPosTop = e.clientY - scannerHeight / 2 - imageRect.y;

**// 이미지 안에서만 움직일 수 있는 Scanner 범위 구하기**
const allowedPosLeft =
  scannerPosLeft >= 0 && scannerPosLeft <= imageWidth - scannerWidth;
const allowedPosTop =
  scannerPosTop >= 0 && scannerPosTop <= imageHeight - scannerHeight;

✅ 마우스 커서 Scanner 가운데에 위치하기

  • Scanner의 left, top 값을 활용하여 구현할 수 있다.
  • 현재 커서의 위치에서 Scanner 크기의 절반만큼 뺀 값으로 Scanner의 left, top 값을 설정한다.
const scannerPosLeft = e.clientX - scannerWidth / 2;
const scannerPosTop = e.clientY - scannerHeight / 2;

  • 하지만 Scanner는 div(=상위요소) 기준으로 위치하고 있기 때문에 이미지 요소의 좌표를 추가로 활용해야 한다.
  • element.getBoundingClientRect 메소드를 사용하여 해당 요소의 크기현재 브라우저 창 기준 좌표를 확인한다.
  • 해당 값을 활용해서 따라서 현재 요소의 좌표값을 추가로 빼서 마우스 커서를 Scanner 가운데에 위치하게 할 수 있다.
const scannerPosLeft = e.clientX - scannerWidth / 2 - imageRect.x;
const scannerPosTop = e.clientY - scannerHeight / 2 - imageRect.y;

✅ element.getBoundingClientRect

  • 객체 크기, 위치 정보를 포함하고 있다.
  • 이 메소드는 요소에 적용해야하기 때문에, DOM 요소에 접근할 수 있는 참조값이 필요하다.
    • useRef를 활용하여 요소의 참조를 갖고오는 방식을 활용할 수 있다.
const imageRef = useRef(null); // 입력한 이미지 정보
const [imageRect, setImageRect] = useState(null); // 입력된 이미지 객체 정보

// 이미지 요소의 위치와 크기 정보를 가져오는 함수
const handleImageLoad = () => {
  const imageRectVal = imageRef.current.getBoundingClientRect(); // 요소의 상대적 위치와 크기 정보가 담긴 객체반환
  setImageRect(imageRectVal);
};

...

<img
  src={imagePreview}
  alt="image"
  onLoad={handleImageLoad}
  ref={imageRef}
/>

✅ Scanner 조건 추가

  • Scanner가 이미지 일부 요소에 걸쳐있고, 밖으로 튀어나와있는 경우

    • Scanner가 항상 이미지 요소내에 존재해야 한다는 조건이 깨지게 됨
    • 중첩 if문을 활용하여 가장 가까운 모서리에 붙도록 설정해서 조건을 성립시킨다.
  • imageRect의 값이 첫 렌더링시에는 null값을 갖고있기 때문에 추가적으로 예외처리를 진행한다.

// 마우스가 움직일 때 마다 실행되는 이벤트함수
  const onMouseMove = (e) => {
    // 입력된 이미지 객체 정보가 존재하지 않는다면 함수 종료
    if (!imageRect) {
      return;
    }

    // 입력 이미지 크기
    const imageWidth = imageRect.width;
    const imageHeight = imageRect.height;
    // Scanner 크기
    const scannerWidth = 256;
    const scannerHeight = 256;

    // 마우스 커서 Scanner 가운데에 위치하도록 설정
    const scannerPosLeft = e.clientX - scannerWidth / 2 - imageRect.x;
    const scannerPosTop = e.clientY - scannerHeight / 2 - imageRect.y;

    // 이미지 안에서만 움직일 수 있는 Scanner 범위 구하기
    const allowedPosLeft =
      scannerPosLeft >= 0 && scannerPosLeft <= imageWidth - scannerWidth;
    const allowedPosTop =
      scannerPosTop >= 0 && scannerPosTop <= imageHeight - scannerHeight;
      
    // 현재 scanner offset값 초기화
    const scannerPosition = { left: 0, top: 0 };
    
    // 이미지 보더라인 안에서만 움직일 수 있도록 제한
    if (allowedPosLeft) {
      scannerPosition.left = scannerPosLeft;
    } else {
      if (scannerPosLeft < 0) {
        scannerPosition.left = 0;
      } else {
        scannerPosition.left = imageRect.width - scannerWidth;
      }
    }
    if (allowedPosTop) {
      scannerPosition.top = scannerPosTop;
    } else {
      if (scannerPosTop < 0) {
        scannerPosition.top = 0;
      } else {
        scannerPosition.top = imageRect.height - scannerHeight;
      }
    }

    // Scanner offset 설정
    setScannerPosition({
      left: scannerPosition.left,
      top: scannerPosition.top,
    });
  };

✅ View 생성하기

 const onMouseMove = (e) => {
	 ...
	 
	 // View offset 설정 -> 2배 확대
  setViewPotision({
    left: scannerPosition.left * -2,
    top: scannerPosition.top * -2,
  });
 }
 
 **...**

{imageRect && viewPosition && (
  <div
    className="absolute bg-no-repeat object-scale-down bg-contain z-10 border-4 border-solid border-border-gray"
    style={{
      width: `${imageRect.width}px`,
      height: `${imageRect.height}px`,
      top: `${scannerPosition.top}px`,
      left: `${scannerPosition.left}px`,
      backgroundImage: `url(${imagePreview})`,
      backgroundPosition: `${viewPosition.left}px ${viewPosition.top}px`,
      backgroundSize: "200% 200%",
    }}
  />
)}
  • img요소가 아닌, background이미지로 적용한다.
    • backgroundImage : 표시할 이미지
    • backgroundPosition : 확대할 이미지의 position
    • backgroundSize : 이미지 확대비율
  • Scanner와 동일하게 동적으로 이미지를 움직여야한다.
    • viewPosition의 상태값이 추가적으로 필요
  • Scanner Position과 다르게 -2 값을 곱해서 설정해야한다. (2배 줌 기준)
    • view의 크기는 이미지 요소의 크기와 동일하지만, 2배 확대된 이미지를 보여줘야한다.
      • 위치 이동을 Scanner와 동일하게 하면 이미지는 중간까지밖에 가지 못하기 때문
    • 따라서 확대한 비율만큼 곱한 값으로 이동한다.
  • 의도한대로 결과값을 얻기 위해서는 음수값을 곱해야한다.
    • backgroundSize 속성값을 이미지 크기의 2배로 확대했기 때문에 요소의 크기가 이미지 크기보다 작은상태로 전체 이미지가 부모 요소에 일부 가려져 있다.
    • 따라서 음수값을 background-position에 동적으로 전달하여 가려지는 이미지가 없도록 설정한다.
    • 양수값을 전달하게 되면, 요소로부터 왼쪽위에서 백그라운드 이미지가 멀어지기 때문

✅ MouseLeave

  • 마우스가 이미지 요소에서 벗어날 경우 Scanner와 View가 사라져야한다.
    • 따라서 조건을 추가하여 position 값을 null로 설정해준다.
// 마우스가 이벤트가 사라지면 Scanner, View도 사라지게 설정
const onMouseLeave = () => {
  if (imageRect) {
    setScannerPosition(null);
    setViewPotision(null);
  }
};

...

<div
  className="relative flex justify-center items-center z-0 w-10/12 h-480 min-w-640"
  onMouseMove={onMouseMove}
  onMouseLeave={onMouseLeave}
>

...

일단 윗글 내용을 바탕으로 이미지 확대기능을 구현했다.

하지만 요소 레이아웃 설계를 잘못한 것도 있고, 이에 맞춰서 개발을 하다보니 이미지 요소의 크기에 따라 zoom 이미지가 올바르게 되지않는 경우가 존재한다.

따라서 레이아웃 설계부터 다시해서 오류를 수정해 나가볼 예정이다.

→ 레이아웃을 다시 설계해서 특정 부분은 확대가 안됐던 문제 해결함.

profile
FrontEnd Developer.

0개의 댓글

관련 채용 정보