next/image 반응형 적용하기 | Next.js 13

Bori·2023년 6월 18일
5

Next.js

목록 보기
9/12
post-thumbnail

Next.js는 이미지 최적화를 위해 Image 컴포넌트를 제공합니다.

Eslint 설정에 따라 Next.js 프로젝트에서 Image 컴포넌트가 아닌 일반 img 태그를 사용할 경우 다음과 같은 Eslint 경고 메시지가 나타날 수 있습니다.

관련 링크

Image 컴포넌트의 필수 속성

Image 컴포넌트에는 다음과 같은 필수 속성들이 있습니다.

  • width, height 속성은 정적 이미지거나 fill 속성을 사용했을 경우를 제외하고 필수로 입력해야 합니다.

다음 예시 코드처럼 작성하면 Image 컴포넌트를 간단하게 사용할 수 있습니다.

import Image from 'next/image'
 
const Page = () => {
  return (
    <Image
      src={src}
      alt={alt}
      width={500}
      height={500}
    />
  )
}

export default Page;

width, height에는 원본 이미지의 픽셀 값을 숫자로 입력하면 됩니다.

만약 이미지의 크기를 정확히 알 수 없는 경우 어떻게 해야할까요? 그리고 반응형을 고려해야한다면 어떻게 해야할까요?

fill 속성

fill 속성을 사용하면 이미지가 부모 요소를 채우도록 동작합니다. 이미지의 너비와 높이를 설정하는 대신 이미지를 부모 요소에 맞게 확장할 수 있습니다.

fill 속성을 적용하면 width, height를 설정할 수 없습니다.
만약 fillwidth, height를 함께 적용하면 에러가 발생합니다.

<Image
  src={src}
  alt={alt}
  width={500}
  height={500}
  fill
/>

style로 직접 width, height를 지정해도 에러가 발생합니다.

<Image
  src={src}
  alt={alt}
  fill
  style={{ width: '500px', height: '500px' }}
/>

fill 속성을 사용하면 다음과 같은 스타일이 기본으로 적용됩니다.

<img 
    style="position:absolute; height:100%; width:100%; left:0; top:0; right:0; bottom:0; color:transparent"
     ...
 />

개발자 도구 > Elements 탭에서 확인할 수 있습니다.

img 요소에 position: "absolute"가 자동으로 지정되기 때문에 부모 요소에 position: "relative", position: "fixed", 또는 position: "absolute" 를 지정해야 합니다.

부모 요소에 position을 지정하지 않으면 다음과 같이 나타납니다.
개발자 도구 > Layers 탭에서 확인해보니 상단 header(z-index를 설정하지 않은 상태)를 덮어버렸네요.
피드 형식으로 게시글이 나타나는 페이지인데 모든 이미지가 body를 기준으로 position: "absolute" 된 상태입니다.

부모 요소에 position: "relative"를 적용하고 너비와 높이를 지정해보겠습니다.

<Container>
  <Image
    src={src}
    alt={alt}
    fill
  />
</Container>

// Emotion을 이용한 스타일 코드 작성
const Container = styled.div`
  position: relative;
  width: 100%;
  height: 200px;
`;

부모 요소의 크기에 맞춰 이미지가 적용되었습니다.
예시 코드에서는 height: 200px;로 높이 값을 직접 지정했습니다. 높이 값을 지정하지 않는다면, 부모 요소의 크기를 기반으로 이미지가 나타나므로 이미지도 높이 값이 height: 0이 됩니다.

반응형으로 나타내기

첫 번째 방법 - fill

fill 속성을 이용해서 반응형을 고려한 Image 컴포넌트를 적용해보겠습니다.
기본으로 적용되어 있는 스타일을 상쇄하기 위해 CSS 선택자 우선순위가 가장 높은 !important를 사용할 수 있습니다.

Image 컴포넌트 자체에 다음과 같이 스타일을 직접 적용합니다.

<Container>
  <StyledImage
    src={src}
    alt={alt}
    fill
  />
</Container>

const Container = styled.div`
  position: relative;
`;

const StyledImage = styled(Image)`
  position: relative !important;
  height: unset !important;
`;

먼저, position: relative !important; 를 적용하여 문서의 흐름에 맞게 배치될 수 있도록 합니다.

다음으로 height: unset !important;를 적용했습니다.
이 때 unset 속성은 부모로부터 상속할 값이 존재하면 상속값(inherit으로 동작)을, 그렇지 않다면 초깃값(initial로 동작)을 사용합니다.
부모 요소에 높이 값을 상속하지 않으면 초깃값인 이미지의 높이 값이 적용됩니다.

height: initial !important;을 적용해도 이미지의 높이 값에 맞춰 나타납니다.
프로젝트 상황에 맞게 선택해주세요 :)

두 번째 방법 - width, height, sizes

이 방법은 훨씬 간단합니다.

<StyledImage
  src={src}
  alt={alt}
  width={0}
  height={0}
  sizes="100vw"
/>

const StyledImage = styled(Image)`
  width: 100%;
  height: auto;
`;

fill 속성을 사용하지 않기 때문에 position: "absolute"로 인해 부모 요소에 position을 지정하지 않아도 됩니다.

sizes="100vw"를 지정하지 않으면 이미지가 상당히 깨져서 나타납니다. 그 이유는 Elements 탭에서 확인할 수 있습니다.

  • sizes="100vw"를 지정하지 않았을 경우 적용되는 코드
  • sizes="100vw"를 지정했을 경우 적용되는 코드

쨔쟌! header에 z-index를 설정하지 않아도 이미지가 header를 가리지 않게 되었습니다.

최종 코드

저는 반응형 이미지를 적용할 수 있는 컴포넌트를 생성해서 적용했습니다.

import styled from '@emotion/styled';
import Image from 'next/image';

interface ResponsiveImageStyleProps {
  aspectRatio?: number | 'auto';
}

interface ResponsiveImageProps extends ResponsiveImageStyleProps {
  src: string;
  alt: string;
  quality?: number;
}

const ResponsiveImage = ({
  src,
  alt,
  quality,
  aspectRatio = 'auto',
}: ResponsiveImageProps) => {
  return (
    <ImageContainer aspectRatio={aspectRatio}>
      <StyledImage src={src} alt={alt} quality={quality ?? 100} fill priority />
    </ImageContainer>
  );
};

export default ResponsiveImage;

const StyledImage = styled(Image)`
  position: relative !important;
  height: unset !important;
  object-fit: cover;
`;

const ImageContainer = styled.div<ResponsiveImageStyleProps>`
  position: relative;

  ${StyledImage} {
    aspect-ratio: ${({ aspectRatio }) => aspectRatio};
  }
`;

저는 첫 번째 방법을 이용하여 적용했습니다.
(header에 z-index도 적용했습니다.)
추가로 object-fit: cover;를 적용하여 이미지가 크기에 꽉 차게 설정하고, 상황에 따라 aspect-ratio를 적용하기 위해 aspectRatio 값을 전달받을 수 있도록 했습니다.

aspectRatio를 Image 컴포넌트에 직접 props로 전달하여 적용하지 않은 이유는 Image 컴포넌트에 지정된 속성 이외의 속성을 설정할 수 없기 때문입니다. 따라서, 부모 요소에서 aspectRatio 값을 받아 그 값을 Image 컴포넌트로 전달할 수 있도록 했습니다.
또는 부모 요소에서 aspectRatio 값으로 aspect-ratio를 적용할 수 있습니다. 만약 부모 요소에 적용한다면 overflow: hidden;도 함께 적용해주세요.

마무리

Next.js가 13버전으로 업데이트 되면서 Image 컴포넌트의 몇몇 속성들이 레거시가 되었습니다. 13버전 이 전 버전의 예시들은 찾기 쉬웠는데 13버전 예시는 비교적 찾기 어려워서 글을 작성하게 되었습니다.
fill 속성을 사용해본 적이 없다가 이번 기회에 사용하면서 많은 공부가 되었습니다.
!important는 최대한 사용을 피하고자 하는데 fill 속성을 이용하고 싶어서 같이 사용했습니다. 하핫
unset에 대해서도 스치듯 인지하고 있던 속성이었는데 이번에 다시 한 번 자세히 공부할 수 있어서 좋았습니다.
이 글이 많은 분들에게 도움이 되길 바랍니다 :)

참고

0개의 댓글