이미지 리플레이스(IR) 전략

contability·2025년 8월 19일

이미지 리플레이스 전략 정리

이미지 리플레이스 전략이란?

이미지 리플레이스 전략은 웹사이트나 애플리케이션에서 기존 이미지를 다른 이미지로 교체하는 기술적 방법들을 의미한다.

핵심은 개발자가 의도적으로 설정한 조건에 따라 이미지가 교체되는 것이다.

이미지 리플레이스 vs 이미지 최적화

이미지 리플레이스 전략 ✅

  • 기존 이미지를 다른 이미지로 교체하는 것
  • 개발자의 의도적인 선언이 포함됨

이미지 최적화 기법 ❌

  • 레이지 로딩 (Lazy Loading)
  • 프로그레시브 로딩 (Progressive Loading)
  • 이미지 캐싱, 압축 등

이미지 리플레이스 전략 유형

1. 선언적 이미지 리플레이스

개발자가 미리 조건과 교체 규칙을 선언해두는 방식

srcset 활용

<img 
  src="image-800w.jpg"
  srcset="image-400w.jpg 400w, 
          image-800w.jpg 800w, 
          image-1200w.jpg 1200w"
  sizes="(max-width: 600px) 400px, 800px"
/>

srcset의 경우 이미지 리플레이스 전략인가에 대해 애매한 경계선에 있다.

관점에 따른 분류

1. 이미지 리플레이스가 아니라는 관점

  • 개발자가 직접 교체를 제어하지 않는다
  • 브라우저가 자동으로 최적 이미지를 선택한다
  • 사용자나 개발자의 의도적인 "교체" 액션이 없다

2. 이미지 리플레이스라는 관점

  • 실제로는 다른 이미지 파일이 로드된다
  • 조건(화면 크기, 해상도)에 따라 이미지가 바뀐다
  • 결과적으로 "교체"가 일어난다

이렇게 갈리는데 개인적으로는 이미지 리플레이스 전략이라고 생각한다.

이유는 개발자가 직접 교체를 제어하거나 교체 명령으로 액션을 일으키는 것은 아니지만,

“이런 조건에서는 이 이미지로 교체해라”라고 전략을 세워 놓은 것이니까 이미지 리플레이스 전략이라고 생각한다.

picture 요소 활용

<picture>
  <source media="(max-width: 768px)" srcset="mobile.jpg" />
  <source media="(min-width: 769px)" srcset="desktop.jpg" />
  <img src="fallback.jpg" alt="반응형 이미지" />
</picture>

포맷별 조건부 교체

<picture>
  <source srcset="image.avif" type="image/avif" />
  <source srcset="image.webp" type="image/webp" />
  <img src="image.jpg" alt="최적화된 이미지" />
</picture>

2. 명령적 이미지 리플레이스

개발자가 런타임에 직접 제어하는 방식

상태별 이미지 교체

const [isHovered, setIsHovered] = useState(false);

return (
  <img 
    src={isHovered ? 'hover-image.jpg' : 'normal-image.jpg'}
    onMouseEnter={() => setIsHovered(true)}
    onMouseLeave={() => setIsHovered(false)}
    alt="인터랙티브 이미지"
  />
);

조건부 이미지 교체

// 테마에 따른 교체
<img src={isDarkMode ? 'dark-logo.png' : 'light-logo.png'} alt="로고" />

// 언어에 따른 교체
<img src={lang === 'ko' ? 'korean-banner.jpg' : 'english-banner.jpg'} alt="배너" />

동적 콘텐츠 교체

const [selectedImage, setSelectedImage] = useState(images[0]);

return (
  <div>
    <img src={selectedImage} alt="선택된 이미지" />
    {images.map((img, index) => (
      <button 
        key={index}
        onClick={() => setSelectedImage(img)}
      >
        이미지 {index + 1}
      </button>
    ))}
  </div>
);

3. CSS 기반 이미지 교체

실제 이미지 파일 교체

.button {
  background: url(normal-button.png);
}
.button:hover {
  background: url(hover-button.png); /* 다른 이미지 파일로 교체 */
}

CSS 스프라이트 (교체 아님)

/* 이것은 이미지 교체가 아니라 같은 이미지의 다른 부분을 보여주는 것 */
.button {
  background: url(sprite.png) 0 0;
}
.button:hover {
  background-position: 0 -50px; /* 같은 이미지 내에서 위치만 변경 */
}

접근성 고려사항

의미 있는 대체 텍스트

const AccessibleImage = ({ src, alt, decorative = false, longDescription }) => {
  const descriptionId = useId();

  if (decorative) {
    return <img src={src} alt="" role="presentation" />;
  }

  return (
    <>
      <img
        src={src}
        alt={alt}
        aria-describedby={longDescription ? descriptionId : undefined}
      />
      {longDescription && (
        <div id={descriptionId} className="sr-only">
          {longDescription}
        </div>
      )}
    </>
  );
};

로딩 상태 접근성

const AccessibleImageReplace = ({ normalSrc, hoverSrc, alt }) => {
  const [currentSrc, setCurrentSrc] = useState(normalSrc);
  const [isLoading, setIsLoading] = useState(false);

  const handleMouseEnter = () => {
    setIsLoading(true);
    const img = new Image();
    img.onload = () => {
      setCurrentSrc(hoverSrc);
      setIsLoading(false);
    };
    img.src = hoverSrc;
  };

  return (
    <div>
      {isLoading && (
        <span className="sr-only">이미지를 변경하는 중입니다</span>
      )}
      <img
        src={currentSrc}
        alt={alt}
        onMouseEnter={handleMouseEnter}
        onMouseLeave={() => setCurrentSrc(normalSrc)}
      />
    </div>
  );
};

실제 구현 예시

완전한 이미지 리플레이스 컴포넌트

interface SmartImageReplaceProps {
  normalSrc: string;
  hoverSrc?: string;
  alt: string;
  className?: string;
  priority?: boolean;
}

const SmartImageReplace: React.FC<SmartImageReplaceProps> = ({
  normalSrc,
  hoverSrc,
  alt,
  className,
  priority = false
}) => {
  const [currentSrc, setCurrentSrc] = useState(normalSrc);
  const [isHovered, setIsHovered] = useState(false);
  const [preloadedImages, setPreloadedImages] = useState<Set<string>>(new Set());

  // 호버 이미지 프리로드
  useEffect(() => {
    if (hoverSrc && priority) {
      const img = new Image();
      img.onload = () => {
        setPreloadedImages(prev => new Set(prev).add(hoverSrc));
      };
      img.src = hoverSrc;
    }
  }, [hoverSrc, priority]);

  const handleMouseEnter = () => {
    if (hoverSrc) {
      setIsHovered(true);
      if (preloadedImages.has(hoverSrc)) {
        setCurrentSrc(hoverSrc);
      } else {
        const img = new Image();
        img.onload = () => setCurrentSrc(hoverSrc);
        img.src = hoverSrc;
      }
    }
  };

  const handleMouseLeave = () => {
    setIsHovered(false);
    setCurrentSrc(normalSrc);
  };

  return (
    <img
      src={currentSrc}
      alt={alt}
      className={`transition-opacity duration-200 ${
        isHovered ? 'opacity-90' : 'opacity-100'
      } ${className}`}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      loading={priority ? 'eager' : 'lazy'}
    />
  );
};

핵심 요점

  1. 이미지 리플레이스 = 이미지 교체

    • 다른 이미지 파일로 바뀌는 것
    • 개발자의 의도적인 선언이나 제어가 포함됨
  2. 선언적 vs 명령적

    • 선언적: srcset, picture 등으로 미리 규칙 설정
    • 명령적: JavaScript로 런타임에 직접 제어
  3. 접근성 필수 고려

    • 적절한 alt 텍스트 제공
    • 로딩 상태에 대한 정보 제공
    • 스크린 리더 사용자 배려
  4. 성능 최적화와 구분

    • 레이지 로딩, 프로그레시브 로딩은 최적화 기법
    • 이미지 리플레이스는 교체 전략

0개의 댓글