728x90

서비스를 운영하다보면 아무리 이미지 압축을 잘 하더라도,

이미지 자체가 워낙 큰 파일이라 로드에 시간을 길게 소요하는 경우가 발생합니다.

 

이런 경우를 대비해 이미지 로드에 부가적인 처리를 좀 해주면

멋스럽고 좋은 사용자 경험을 제공할 수 있습니다.

 

간단한 js와 css를 함께 응용해야하는데,

예제 코드들로 살펴보도록 하겠습니다.

 

[ 주요 키워드: classList, Image ]

 

1. 기본 핵심코드

1.1 html

<div class='image-wrapper'>
  <img class='image-thumbnail' src='/no-image.png' alt='이미지' />
</div>

1.2 css

.image-wrapper {
  background-color: #dadada;
  
  &.visible {
    background-color: transparent;
    transition: all 0.25s;
  }
  
  &.visible > .image-thumbnail {
    transform: scale(1);
    opacity: 1;
  }
}

.image-thumbnail {
  transform: scale(0.5);
  opacity: 0;
  
  width: 100%;
  height: auto;
  object-fit: cover;
}

1.3 js

<script>
  const source = "https://t1.daumcdn.net/tistory_admin/static/top/pc/img_common_tistory_200910.png";
  const image = new Image();
  image.setAttribute('src', source);
  image.onload = function() {
    const wrapper_node = document.querySelector('image-wrapper');
    const node = document.querySelector('image-thumbnail');
    
    wrapper_node.classList.add('visible')
    node.src = source;
  }
</script>

 

 

자, 이렇게하면 이미지가 로드된 후에 고급진 이펙트와 함께 이미지가 나타나게됩니다.

가능하다면 no image상태일때 이미지를 설정해줘도 좋겠죠?

 

 

하지만, 이렇게 단일 이미지를 로드할때보다는

여러 이미지를 로드할때 이것은 더욱 빛을 바랍니다.

예를들어,  Youtube의 영상 목록을 불러올때나 와디즈에서 펀딩 목록을 불러올때

image가 로드되는동안 기본 이미지(혹은 배경색)을 보여주다가

이미지가 로드되면 약간의 효과와 함께 이미지를 띄워줍니다.

 

 

이번에는 React 예제로 살펴보겠습니다.

예시의 편의성을 위해 데이터는 어딘가 서버로부터 불러왔다고 가정하겠습니다.

그리고 실제 개발에서 쓸수있도록, 페이지 스크롤에 따라 이미지 로드와 효과 적용을 핸들링 해보도록 하겠습니다.

 

const ImageList = ({ images ) => {
  return (
    <>
    {
      images.map((img, index) => {
        const image = new Image();
        image.load = function() {
          // how...?
        }
        return (
          <div key={"image-item-" + index.toString()} className='image-wrapper>
            <img className='image-thumbnail' alt='썸네일' />
          </div>
        );
      })
    }
    </>
  )
}

 

이전과 비슷한데  React라는 점, 그리고 단일 이미지가 아닌 list라는 부분에서 차이가 발생했습니다.

여기서 어떻게 각기 알맞는 이미지 태그에 이미지를 넣어줄 수 있을까요?

 

여러가지 방법이 있을 수 있지만,

여기서는 React hooks의 useRef를 사용해보도록 하겠습니다.

(최근에, hooks를 좀 익혀보는 중이거든요)

 

[ 주요 키워드: classList, Image, useRef, useEffect ]

 

<script>
import React, { useRef, useEffect } from 'react';

const ImageList = ({ images ) => {
  return (
    <>
    {
      images.map((img, index) => {
        const image_card = useRef(null);
        
        useEffect(() => {
          scorller();
          window.addEventListener('scroll', scroller, false);
          
          return window.removeEventListener('scoll', scroller, false);
        });
        
        const scroller = () => {
          const { classList: classes, offsetTop, clientHeight } = image_card.current;
          
          // 이미 visible 처리된 element는 중복작업 방지 - 쓸데없는 메모리낭비x
          if (classes.contains('visible')) return null;
          
          // 화면 스크롤 높이에따라 element가 노출되고 있는지를 체크
          const visible_timing = window.innerHeight + windwo.scrollY >= offsetTop - (clientHeight / 2);
          
          // scroll 위치까지 고려해서 이미지 로드 처리
          if (visible_timing) {
            const image = new Image();
            image.setAttribute('src', img);
            image.onload = function() {
              const node = image_card.current.querySelector('.image-thumbnail');
              if (node) {
                classes.add('visible');
                node.src = img;
              }
            }
          }
        }
        
        return (
          <div 
            key={'image-item-' + index.toString()} 
            className='image-wrapper'
            ref={image_card}
          >
            <img className='image-thumbnail' alt='썸네일' />
          </div>
        );
      })
    }
    </>
  )
}
</script>

 

이 처럼 React로 이미지 목록을 구현하는 방법을 살펴보았습니다.

어떤가요?

코드가 어렵다거나 하지는 않죠?

생각보다 간단하게 괜찮은 효과를 낼수 있음을 알 수 있습니다.

 

좀 더 나아간다면 ImageCard를 모듈화 하면 더 좋겠죠?

 

 

728x90
반응형