본문 바로가기

카테고리 없음

UX 개선을 위한 이미지 프리로딩 With Promise

프로젝트 거의 막바지에 겪은 문제가 있었다.

바로 '이미지 최적화' 문제!

 

360도 회전 이미지를 구현하는데, 이미지가 버벅거리며 움직이는 문제가 있었다 ...

 

 

이..이게뭐누...

 

사실 이유는 알고 있었다.

근데 어떻게 해야할지 몰랐다.

 

우선 동작 방식은, 마우스를 클릭할 때 돌린다는 신호를 주고, 마우스를 좌, 우로 움직일 때 이미지의 index를 +1 하거나 -1 해주는 방식이다.

const [image, setImage] = useState(1);
  const [prevX, setPrevX] = useState(0);
  const [start, setStart] = useState(false);
  const startSwipe = (event: React.MouseEvent) => {
    if (start) {
      setPrevX(event.clientX);
      if (prevX < event.clientX) {
        if (1 >= image) setImage(60);
        else setImage(prevImage => prevImage - 1);
      }
      if (prevX > event.clientX) {
        if (60 <= image) setImage(1);
        else setImage(prevImage => prevImage + 1);
      }
    }
  };

 

여기서 문제는, 드래그 속도가 빨라 인덱스가 굉장히 빨리 올라간다는 거고, 그만큼 많은 양의 이미지를 짧은 시간에 로드해야한다는 것이다.

하지만 우리의 네트워크는 그만큼 빠르지 않기 때문에 사용자는 버벅이는 이미지를 확인하는 것이다.

 

그럼 여기서 2가지 방법을 생각할 수 있다.

우선 기본적인 배경은 이렇다.

네트워크에서 어떤 URL의 이미지를 미리 로드해두면, 해당 URL 요청으로 들어온 이미지는 브라우저에서 이미 캐싱해놨기 때문에, 빠르게 가져와 사용할 수 있다.

 

 

 

이 방법을 사용하는건데 ...

첫번째는 모든 사진을 한 페이지에서 미리 로드하는 방식이다.

이 방식은 간단하게 모든 사진을 로드할 수 있겠지만, 사진의 갯수가 늘어나거나 용량이 크다면 페이지에서 버벅임이 있을 수 있다.

만약 글로벌 서비스를 지원한다면, 네트워크 속도가 우리나라보다 느리기 때문에 이 버벅임이 더 심하고,

부정적인 UX를 제공할 수 있는 확률이 있다.

 

그래서 생각한 두번째 방법은 사용자가 특정 액션을 할 때, 조금씩 사진을 로드해오는 방법이다.

예를 들어, 마우스를 올릴 수 밖에 없는 버튼에 이미지를 로드해오는 함수를 적용하는 방식이다.

 

우선, 이미지를 불러오는데는 Promise 객체를 사용한다.

여기서 Promise 객체란?

 

자바스크립트에서 제공하는 비동기를 간편하게 처리할 수 있게 도와주는 객체이다.

Promise 이전에 비동기 처리로 콜백 패턴을 주로 사용했었다.

그러나 콜백 지옥(Callback Hell)으로 인해 가독성도 나쁘고, 비동기 처리 중에 발생한 에러의 처리가 까다로웠다.

Promise는 이러한 단점을 보완하기 위해 나온 대안이라고 봐도 무방하다.

 

간단한 사용 방법은,

 

// Promise 객체의 생성
const promise = new Promise((resolve, reject) => {
  // 비동기 작업을 수행
​
  if (/* 비동기 작업 수행 성공 */) {
    resolve('Success');
  }
  else { /* 비동기 작업 수행 실패 */
    reject('Failed');
  }
});

 

이런식으로, 비동기로 작업을 수행하고 성공하면 그 값을 리턴해준다.

 

그럼 어떻게 사용하느냐?

 

function preloadImage(src:string) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = function () {
        resolve(img);
      };
      img.onerror = img.onabort = function () {
        reject(src);
      };
      img.src = src;
    });
  }

 

이렇게 url을 파라미터를 받아서, 해당 이미지 객체를 미리 생성해둔다.

비동기로 한번에 여러개의 이미지를 요청하고, 로드가 끝나면 해당 이미지를 반환하는(캐싱하는) 방식이다.

이렇게 여러 url들을 미리 변환해놓으면, 해당 url을 요청할 땐 캐싱한 데이터를 사용하기 때문에 빠르다 !!

 

 

 

따라서 이렇게 엔진, 바디, 구동방식, 트림 등을 선택할 때 사용자가 마우스를 올리는 액션을 취하는데,

 

		<ButtonBox
            onClick={e => {
              findSpan(e);
            }}
            onMouseOver={preloadImages}
          >
            <EBWButton value="디젤 2.2" price={1480000} onClick={handleButtonClick} />
            <EBWButton
              value="가솔린 3.8"
              price={0}
              onClick={handleButtonClick}
            />

 

그럴 때 마다 preloadImages( ) 함수를 호출해 이미지를 조금씩 로드하는 방식이다.

 

한번에 다량의 이미지를 처리하는 방식이 처음이라, 어떻게 해결해야 할지 방법이 떠오르지 않았는데

이미지 pre-loading이라는 방식을 찾고, 해결했다.

 

그 결과 ...

 

스무쓰 라잌 버터

 

잘 돌아간다 !!!

어려운 작업은 아니지만, 해보지 않았다면 난감했을 문제 ! 도움이 되었길 바란다.