React로 360도 돌아가는 이미지 구현 (react-360-view)

2024년 07월 13일

TOC

React

360도 회전하는 이미지를 볼 수 있고, 더 나아가 드래그 이벤트를 통해 그 물체를 조작해 볼 수 있는 기능은 다양한 웹사이트에서 사용되고 있다. 주로 자동차, 신발, 시계 등의 상품을 관찰할 수 있게 서비스하는 경우가 많다. 이 포스트는 360 뷰어를 구현할 수 있는 다양한 방법 중 react-360-view 라이브러리를 통해 구현하는 법과 오류 방지 팁, 한계점에 대해 다룬다.

react-360-view

360viewer1 360viewer2

물체에 회전 효과를 주기 위해선 어떤 방법을 사용해야 할까? 자체적으로 3D 프로그램을 통해 개발을 할 수도 있겠지만, 가벼운 프로젝트나 중,소 규모 웹사이트에 적용하기에는 다소 어려워 보인다.

간단하게 생각해 보면 다른 각도를 지닌 사진 여러 장을 부드럽게 전환하는 것도 좋은 방법이다.

car-1 car-2 car-3 car-4 car-5 car-6 car-7 car-8 car-9 car-10 car-11 car-12 car-13 car-14 car-15 car-16 car-17 car-18 car-19 car-20 car-21 car-22 car-23 car-24 car-25 car-26 car-27 car-28 car-29 car-30 car-31 car-32 car-33 car-34 car-35 car-36

위처럼 여러 각도를 가진 이미지들이 있고, 이를 부드럽게 전환시킨다면 해당 물체가 360도로 돌아가는 느낌을 줄 수 있다.

라이브러리를 사용하지 않고 드래그 이벤트 처리를 통해 마우스 이동 방향에 따라 이미지 값을 바꾸어 가는 방식으로 개발을 할 수도 있지만, react-360-view 라이브러리를 사용하면 복잡한 코드를 작성하지 않고 간단하게 구현해낼 수 있다. (그만큼 존재하는 단점에 대해서는 포스트 뒷 부분에)

img-list

각도 변화에 따른 여러 이미지가 준비되어 있어야 한다. 구현에는 GitHub에 업로드한 차량 이미지를 사용해 보았다.

$ npx create-react-app react360view
$ cd react360view
$ npm start

먼저 React를 설치한다.

$ npm install react-360-view

react-360-view 라이브러리를 설치한다.

// App.js
import ThreeSixty from "react-360-view"
import "./App.css"

function App() {
  return (
    <div>
      <div
        style={{
          width: "40%",
          alignItems: "center",
          border: "2px solid black",
          margin: "30px auto",
          position: "relative",
        }}
      >
        <ThreeSixty
          amount={36}
          imagePath="https://raw.githubusercontent.com/yhuj79/blog-assets/main/240713/car"
          fileName="car-{index}.webp"
          spinReverse
        />
      </div>
    </div>
  )
}

export default App
/* App.css */
@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css");

App.js와 App.css의 내용을 위와 같이 수정했다. 여기서 css는 라이브러리의 아이콘이 fontawesome을 사용하기 때문에 적용되었다.

ThreeSixty 속성을 살펴보면 amount가 있는데, 뷰어에 적용될 총 이미지 수를 넣으면 된다. 이미지 수가 많아질수록 미세한 각도까지 표현되면서 더 부드러워질 것이다. imagePath에는 도메인을, fileName에는 이미지 이름과 형식을 적었다. 이 부분이 중요한데, {index}를 통해 이미지 순서에 따라 구현이 된다. 사용한 이미지가 car-1, car-2, car-3 ... 의 패턴이기 때문에 car-{index}.webp 로 작성하였다.

360viewer3

이미지가 부드럽게 돌아간다. 드래그에 따른 반응 또한 문제 없이 잘 이루어진다.

다른 느낌을 보기 위해 Google Earth 에서 캡쳐한 지구로 다시 테스트해 보았다.

earth-1 earth-2 earth-3 earth-4 earth-5 earth-6 earth-7 earth-8 earth-9 earth-10 earth-11 earth-12 earth-13 earth-14 earth-15 earth-16 earth-17 earth-18 earth-19 earth-20 earth-21 earth-22 earth-23 earth-24 earth-25 earth-26 earth-27 earth-28 earth-29 earth-30 earth-31 earth-32 earth-33 earth-34

360viewer4

지구가 열심히 돌아간다.

이 이상으로는 GIF 프레임의 한계 때문에 느낌이 전달되지 않기 때문에 react-360-view에 대해 궁금하거나 관련 기능을 필요로 하고 있다면, 이미지를 구해 실제로 테스트를 해보는 것을 추천한다.

스크롤 시 오류 방지

react-360-view 라이브러리는 회전 뿐만 아니라 이미지의 확대, 축소 기능도 제공한다. 위에서 다룬 지구를 예로 들자면, Google Earth만큼 좋은 화질은 아니겠지만 원하는 부분을 확대해볼 수 있다.

360viewer-zoom1 360viewer-zoom2

하지만 프로젝트에 적용할 때 한 가지 문제가 발생한다. 이미지 위에 마우스가 위치할 때 확대 및 축소가 발생할 텐데, 웹페이지의 다른 부분에서부터 스크롤을 진행하다가 뷰어 위에 마우스가 위치하게 된다면 의도치 않게 확대, 축소가 작동할 가능성이 있다.

확대와 축소는 이미지 아래 툴 박스를 이용하면 충분하다. 그래서 오류 방지를 위해 wheel event 발생을 차단하는 코드를 추가해 보았다.

import { useEffect } from "react"
import ThreeSixty from "react-360-view"
import "./App.css"

function App() {
  // 스크롤 시 의도치 않게 특정 요소가 확대, 축소되는 현상 방지
  useEffect(() => {
    const element = document.getElementById("container-viewer")

    if (element) {
      const handleWheel = (event) => {
        event.stopPropagation()
      }

      // wheel event가 다른 요소로 전파되기 전에 stopPropagation에 의해 중단
      element.addEventListener("wheel", handleWheel)

      // 언마운트 될 때, wheel event listener 제거
      return () => {
        element.removeEventListener("wheel", handleWheel)
      }
    }
  }, [])

  return (
    <div>
      <div
        id="container-viewer"
        style={{
          width: "40%",
          alignItems: "center",
          border: "2px solid black",
          margin: "30px auto",
          position: "relative",
        }}
      >
        <ThreeSixty
          amount={34}
          imagePath="https://raw.githubusercontent.com/yhuj79/blog-assets/main/240713/earth"
          fileName="earth-{index}.png"
          spinReverse
        />
      </div>
    </div>
  )
}

export default App

div에 container-viewer라는 id를 부여하여 관리한다. 이벤트 리스너를 통해 wheel event를 제어함으로써 해당 컨테이너는 스크롤이 더이상 작동하지 않게 되었다.

한계점

지금까지 다룬 부분에 대해서만 다룰 것이라면, 이 라이브러리는 기능적으로 충분하다.

하지만 좀 더 깊이 다루고자 한다면 막히는 부분이 생긴다. 예시를 몇 개 들어보자면...

  1. amount가 많고 고화질인 이미지의 로딩 문제
  2. 이미지 교체가 필요할 때 (ex: 자동차의 타이어, 자동차의 색상, 신발 끈 색상 등)

1번 예시처럼 로딩 문제가 발생할 경우 Pre Loading 과 같은 이미지 처리가 필요할 것이고, 2번의 경우 직접 드래그 이벤트를 작성하면서 뷰어를 처음부터 개발해 나가야 할 것이다. Vanilla Javascript로 코드를 작성한다면 throttle 도달 수치를 조정하여 드래그 감도를 조절하고, 자동차의 색상이나 타이어를 교체할 수 있게 추가적인 커스터마이징도 충분히 가능할 것이다. 대신 그만큼 필요한 이미지의 수 또한 늘어날 것이다.


Reference

react-360-view - npm

360 degrees Product Viewer in React js - Code With Yd

GIFs and Spins: Making your Product Come to Life

Configurator | Mercedes-Benz

Google Earth

Written by

yhuj79

🌱 Junior Developer