FrontEnd/기능구현

이미지 확대기능 (ZoomImage - React)

3일마다 작심3일 2021. 12. 24. 16:05

이미지 확대기능이란?

말 그대로 이미지에 마우스 커서를 올려놓았을때 해당 부분을 확대해서 보여주는것입니다.

쇼핑몰같은 사이트에서 많이 사용되는 기술입니다.

 

PZoomImage.js

import React from 'react'
import ZoomImage from '../components/ZoomImage'

const PZoomImage = () => {
    return (
        <ZoomImage zoomRate={5} width={350} height={300}/>
    )
}

export default PZoomImage

 

 

  • zoomRate : 몇배만큼 확대할지 넘겨주는 props
  • width, height: 이미지의 크기

 

ZoomImage.js

import React, { useCallback, useRef, useState } from "react";
import styled, { css } from "styled-components";
import testImage from "../images/test_Image.png";

const Container = styled.div`
  position: absolute;
  display: flex;
  justify-content: center;
  align-items: center;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
`;

const Target = styled.div`
  justify-content: center;
  align-items: center;
  width: 250px;
  height: 150px;
  margin: 0 auto;
  border: 1px solid black;
`;

const Image = styled.img`
  width: 100%;
  height: 100%;
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  transition: transform 0.5s ease-out;
  overflow: hidden;

  ${({ isScale, origin }) =>
    isScale &&
    css`
      transform: scale(1);
      transform-origin: ${origin};
    `}
`;

const ImageZoom = styled.div`
  position: absolute;
  ${({ width, height }) =>
    css`
      width: ${width}px;
      height: ${height}px;
    `}
  margin-top: 30px;
  top: 0%;
  display: inline-block;
  transition: transform 0.5s ease-out;
  background-image: url(${testImage});
`;

const Cursor = styled.div`
  position: absolute;
  background: white;
  opacity: 0.3;
  ${({ cursor, cursorSize, width, height }) =>
    cursor &&
    css`
      width: ${width / cursorSize}px;
      height: ${height / cursorSize}px;
      left: calc(${cursor.cursorX}px - ${width / cursorSize / 2}px);
      top: calc(${cursor.cursorY}px - ${height / cursorSize / 2}px);
    `}
  z-index: 999;
`;

const ZoomImage = ({ zoomRate, width, height }) => {
  const [cursor, setCursor] = useState({
    cursorX: 0,
    cursorY: 0,
  });
  const imageRef = useRef(); // 원본 이미지
  const imageZoomRef = useRef(); // 확대될 이미지
  const cursorRef = useRef(); // 마우스커서 혹은 확대할곳

  const onMouseMove = useCallback((e, zoomRate) => { // mouse가 움직일때마다 실행 될 함수
    imageZoomRef.current.style.backgroundSize = `${ // 백그라운드 사이즈를 설정함으로써 이미ㅣ지를 확대 할 수 있다.
      imageRef.current.offsetWidth * zoomRate   
    }px ${imageRef.current.offsetHeight * zoomRate}px`; // 실제이미지의 넓이와 높이 확대될 비율을 곱한다.

    const rect = e.target.getBoundingClientRect(); // 원본 이미지의 위치를 알아냅니다.

    const getCursorPos = (e) => { // 커서의 좌표를 구하는 함수
      let x = 0,
        y = 0;
      x = e.pageX - rect.left; 
      y = e.pageY - rect.top; // 뷰포트기준 원본 이미지 안의 마우스의 위치 - 페이지 전체 위치에서 원본 이미지가 떨어져있는 위치값

      return { x, y };
    };

    const pos = getCursorPos(e);

    let x = pos.x - cursorRef.current.offsetWidth / 2;
    let y = pos.y - cursorRef.current.offsetHeight / 2;

    imageZoomRef.current.style.backgroundPosition = `-${x * zoomRate}px -${ // 복사된 이미지의 어디가 확대 될 곳인지 position을 정해준다.
      y * zoomRate
    }px`;

    setCursor({
      cursorX: e.pageX,
      cursorY: e.pageY,
    });
  }, []);

  return (
    <Container>
      <Target>
        <Cursor width={width} height={height} ref={cursorRef} cursor={cursor} cursorSize={zoomRate} />
        <Image
          ref={imageRef}
          src={testImage}
          onMouseMove={(e) => onMouseMove(e, zoomRate)}
        />
      </Target>
      <ImageZoom ref={imageZoomRef} width={width} height={height} />
    </Container>
  );
};

export default ZoomImage;

우선 커서의 위치를 잡아주기위해 cursor를 상태로 관리해줍니다.

 

ImageRef: 원본이미지에 대한 정보를 갖고있습니다.

ImageZoomRef: 확대될 이미지에 대한 정보를 갖고있습니다.

cursorRef: 커서에 대한 정보를 갖고있습니다.

 

Cursor에는 width, height, cursor, cursorSize에 대한 정보를 styledComponent를 이용하여

확대되는 비율에 따라 커서를 따라 움직이는 영역의 크기를 정해줍니다.

 

const Cursor = styled.div`
  position: absolute;
  background: white;
  opacity: 0.3;
  ${({ cursor, cursorSize, width, height }) =>
    cursor &&
    css`
      width: ${width / cursorSize}px; 
      // cursorSize는 zoomRate입니다.
      height: ${height / cursorSize}px; 
      // 확대되는 크기가 커질수록 확대되는 영역이 작아지기때문에 분모의 크기를 줄이는 방법을 선택했습니다.
      left: calc(${cursor.cursorX}px - ${width / cursorSize / 2}px); 
      top: calc(${cursor.cursorY}px - ${height / cursorSize / 2}px);
       // 커서에 따라 중앙에 위치해야하기 때문에 크기를 받아 2로 나누면 중앙으로 위치하게 할 수 있습니다,
    `}
  z-index: 999;
`;

이제 커서에 대한 처리는 끝났고 이미지를 확대시켜야하기 때문에 div속성에 있는 onMouseMove의 event를 이용하여 코드를 구성해봅시다.

 

onMouseMove함수

  const onMouseMove = useCallback((e, zoomRate) => { // mouse가 움직일때마다 실행 될 함수
    imageZoomRef.current.style.backgroundSize = `${ // 백그라운드 사이즈를 설정함으로써 이미지를 확대 할 수 있다.
      imageRef.current.offsetWidth * zoomRate   
    }px ${imageRef.current.offsetHeight * zoomRate}px`; // 실제이미지의 넓이와 높이 확대될 비율을 곱한다.

    const rect = e.target.getBoundingClientRect(); // 원본 이미지의 위치를 알아냅니다.

    const getCursorPos = (e) => { // 커서의 좌표를 구하는 함수
      let x = 0,
        y = 0;
      x = e.pageX - rect.left; 
      y = e.pageY - rect.top; //원본 이미지 기준 마우스의 위치 - 페이지 전체 위치에서 원본 이미지가 떨어져있는 위치값

      return { x, y };
    };

    const pos = getCursorPos(e);

    let x = pos.x - cursorRef.current.offsetWidth / 2;
    let y = pos.y - cursorRef.current.offsetHeight / 2;

    imageZoomRef.current.style.backgroundPosition = `-${x * zoomRate}px -${ // 복사된 이미지의 어디가 확대 될 곳인지 position을 정해준다.
      y * zoomRate
    }px`;

    setCursor({
      cursorX: e.pageX,
      cursorY: e.pageY,
    });
  }, []);

우선 이미지를 확대할 방법을 찾아야하는데 css 속성중에 backgroundSize를 조절하는 프로퍼티가 있습니다.

해당 프로퍼티를 이용하여 확대, 축소를 할 수 있습니다.

계산식으로는

 

원본 이미지의 넓이 * 확대될 비율 px 원본 이미지 높이 * 확대될 비율

로 설정하면 이미지를 확대할 수 있습니다.

 

이제 확대는 잘 되지만 마우스 커서와 상관없이 확대를 하고 있습니다. 우리가 원하는건 마우스 커서가 가리키는 영역에 대한 확대이니

커서의 위치에 따라 이미지를 이동하게끔 만들면 됩니다.

이 부분은 backgroundPosition이라는 css 프로퍼티를 이용하였습니다.

 

우선 뷰포트 기준 원본 이미지의 위치를 구해야합니다.

다행이 이벤트에서 getBoundingClinetRect()메서드를 지원해주고 있어 쉽게 알아낼 수 있습니다.

 

해당정보와 뷰포트 기준 원본 이미지 내부에 있는 마우스커서의 위치를 알려주는 e.pageX, Y 프로퍼티와 rect에 담겨져있는 떨어져있는 거리인 rect.left, rect.top 을 이용해 마우스 커서의 좌표를 구합니다.

 

이제 데이터는 충분하니 연산을 하여 커서 위치를 구하고 backgroundPosition에 적용시켜줍니다.

 

imageZoomRef.current.style.backgroundPosition = `-${x * zoomRate}px -${ // 복사된 이미지의 어디가 확대 될 곳인지 position을 정해준다.
      y * zoomRate
    }px`;

 

끝! 

 

생각보다 css에 대한 이해와 수학적인? 지식이 필요했어서 꽤나 번거롭고 이해하기도 어려웠으나 그만큼 배운것도 많았던 기능입니다.

 

 

출처 

- https://velog.io/@leitmotif/Image-zoom-in-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-2

 

Image zoom in 구현하기 - 2

커서를 이미지에 올렸을 때 확대되는 것을 만들어보았습니다.

velog.io