프로젝트가 얼추 끝나 진행하며 생긴 이슈들을 정리하고자 한다.

완성본이 궁금하다면?
[배포 링크] https://courageous-hamster-cbe0a4.netlify.app/
[레포지토리] https://github.com/0hhanum/Book-Book-Book

1. Safari Rotate 애니메이션 문제

rotateX, rotateY 트랜지션을 이용한 프로그래스 컴포넌트를 만들었는데,

사파리에서 확인하니 애니메이션을 준 컴포넌트의 가상 배경같은게 생겨서 그놈이 같이 돌아가는 요상한 현상을 발견했다.

요런 애니메이션이,

이렇게 난리가 났다.

사파리 Webkit 엔진의 HTML 렌더링 방식이 크롬의 Blink와 차이가 있고, 특히 rotate 관련 애니메이션에서 엉뚱하게 동작하는 일이 많다고 한다.

나와 정확하게 일치하는 현상은 찾기 힘들었는데, rotate 되는 축을 따라서 간헐적으로 뒷 배경같은게 돌아가는걸로 확인됐다.

position: absolute 의 컨테이너를 만들어 래핑하니 해결됐다.
reflow시 다른 요소들과 간섭이 있었던 걸로 추정

const DotContainer = styled.div`
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
`;

...

  <DotContainer>
      <ProgressDot />
  </DotContainer>

2. Gatsby 빌드 시 PeerJS 문제

PeerJS

WebRTC를 통해 페이지 내에서 다이렉트로 나에게 영상통화를 쏠 수 있는 기능을 생각했다.
바닥부터 개발하기엔 무리가 있어서 구글 STUN서버와 PeerJS 중 고민하다 PeerJS를 선택했다. PeerJS?

const peer = new Peer() 별도 설정 없이 peer 객체를 생성하고,

상대 peer 아이디만 알면 call() 메서드를 통해 브라우저간 webRTC 통신이 가능하다.

객체 생성 시 라이브러리 메인테이너가 제공하는 peerServer 를 통해 peer 객체들 간 서로를 찾을 수 있는 아이디를 부여해주고, call() 실행 시 해당 서버로 상대 아이디를 던져 실제 IP와 포트 주소를 받아와 연결하는 방식인데, 참 신기하고 편하다.

빌드 안됨

SSG 프레임워크 개츠비를 사용했는데, 브라우저용 API인 navigation객체가 node.js 런타임에는 존재하지 않아 빌드 시 오류가 발생했다.

WebpackError: ReferenceError: window is not defined
WebpackError: ReferenceError: navigation is not defined

원인은 동일하다.

NEXT로 개발시에도 종종 발생하는 문제란다.
lazy load를 이용해 peerJS 사용하는 컴포넌트를 CSR 컴포넌트로 빼고,
mediaStream 얻어오는 부분은 예외처리해 해결했다.

util.ts

// 빌드 중인지 확인하는 함수
export const checkIsSSR = (): boolean => {
  return typeof window === "undefined";
};

admin.tsx
const AdminComponent = lazy(
  () => import("../../components/RTCs/peerJS/AdminComponent")
);
const Admin = () => {
  // ROUGHLY MADE (Admin)
  const isSSR = checkIsSSR();
  return (
    <>
      {!isSSR && (
        <Suspense>
          {/* PeerJS only be executed in the browser environment */}
          <AdminComponent />
        </Suspense>
      )}
    </>
  );
};

요런식으로 빌드중에는 해당 컴포넌트 접근할 일이 없도록 한다.

mediaStream 얻어오는 부분은 아래와 같다.

RTCutil.ts

export const setStream = async () => {
  if (typeof navigator === "undefined") return;
  // This code executed only in the browser environment
  try {
    const stream = await navigator.mediaDevices.getUserMedia({
      video: {
        facingMode: "user",
      },
      audio: true,
    });
    return stream;
  } catch (error) {
    return;
  }
};

별거없소

3. PeerJS MediaConnection close() 오류

peerJS의 connection close 이벤트가 정상적으로 동작하지 않는 오류가 있었다.

발신자 A가 connection.close() 실행 시 연결이 종료되며 수신자 B의 close 이벤트리스너가 동작하지만, 수신자의 connection.close()는 동작하지 않는 오류였다.

https://github.com/peers/peerjs/issues/636 2020년에 제출된 오류로 3년 가까이 개선되지 않던 문제였는데 포스팅 시점에 처리되었다. ㄱ-

유저 간의 비디오 콜을 구현하는 거였다면 꽤 골치아픈 이슈였을텐데, 다행히 내 경우 무조건 Admin(나 ㅋㅋ) 쪽에서 메일로 받은 peerId로 전화를 거는 방식으로 발신자가 고정되어 있어 쉽게 처리할 수 있었다.

기술적인 처리는 아니였다. Admin 페이지에만 closeCall 버튼을 두어 통화 종료 시 유저의 close 이벤트 콜백이 동작하도록 하는 것.

[유저 측 브라우저 화면] Admin 측(발신자)의 커넥션 종료 이벤트를 받을 수 있다.

4. 정적파일 preload 및 GLB 모델 Compress

책책책, 책을 읽읍시다! 는 서비스 호스팅에 Netlify, CMS로 Contentful을 이용중이다.


요렇게 생긴 3D 책 모델 .glb 파일만 호스팅 서버에 두고, 책 표지나 독후감에 이용되는 정적 파일들은 CMS를 이용해 관리했다.

하루키의 댄스댄스댄스 독후감으로 스크롤 애니메이션으로 텍스트를 두고
마지막 스크롤 화면에 진입하면 바다에 떠있는 마세라티를 ThreeJS를 통해 보여주려 했는데,
마지막 화면에 진입 시 glb파일을 불러옴과 동시에 ThreeJS Canvas 객체를 만들고 오브젝트들을 삽입하려하니 브라우저 자원이 부족했는지 너무 버벅였다.

preload() 유틸함수를 만들어 해당 페이지 진입 시 모델을 preload 했다.

export const preloadImage = (src: string, callback?: () => void) => {
  const virtualImage = new Image();
  virtualImage.src = src; // it makes load texture concurrently using disc cache
  if (callback) {
    virtualImage.onload = () => callback();
  }
};

가상의 img element를 만들고, src를 달아 해당 이미지를 디스크에 캐싱한다. Ocean 모델에 사용될 바다 텍스쳐 이미지를 위 방법을 이용해 preload하고, 3D 모델의 경우 react-three/drei 에서 제공하는 useGLTF.preload() 메서드를 이용해 쉽게 preload 할 수 있었다.

이미지 로드에 의한 버벅임은 줄어들었지만, 마세라티 모델 자체가 커서 해당 모델 초기 렌더링 시 버벅임은 여전히 남아있었다. 애초에 웹 환경에서 구동할 모델로 만든 것이 아니라 크기가 컸다.

3D 모델은 각이 많을수록 그 볼륨이 증가한다. 블렌더라 다른 툴을 다룰 줄 알면 모델을 최적화하기 좋았겠지만 그런 능력은 없어서 모델 최적화 서비스를 찾아봤다.

Aspose에서 무료로 제공하는 GLB compressor를 이용해 최적화하니 18.4mb -> 4.5mb로 용량이 1/4가량 축소되어 훨씬 원활하게 렌더링 할 수 있었다.

모델이 너무 커 고민중이라면 사용해 보시길
https://products.aspose.app/3d/compression/glb

5. 책 .gltf 모델 텍스쳐 로드 문제

#4 이슈와 비슷하게, 책 모델의 .gltf 가 사용하는 텍스쳐의 이미지 크기가 너무 커 로드가 늦어지는 이슈가 있었다.

unsplash와 비슷하게 3D 모델들을 제공하는(무료도 있다) sketchfab에서 책 모델을 받아 사용했는데,
해당 모델에 책 질감을 내주기 위한 metalicRough.png, base_normal.png 등의 텍스쳐 이미지 파일의 크기가 너무 컸다. (2mb, 4mb)

(그냥 이런 이미지이다)

preload 처리를 해도 해당 텍스쳐 이미지를 받는데 2.1s 이상이 소요되었고, 이건 너무 길다 싶어 이미지 압축을 통해 최대 1.5mb로 크기를 줄였다.

2.1s -> 1.2s 로 속도를 향상시킬 수는 있었지만, 그래도 1초 이상 걸리는건 찝찝했다. 더군다나 나의 경우 책의 텍스쳐 위에 내가 그린 표지를 덧씌워 사용중이라 굳이 텍스쳐가 필요한가 싶어 gltf 파일을 수정하려고 했다.

.gltf 형식의 모델은 json 형식으로 되어있어 사람이 읽고 수정하기 편하다.

images 데이터를 삭제하고,

해당 텍스쳐 이미지를 사용하는 material과 관련된 부분도 지워주었다.

텍스쳐 로딩을 아예 없애, 초기 로딩 UX가 훨씬 개선되었다.

텍스쳐를 전부 지웠더니 양장본 특유의 오돌토돌한 느낌이 없어지고(형태는 양장본이다) 문고본 느낌의 책이 되었지만 뭐, 만족스럽다.

profile
https://github.com/0hhanum

1개의 댓글

comment-user-thumbnail
2023년 7월 29일

공감하며 읽었습니다. 좋은 글 감사드립니다.

답글 달기