원문: https://blog.logrocket.com/progressive-image-loading-react-tutorial/
이미지는 웹사이트에 많은 영향을 줍니다. 그 존재만으로 사용자 경험을 증진시키고 더 몰입할 수 있게 하죠. 하지만 고화질의 이미지를 로딩하는 것은 시간이 걸리고 특히 인터넷이 느릴 경우 사용자들은 더 실망스러운 경험을 하게 될 수 있습니다.
이 문제를 해결하려면 개발자는 긍정적인 로딩 경험을 지원하는 전략을 전개해야 합니다. 그러한 전략 중 하나가 바로 점진적 이미지 로딩입니다.
이 튜토리얼에서는 점진적 이미지 로딩이 무엇인지, 이 전략을 리액트에서 어떻게 적용하는지 등에 대해 안내하려 합니다.
점진적 로딩을 사용하면 실제 이미지가 로드되기 전까지 저화질 혹은 미리 보기 이미지를 표시할 수 있습니다. 이는 이미지가 곧 보일 것임을 인식하게 함으로써 사용자 경험을 향상시킵니다.
아래 GIF는 기본 <img />
요소를 사용하여 이미지를 렌더링 하는 모습을 보여줍니다.
보시다시피, 페이지는 이미 로드되었지만 이미지가 렌더링 되기까지 시간이 더 걸려 빈 공간이 발생하는 것을 확인할 수 있습니다. 인터넷 연결이 매우 느릴 때 이 경험은 더 악화됩니다.
점진적 로딩 기술을 사용하여 이미지의 작은 사이즈 버전을 렌더링 해 로드 시간을 줄일 수 있습니다. 고화질 버전이 로드되면 그때 이미지 파일을 교환합니다. 아래 GIF 데모를 참고하세요.
플레이스 홀더 이미지가 거의 즉시 로드되므로 이 전략은 웹 페이지 이미지로 인한 레이아웃 변경 문제를 줄이는 데에도 도움이 됩니다. 레이아웃 변경은 주로 브라우저가 이미지를 위해 예약할 공간을 인식하지 못하기 때문에 발생합니다.
이미지에 width
및 height
속성을 추가함으로써 레이아웃 변경을 방지할 수 있습니다. 이는 브라우저에게 이미지가 차지할 공간을 예약할 수 있도록 미리 알려줍니다. 그 후에 이미지가 반응형 레이아웃에 올바르게 동작하게끔 CSS 파일의 image에 max-width: 100%
와 height: auto
속성을 적용해야 합니다.
이 튜토리얼에서는 리액트에서 이미지를 점진적으로 로드하여 사용자 경험을 개선하고 레이아웃 변경을 방지하는 방법에 대해 알아봅니다. 또한 동일한 결과를 얻기 위해 외부 라이브러리를 사용하는 방법도 배울 것입니다.
이 튜토리얼을 진행하기 위해서는 리액트에 대한 실무 지식이 필요합니다.
점진적 이미지의 마법 같은 효과는 실제 이미지와 크기가 작은 버전의 이미지(보통 2kB 미만을 사용합니다)의 두 가지 이미지 버전을 만듦으로써 얻을 수 있습니다.
저화질 이미지는 빠른 표시를 위해 처음에 로드되고 메인 이미지가 다운로드되는 동안 메인 이미지 너비에 맞게 확대됩니다. 그 후 블러 효과와 적절한 CSS 트랜지션이 적용됩니다.
Gatsby와 Next.js와 같은 리액트 프레임워크도 이미지 컴포넌트에서 이 패턴을 사용합니다. 사용자가 직접 작은 버전의 이미지를 만드는 대신 프레임워크가 소스 이미지에서 자동으로 이미지를 생성합니다.
또한 이러한 프레임워크에서는 더 발전된 이미지 처리 옵션을 사용하고 화면 밑에 있는 이미지의 경우 지연 로딩을 지원합니다.
여기에선 리액트에서의 점진적 이미지 로딩에 초점을 맞춥니다. 그럼 이제 구현을 시작하겠습니다.
ProgressiveImg
라는 이미지 컴포넌트를 생성하여 <img/>
엘리먼트와 점진적 로딩 로직을 캡슐화할 것입니다. 이 컴포넌트는 기본 <img />
엘리먼트를 대체할 수 있습니다.
const ProgressiveImg = ({ placeholderSrc, src, ...props }) => {
return (
<img
{...{ src: placeholderSrc, ...props }}
alt={props.alt || ""}
className="image"
/>
);
};
export default ProgressiveImg;
위 코드에서 컴포넌트는 실제 이미지 소스, 플레이스 홀더 소스, 그리고 이외 다른 props를 전달받습니다. 그 후 이 props를 <img />
엘리먼트 속성에 할당합니다.
...
전개 연산자를 사용하여 컴포넌트가 받는 다른 props를 주입한 방법에 주목하세요. 예를 들어 컴포넌트는 이미지의 width
와 height
를 전달받습니다. 그동안 앞서 언급했듯이 빠른 표시를 위해 src
에 플레이스 홀더 이미지 소스를 할당합니다.
다음으로 위에서 언급한 props를 다음과 같이 <ProgressImg />
에 전달합니다.
import ProgressiveImg from "./components/ProgressiveImg";
import image from "./images/large*.jpg";
import placeholderSrc from "./images/tiny*.jpg";
export default function App() {
return (
// ...
<ProgressiveImg
src={image}
placeholderSrc={placeholderSrc}
width="700"
height="465"
/>
// ...
);
}
코드에서 볼 수 있듯이 이미지와 2kB 미만으로 크기를 조정한 작은 버전을 전달했습니다. 또한 레이아웃 이동을 방지하기 위해 이미지의 width
와 height
를 전달해야 합니다.
이미지 크기가 지정된 값보다 큰 경우 가로 세로 비율을 유지해야 합니다. 이를 통해 프런트엔드는 다음과 같아야 합니다.
img
의 src
를 업데이트하고 실제 이미지를 렌더링 하기 위해서는 useState
훅을 통해 이미지 소스를 상태 변수에 저장해야 합니다. 그 후 실제 이미지가 로드되면 useEffect
훅 내부의 변수를 업데이트할 수 있습니다.
다음과 같이 ProgressiveImg
컴포넌트를 업데이트해봅시다.
import { useState, useEffect } from "react";
const ProgressiveImg = ({ placeholderSrc, src, ...props }) => {
const [imgSrc, setImgSrc] = useState(placeholderSrc || src);
useEffect(() => {
// 이미지를 업데이트 합니다.
}, []);
return (
<img
{...{ src: imgSrc, ...props }}
alt={props.alt || ""}
className="image"
/>
);
};
export default ProgressiveImg;
img
에 대한 src
속성은 이제 상태 변수의 값으로 할당되었습니다. 기본적으로 이 값은 플레이스 홀더 소스(있을 경우)로 설정됩니다. 그렇지 않으면 기본 이미지가 할당됩니다.
이제 다음과 같이 useEffect
훅을 업데이트해봅시다.
useEffect(() => {
const img = new Image();
img.src = src;
img.onload = () => {
setImgSrc(src);
};
}, [src]);
이 훅에서는 Image()
객체를 인스턴스화하고 src
속성을 실제 이미지 소스로 세팅하여 img
엘리먼트를 생성하는 것으로 시작합니다.
이미지 객체의 onload
이벤트 핸들러를 사용하여 실제 이미지가 백그라운드에서 완전히 로드된 시점을 감지할 수 있습니다. 그 후 이미지 src
를 실제 이미지로 업데이트합니다.
결과는 아래와 같습니다.
자연스러운 효과를 위해 CSS 트랜지션을 추가해 봅시다. ProgressiveImg
컴포넌트에서 return
문 위에 다음 코드를 추가합니다.
const customClass =
placeholderSrc && imgSrc === placeholderSrc ? "loading" : "loaded";
로드 상태에 따라 이미지에 클래스를 동적으로 추가합니다.
<img />
가 커스텀 클래스를 갖도록 업데이트하겠습니다.
return (
<img
// ...
className={`image ${customClass}`}
/>
);
만약 실제 이미지가 아직 로드 중이라면, 이미지에 loading
클래스를 추가합니다. 로드되었다면 loaded
클래스를 추가합니다. 그 후 다음 스타일 규칙을 포함하도록 CSS를 업데이트합니다.
.loading {
filter: blur(10px);
clip-path: inset(0);
}
.loaded {
filter: blur(0px);
transition: filter 0.5s linear;
}
저장 후 프런트엔드의 변화를 확인하세요. 전체 코드는 CodeSandbox에서 확인할 수 있습니다.
react-progressive-graceful-image
라이브러리를 사용하여 이미지를 점진적으로 로드할 수도 있습니다. 이를 사용하려면 먼저 라이브러리를 설치해야 합니다.
npm i react-progressive-graceful-image
그 후 ProgressiveImage
컴포넌트를 불러와 다음과 같이 구현할 수 있습니다.
import ProgressiveImage from "react-progressive-graceful-image";
import image from "./images/large_.jpg";
import placeholderSrc from "./images/tiny.jpg";
export default function App() {
return (
// ...
<ProgressiveImage src={image} placeholder={placeholderSrc}>
{(src, loading) => (
<img
className={`image${loading ? " loading" : " loaded"}`}
src={src}
alt="sea beach"
width="700"
height="465"
/>
)}
</ProgressiveImage>
// ...
);
}
ProgressiveImage
컴포넌트는 render props 기술을 사용하여 점진적 이미지 로딩을 구현합니다. 자식 함수 prop에서 렌더 콜백의 src
와 loading
인수에 접근 가능합니다.
loading
인수로 img
엘리먼트에 동적으로 클래스를 추가할 수 있습니다. 실제 이미지가 로딩 중 일 경우엔 loading
이 true를 반환하고 그렇지 않을 경우엔 false를 반환합니다.
CSS 파일에 각 클래스의 스타일 규칙 또한 추가하였습니다. 전체 코드와 데모는 CodeSandbox에서 확인하실 수 있습니다.
점진적 이미지 로딩 기술을 적용함으로써 리액트 프로젝트에서 사용자 경험을 크게 향상시킬 수 있었습니다.
이 튜토리얼에서는 외부 라이브러리를 사용하지 않고 리액트에서 이미지를 점진적으로 로드하는 방법에 대해 다뤘습니다. 이 가이드가 재밌고 도움이 됐기를 바랍니다.
이 튜토리얼에서는 외부 라이브러리를 사용하지 않고 리액트에서 이미지를 점진적으로 로드하는 방법에 대해 다뤘습니다. 이 가이드가 재밌고 도움이 됐기를 바랍니다. https://gmailguide.io
Thanks for the tutorial, how about loading the image without using React within internal library?
https://geospatialworldforum.org/blogs/hotmail-signup/
After much searching, I eventually located this blog, which aids in my understanding of many strategies. I appreciate the help and will use these fresh perspectives in my professional life.
https://medcraveonline.com/intl/en-us/gmail-signup/
Introducing excellence to students
https://medcraveonline.com/articles/outlook-login/
Can we do it without progessive image uploading method in React?
https://medcraveonline.com/articles/hotmail-login/
지연 로딩 말고도 점진적 로딩을 적용하면 훨씬 자연스러운 서비스 사용성 제공이 가능해지겠어요.
혹시 두가지 방법을 적절히 섞어서 사용하는 방법은 어떨까요?