이미지 확대기능이란?
말 그대로 이미지에 마우스 커서를 올려놓았을때 해당 부분을 확대해서 보여주는것입니다.
쇼핑몰같은 사이트에서 많이 사용되는 기술입니다.
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
'FrontEnd > 기능구현' 카테고리의 다른 글
공공 데이터 포털 open API활용 코로나 확진 수 테이블 만들기 (Covid19 dashboard - React) (0) | 2021.12.29 |
---|---|
소셜 로그인 (SNS login - React) (0) | 2021.12.28 |
댓글 기능 (Comment - React) - feat CRUD (0) | 2021.12.27 |
서치박스 (SearchBox - React) (0) | 2021.12.24 |
무한스크롤 (InfiniteScroll) - 리액트(React) (0) | 2021.12.21 |