문제점1. 첫 화면 컨텐츠 렌더링시 버벅거리는 문제
첫 화면에서 AOS 라이브러리를 사용하여 이미지에 flip 효과를 주는 애니메이션을 적용했다.
하지만 해당이미지가 렌더링 될 때 커튼처럼 버벅거리며 렌더링되었고 깜빡거리면서 보여지는 문제가 발생했다.
해당 문제는 두 가지의 방법으로 해결했다.
CSS의 will-change 속성으로 해결
CSS 작업에서 스타일 계산, 레이아웃 처리, 애니메이션 프레임 계산 등을 CPU가 담당하는데, CPU가 과도하게 사용되는 경우 버벅거리고 깜빡이는 문제가 발생할 수 있다.
CSS의 will-change 속성은 브라우저에게 요소의 예상 변경 사항을 미리 알려주는 역할을한다.
이 속성을 사용하면 브라우저는 요소가 실제로 스타일 변형이 필요할 때 필요한 리소스를 최적화하여 요소의 스타일변경과 렌더링을 더 빠르고 최적화된 방식으로 처리할 수 있게 도와준다.
will-change속성을 사용하면 GPU에서 해당 요청을 처리할 수 있도록 최적화할 수 있고, 브라우저에게 특정 요소에 대해 하드웨어 가속을 미리 준비하도록 지시할 수 있다.
이미지 사이즈 최적화 및 파일 확장자를 webp로 변경
또한 이미지 사이즈가 너무나도 컸다.
실제 보여지는 이미지는 435x435였는데 사진 크기는 4000x4000이였으며 해당 이미지의 사이즈를 줄여 최적화를 했다.
jpg,png를 사용하는게 아닌 webp 확장자를 사용했다. webp는 무손실 및 손실 압축을 제공하는 최신 이미지 파일 포멧으로 무손실 압축의 경우 동일 화질에서 png대비 26%, jpg대비 25~34% 더 작은 이미지를 만들 수 있다.
문제점2. 이미지를 한번에 받아오기 때문에 다운로드 받는 속도가 매우 느린 문제
여러장의 이미지를 slider 형태로 넘기면서 볼 수 있는 컴포넌트가 있다.
해당 컴포넌트는 createPortal로 `<div id=modal/>` 의 자식 컴포넌트로 렌더링을 하고 있다.
허나 img태그에 lazy load 속성을 적용했음에도 불구하고 웹페이지가 로드되자마자 모든 이미지를 다운로드 받는 현상이 발생했고 이로 인해 로딩이 느려지는 이슈가 생겼다.
원인은 아래와 같다.
modal의 상태가 display:none이지만 position:fixed인 상태여서 뷰포트 내부에 있다고 판단.
position:fixed인 속성을 position aboluste로 변경하고, createPortal로 띄우는 대신 필요한 섹션의 자식 컴포넌트로 렌더링하게 수정했다.
더 나은 해결방법으로는 이미지를 동적으로 렌더링하거나, IntersectionObserver를 통해서 모달이 실제 화면에 표시될 때 이미지를 불러오는 방식을 사용할 수 있다.
문제점3. 이외의 자잘한 성능 이슈
fadeout 애니메이션을 구현하기 위해 자바스크립트로 하드코딩하여 애니메이션을 구현했으나 아래와 같은 문제점이 있었다.
- 한 번만 사용되는 애니메이션을 위해 isShow라는 불필요한 상태가 사용되었고, 이로인해 불필요한 리렌더링이 발생
- CSS 애니메이션은 초기 로딩 시 즉시 적용되지만 자바스크립트를 사용한 애니메이션은 컴포넌트 마운트 이후 실행되기 때문에 약간의 지연이 발생했음.
- 코드적으로 자바스크립트는 복잡하고 선언적이며 setTimeout을 사용하고 있어 정확한 타이밍 제어가 어려웠음.
자바스크립트 사용시
function App(){
const [isShow, setIsShow] = useState(false);
useEffect(() => {
const showTimeout = setTimeout(() => {
setIsShow(() => true);
}, 400);
const hideTimeout = setTimeout(() => {
setIsShow(() => false);
}, 2200);
return () => {
clearTimeout(showTimeout);
clearTimeout(hideTimeout);
};
}, []);
return(
<StyledNav className={isShow ? "active" : ""}>배경음악이 준비되어 있습니다.</StyledNav>
)
}
const StyledNav = styled.div`
@media (max-width: 360px) {
max-width: 360px;
}
width: 100%;
max-width: 435px;
height: 48px;
position: absolute;
top: 0;
transform: translateY(-48px);
transition: all 0.3s ease-in-out;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.4rem;
background-color: #f0ede6;
color: #606060;
&.active {
transform: translateY(0);
}
`;
css 사용시
function App() {
return (
<StyledNav>배경음악이 준비되어 있습니다.</StyledNav>
);
}
const slideInOut = keyframes`
0% {
transform: translateY(-48px);
}
10%, 90% {
transform: translateY(0);
}
100% {
transform: translateY(-48px);
}
`;
const StyledNav = styled.div`
@media (max-width: 360px) {
max-width: 360px;
}
width: 100%;
max-width: 435px;
height: 48px;
position: absolute;
top: 0;
transition: all 0.3s ease-in-out;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.4rem;
background-color: #f0ede6;
color: #606060;
/* 애니메이션 적용 */
animation: ${slideInOut} 3.5s ease-in-out;
animation-fill-mode: forwards;
`;
이외 수정한 사항들
preloadImage 함수를 통해 이미지를 미리 불러오도록 구현.
이미지를 미리 로드함으로써 사용자가 실제로 이미지를 보게 될 때 이미 브라우저 캐시에 있어 즉시 표시되는 장점이있다.
const preloadImage = (src) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = resolve;
img.onerror = reject;
img.src = src;
});
};
const OptimizedImage = React.memo(({ src, alt, ...props }) => {
useEffect(() => {
preloadImage(src);
}, [src]);
return <StyledImage src={src} alt={alt} {...props} />;
});
export default function Starting() {
const imageUrl = useMemo(() => `${process.env.PUBLIC_URL}/image/main-card.webp`, []);
useEffect(() => {
// Preload the main image as soon as the component mounts
preloadImage(imageUrl);
}, [imageUrl]);
return (
<Container as="section" aria-label="시작">
<ImgWrapper data-aos="flip-left" data-aos-delay="300" data-aos-duration="1200">
<OptimizedImage src={imageUrl} alt="메인 사진" />
</ImgWrapper>
</Container>
);
}
댓글