[AI Filter] 11. 5주차 화요일
1. 웹캠 레이아웃 수정
원본 > 일반 AI
이 순서로 레이아웃을 수정하고 두가지 기능을 추가했다.
2. 양쪽 비교 & 확대
사실 단순 이미지 확대기능은 그렇게 어렵지 않은데 양쪽을 비교하는 중간을 확대하는 코드는 조금 복잡하게 느껴졌다.
div태그가 두개 겹쳐있는 걸 작은 Div태그로 옮긴다고 생각하며 코드 구현했다.
이때 이미지 objectPosition
기능을 이용해 확대기능을 구현했다
전체코드
import { MouseEvent } from "react";
import { styled } from "@mui/material/styles";
import { useTheme } from "@material-ui/core/styles";
import { Box, IconButton } from "@mui/material";
import { useState, useRef } from "react";
import beforeImage from "../../Assets/Image/beforeImage.png";
import afterImage from "../../Assets/Image/afterImage.png";
import ArrowLeftIcon from "@mui/icons-material/ArrowLeft";
import ArrowRightIcon from "@mui/icons-material/ArrowRight";
const PageDiv = styled("div")({
position: "relative",
height: "100vh",
width: "99.5vw",
background: `url(${afterImage})`,
backgroundSize: "cover",
overflow: "hidden",
});
const Magnify = styled("div")({
width: "300px",
height: "300px",
position: "absolute",
boxShadow: "0 0 0 3px rgba(255, 255, 255, 0.85), 0 0 3px 3px rgba(0, 0, 0, 0.25)",
display: "none",
overflow: "hidden",
zIndex: "2",
});
function HomeBeforeAfterTest() {
const theme = useTheme();
const RATIO = 2;
const magnifyRef = useRef<HTMLDivElement>(null);
const [imgWidth, setImgWidth] = useState<number>(window.innerWidth / 2);
const [objectPosition, setObjectPosition] = useState<string>("-10px 50px");
const buttonStyle = {
position: "absolute",
zIndex: "2",
left: imgWidth,
top: "50%",
transform: "translate(-50%)",
cursor: "move",
};
const beforeTextStyle = {
color: theme.palette.primary.contrastText,
position: "absolute",
top: "50%",
left: window.screen.availWidth / 2 - 100 - 200 * ((window.screen.availWidth - imgWidth) / window.screen.availWidth),
transform: "translate(-50%, -50%)",
};
const AfterTextStyle = {
color: theme.palette.primary.main,
position: "absolute",
top: "50%",
left: window.screen.availWidth / 2 + 100 + 200 * (1 - (window.screen.availWidth - imgWidth) / window.screen.availWidth),
transform: "translate(-50%, -50%)",
};
const CustomTypography = styled("span")({
fontFamily: "Dancing Script",
fontSize: "150px",
});
const handleMove = (event: MouseEvent<HTMLDivElement>) => {
setImgWidth(event.clientX);
setMagnify(event);
};
const handleClick = (event: MouseEvent<HTMLDivElement>) => {
setImgWidth(event.clientX);
};
const setMagnify = (event: MouseEvent<HTMLElement>) => {
let mouseX = event.pageX - event.currentTarget.offsetLeft;
let mouseY = event.pageY - event.currentTarget.offsetTop;
if (!magnifyRef.current?.style) return;
const w = magnifyRef.current.offsetWidth / 2;
const h = magnifyRef.current.offsetHeight / 2;
magnifyRef.current.style.display = "inline-block";
magnifyRef.current.style.left = `${mouseX - w}px`;
magnifyRef.current.style.top = `${mouseY - h}px`;
setObjectPosition(`-${mouseX * RATIO - w}px -${mouseY * RATIO - h}px`);
console.log(objectPosition);
};
const handleMouseLeave = () => {
if (!magnifyRef.current?.style) return;
magnifyRef.current.style.display = "none";
};
return (
<>
<PageDiv onClick={handleClick} onMouseMove={handleMove} onMouseLeave={handleMouseLeave}>
<div
style={{
position: "relative",
zIndex: "1",
width: imgWidth,
height: "100%",
overflow: "hidden",
borderRight: "solid 1px white",
}}
>
<CustomTypography sx={beforeTextStyle}>Before</CustomTypography>
<Box
component="img"
src={beforeImage}
sx={{ width: "99.5vw", height: "100%", objectFit: "cover", objectPosition: "left top" }}
></Box>
</div>
<IconButton sx={buttonStyle}>
<ArrowLeftIcon sx={{ color: "#F2FFFF" }} />
<ArrowRightIcon sx={{ color: "#F2FFFF" }} />
</IconButton>
<CustomTypography sx={AfterTextStyle}>After</CustomTypography>
<Magnify ref={magnifyRef}>
<Box
component="img"
src={afterImage}
sx={{
position: "absolute",
top: "0",
left: "0",
width: "190vw",
height: "100vh",
objectFit: "cover",
objectPosition,
}}
></Box>
<div style={{ position: "relative", width: "150px", borderRight: "solid 1px white", overflow: "hidden" }}>
<Box
component="img"
src={beforeImage}
sx={{
width: "190vw",
height: "200vh",
objectFit: "cover",
objectPosition,
}}
></Box>
</div>
</Magnify>
</PageDiv>
</>
);
}
export default HomeBeforeAfterTest;
확대하는 코드
const setMagnify = (event: MouseEvent<HTMLElement>) => {
let mouseX = event.pageX - event.currentTarget.offsetLeft;
let mouseY = event.pageY - event.currentTarget.offsetTop;
if (!magnifyRef.current?.style) return;
const w = magnifyRef.current.offsetWidth / 2;
const h = magnifyRef.current.offsetHeight / 2;
magnifyRef.current.style.display = "inline-block";
magnifyRef.current.style.left = `${mouseX - w}px`;
magnifyRef.current.style.top = `${mouseY - h}px`;
setObjectPosition(`-${mouseX * RATIO - w}px -${mouseY * RATIO - h}px`);
};
이미지 크기를 확대해놓은 뒤 objectPosition을 현재 상대적인 마우스 위치 * 배율 - 확대경 크기 절반
을 이용해 각각 마이너스 해주었다.
결과화면
3. 양쪽 이미지 확대
이 또한 기본 이미지 확대와는 달랐다. 왜냐하면 일반필터, AI필터 두 영역중에 한군데라도 마우스가 올라가면 두 영역의 동일한 위치가 확대되어 보여야하기 때문이다.
컴포넌트도 구조화되어 있어서 데이터를 어떻게 주고받을까 고민했다.
WebcamFilterExperience.tsx
└ ResultCard.tsx
└ Magnify.tsx
└ ResultCard.tsx
└ Magnify.tsx
위와같은 구조로 짜여져있고, 둘중 하나의 ResultCard
이벤트를 두개의 ResultCard
가 모두 감지해 하위 Magnify
로 위치정보를 전달해주어야 했다. 따라서 ResultCard
의 이벤트를 상위 WebcamFilterExperience
에서 감지해 모든 하위 컴포넌트로 데이터를 전송하는 방식으로 문제를 해결했다.
Magnify.tsx
import { styled } from "@mui/material/styles";
import { Box } from "@mui/material";
import { useRef, useState, useEffect } from "react";
type MagnifyProps = {
width: string;
height: string;
RATIO: number;
imgSrc: string | undefined;
pos: {x: number, y: number} | undefined;
};
const MagnifyDiv = styled("div")({
width: "250px",
height: "250px",
position: "absolute",
boxShadow: "0 0 0 3px rgba(255, 255, 255, 0.85), 0 0 3px 3px rgba(0, 0, 0, 0.25)",
display: "none",
overflow: "hidden",
});
function Magnify({ width, height, RATIO, imgSrc, pos }: MagnifyProps) {
const magnifyRef = useRef<HTMLDivElement>(null);
const [objectPosition, setObjectPosition] = useState<string>("");
useEffect(() => {
if (pos) {
setMagnify();
} else {
if (!magnifyRef.current?.style) return;
magnifyRef.current.style.display = "none";
}
}, [pos]);
const setMagnify = () => {
if (!pos) return;
if (!magnifyRef.current?.style) return;
const w = magnifyRef.current.offsetWidth / 2;
const h = magnifyRef.current.offsetHeight / 2;
magnifyRef.current.style.display = "inline-block";
if (!magnifyRef.current.parentElement) return
magnifyRef.current.style.left = `${magnifyRef.current.parentElement?.offsetLeft + pos.x - w}px`;
magnifyRef.current.style.top = `${magnifyRef.current.parentElement?.offsetTop + pos.y- h}px`;
setObjectPosition(`-${pos.x * RATIO - w}px -${pos.y * RATIO - h}px`);
};
return (
<MagnifyDiv ref={magnifyRef}>
<Box
component="img"
src={imgSrc}
sx={{
position: "relative",
top: "0",
left: "0",
width: `${parseInt(width.substring(0, width.length - 2)) * RATIO}px`,
height: `${parseInt(height.substring(0, height.length - 2)) * RATIO}px`,
objectPosition,
}}
></Box>
</MagnifyDiv>
);
}
export default Magnify;
ResultCard.tsx
import { MouseEvent } from "react";
import { styled } from "@mui/material/styles";
import Magnify from "../Commons/Magnify";
type ResultCardProps = {
imgSrc: string | undefined;
title: string;
width: string;
height: string;
setMousePos: Function;
pos: { x: number; y: number } | undefined;
};
const TitleSpan = styled("span")({
color: "#CEF3FF",
fontSize: "1.5rem",
fontWeight: "600",
padding: "5px",
marginBottom: "36px",
});
function ResultCard({ imgSrc, title, width, height, setMousePos, pos }: ResultCardProps) {
const handleMove = (event: MouseEvent<HTMLElement>) => {
if (!imgSrc) return
let mouseX = event.pageX - event.currentTarget.offsetLeft;
let mouseY = event.pageY - event.currentTarget.offsetTop;
if (mouseX <= 0 || mouseX > event.currentTarget.offsetWidth || mouseY <= 0 || mouseY > event.currentTarget.offsetHeight) {
setMousePos(undefined)
return
}
setMousePos({x: mouseX, y: mouseY})
};
return (
<div>
<div style={{ display: "flex", margin: "1.5rem", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
<TitleSpan>{title}</TitleSpan>
<div onMouseMove={handleMove} className="dashed_border d-flex justify-content-center align-items-center" style={{ overflow: "hidden", height, width }}>
{imgSrc ? (
<>
<Magnify pos={pos} imgSrc={imgSrc} RATIO={3} width={width} height={height} />
<img src={imgSrc} style={{ height, width }} alt="img" />
</>
) : (
"웹캠을 켜주세요"
)}
</div>
</div>
</div>
);
}
export default ResultCard;
상위 컴포넌트에서 props로 전달받은 함수 setMousePos
를 이용해 onMouseMove
이벤트를 전달한다.
WebcamFilterExperience.tsx
const sendMousePos = (pos: { x: number; y: number } | undefined) => {
if (!showMagnify) return
if (pos) {
setMousePos(pos);
} else {
setMousePos(undefined);
}
};
상위 컴포넌트에서 SetMousePos
로 마우스 상대위치 상태관리를 해주고 이 정보는 하위 ResultCard
> Magnify
로 전달된다. 사실 데이터 흐름이 비효율적인 것 같지만 내 지식으로는 이런 데이터 흐름밖에 생각나지 않았다. 혹시 추후에 다른 효율적인 방법이 떠오른다면 다시 업로드 해야겠다.
마무리
이번 프로젝트도 거의 다 끝나간다. 매일 블로그 쓰기는 비록 지키지 못했지만 프로젝트를 진행하며 발생한 문제상황과 그 문제를 해결한 과정, 고민한 과정을 적을 수 있는 공간이 생겼다는 것만으로 이번 프로젝트 기록하기는 성공한거같다!! 내일 또 다른 기능을 구현할 것 같으니까 내일은 내일의 고민 결과를 적는걸로..
Author And Source
이 문제에 관하여([AI Filter] 11. 5주차 화요일), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@hablobien/AI-Filter-11.-5주차-화요일저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)