모바일 브라우저 이슈

LazyMG·2025년 6월 26일
0

개발 일기

목록 보기
3/4
post-thumbnail

기존 PC 환경만 고려했던 프로젝트를 반응형 UI를 적용하면서 모바일 환경에서도 사용할 수 있게 확장하면서 여러 문제들이 생겼습니다.

특히, 모바일 브라우저 환경에서 발생했던 문제들을 정리해보려고 합니다.

자동 재생 방지

해당 프로젝트는 사용자의 클릭으로 유튜브 영상을 자동 재생 시키는 것이 주요 기능이었습니다. 구체적으로, 사용자가 콘텐츠를 클릭하면 콘텐츠에 맞는 영상이 로드되고 자동으로 재생되는 기능이었습니다. 영상을 가려 소리만 나오게 하여 음악을 재생하는 효과를 주고자 했습니다.

PC 환경에서는 사용자의 클릭에 따라 유튜브 영상이 자동으로 재생되어 기능 동작에 전혀 문제가 없었습니다. 하지만 모바일 브라우저에서는 영상이 자동으로 재생되지 않았습니다. 원인을 찾아보니 모바일 브라우저는 사용자의 의사에 맞지 않는 재생으로 인한 소리 방지와 데이터 및 배터리 등의 리소스 절약을 위해 음소거인 상태에서만 자동 재생을 가능하게 한다는 것이었습니다.

사용자가 콘텐츠를 클릭하여 영상이 로드되는 것까지는 가능했지만 자동으로 재생되지 않아 재생 상태 확인에 어려움이 있었습니다. 정확하게는 모든 영상이 자동 재생이 되지 않는 것이 아니라 재생이 될 때도 있고 재생이 되지 않을 때도 있었습니다.

이를 해결하기 위한 두 가지 방법을 시도했습니다.

  1. 재생 상태 로직을 변경하여 로드가 되면 사용자가 직접 재생하도록 하기
  2. 처음에는 음소거로 재생 시켰다가 재생이 되면 음소거 해제하기

첫 번째 방법은 구현이 어렵지 않다고 느꼈지만 사용자 경험에 악영향을 줄 것 같아 차선책으로 미뤘습니다. 따라서 두 번째 방법으로 시도했습니다.

프로젝트에 사용한 react-youtube 라이브러리는 iframe 태그를 사용하여 youtube 영상을 로드하고 제어할 수 있게 하였습니다. 라이브러리에서 제공하는 Youtube 컴포넌트의 opts props를 통해 설정을 조정할 수 있습니다.

const opts: YouTubeProps["opts"] = {
    height: "390",
    width: "320",
    playerVars: {
      autoplay: 1,
      mute: 1,
    },
  };

자동 재생 옵션과 음소거 옵션을 설정하여 모바일 환경에서도 자동 재생이 가능하도록 했습니다. 그 후, 영상이 재생되면 동작하는 Youtube 컴포넌트의 onPlayerPlay props에 사용될 onPlayerPlay 함수를 조작했습니다.

const onPlayerPlay: YouTubeProps["onPlay"] = (event) => {
    // ... //

    event.target.unMute();
  };

하지만 기존과 동일하게 영상이 재생되지 않는 경우가 있었습니다. 아마 영상이 로드되고 재생되는 타이밍이 엇갈려서 동작하지 않았던 것 같습니다. 기존 재생 바 로직에서는 재생 중이라는 UI가 나오는데 영상이 재생되지 않아 사용자에게 큰 불편이 있을 것이라 생각했습니다.

또한, 막상 생각해보니 PC에서도 굳이 음소거가 되었다가 다시 음소거를 해제해야 하는 부분이 걸렸습니다. 특히 볼륨 조절을 구현하였는데 이 부분의 로직이 엉킬 수 있다고 판단했습니다. 때문에 첫 번째 방식인 사용자에게 재생을 맡기는 쪽으로 로직을 변경했습니다.

재생 바에서 재생이 필요하다는 UI를 보여주는 방식을 구현했습니다. 기존 재생 바 로직을 유지한 채 유튜브 영상의 상태가 변경될 때마다 동작하는 onStateChange 함수를 조작하여 구현했습니다.

const onStateChange: YouTubeProps["onStateChange"] = (event) => {
    if (
      isMobile &&
      (ytPlayer === 3 || ytPlayer === 5) &&
      event.target.getPlayerState() === -1
    ) {
      // 모바일의 경우
      setYtPlayer(2);
      setCurrentPlayer((prev) => {
        return {
          ...prev,
          isPaused: true,
          isPlaying: false,
        };
      });
    } else {
      // PC의 경우이거나 정상적으로 재생될 때
      setYtPlayer(event.target.getPlayerState());

      if (event.target.getPlayerState() === 1) {
        setCurrentPlayer((prev) => {
          return {
            ...prev,
            isPlaying: true,
          };
        });
      } else if (event.target.getPlayerState() === 2) {
        setCurrentPlayer((prev) => {
          return {
            ...prev,
            isPaused: true,
          };
        });
      }
    }
  
  	// ... //
  };

모바일 환경에서 영상이 정상적으로 재생되지 않을 경우, 영상이 일시 정지인 상태로 판단하여 재생 바에서 사용자에게 재생이 필요하다는 UI를 보여줄 수 있도록 했습니다.

결과적으로 사용자는 두 가지 상황을 보게 됩니다.

  1. 정상적으로 영상이 자동 재생 됨
  2. 영상이 자동 재생되지 않아 재생 바에서 재생 버튼이 보임

정상적인 재생을 위해서 사용자의 동작이 한 번 더 필요하게 된 것이 매우 아쉬웠습니다. 가장 중요한 기능인 자동 재생이 모바일 브라우저 환경에서 잘 동작하지 않는다는 것이 치명적이었습니다. 서비스를 기획할 때 여러 환경에 대한 지식이 반드시 필요하다는 것을 배웠습니다.

쿠키

모바일 브라우저의 환경에서 마주친 두 번째 문제는 쿠키 사용이었습니다. 서로 다른 도메인 간의 쿠키 전달은 이전에 오류를 통해 경험해봤기 때문에 서버에서 미리 설정했습니다.

// server.ts
app.use(
  cors({
    origin:
      process.env.NODE_ENV === "production"
        ? process.env.FRONT_URL
        : process.env.FRONT_DEV_URL,
    credentials: true,
  })
);

// 쿠키 생성 코드
res.cookie("accessToken", accessToken, {
    httpOnly: true,
    secure: true,
    maxAge: 60 * 60 * 1000, // 1시간
    sameSite: "none",
  });

와일드카드(*)가 아닌 정확한 도메인을 지정하고 쿠키 옵션을 위와 같이 설정하여 PC 환경에서 서로 다른 도메인 간의 쿠키 공유를 구현했습니다. 하지만 문제는 모바일 환경이었습니다.

IOS Safari에서 로그인 테스트를 했을 때 쿠키 공유가 되지 않아 정상적인 로그인 동작이 이루어지지 않았습니다. 이는 서버 로그를 통해 확인할 수 있었습니다. 기존 PC 환경에서 구현했던 쿠키 공유 방식은 서로 다른 도메인이어도 쿠키를 공유할 수 있도록 설정한 것이었습니다. 하지만 모바일 브라우저는 PC 브라우저보다 쿠키 공유를 더 민감하게 제한하기 때문에 기존 방법으로는 불가능했습니다.

때문에 아예 도메인을 맞추기로 했습니다. 도메인 판매 사이트에서 도메인을 구입하고 서버와 클라이언트의 도메인을 설정하여 동일한 도메인을 갖도록 했습니다. 도메인을 서로 맞추니 정상적으로 로그인이 동작하는 것을 확인했습니다.

서로 다른 도메인 간의 쿠키 공유를 이미 해결해본 경험이 있어 쿠키 공유에는 문제가 없을 것이라 생각했지만 모바일 브라우저에 대한 경험은 없었기 때문에 문제를 맞이하게 되었습니다. 이번 사례를 통해 도메인 구입 및 설정까지 경험해볼 수 있었던 것이 큰 수확이었습니다.

스크롤

모바일 브라우저에서 맞이한 세 번째 문제는 스크롤 문제였습니다. 이 부분은 CSS와 밀접하게 관련되었다고 생각합니다.

IOS Safari에서 해결이 필요한 문제였습니다. 안드로이드 갤럭시 기종으로 확인해보니 스크롤이 문제 없이 동작했습니다.

주요 문제는 다음과 같았습니다.

  • 스크롤이 전체 레이아웃 영역과 메인 콘텐츠 컨테이너 영역에서 모두 일어남
  • 스크롤이 하단에서 위로 한 번에 올릴 때 끝까지 올라가지 못함
  • 스크롤이 정상적으로 동작하지 않아 스크롤 이벤트가 걸려있는 헤더의 이벤트가 동작하지 않음

컴포넌트들의 CSS 코드를 수정해보고 overflow 부분을 여러 번 확인해봤지만 원하는 결과가 나오지 않았습니다. 구글링을 해보니 IOS Safari에서 스크롤 관련 문제는 꽤나 빈번하게 발생하는 것을 알게 되었습니다. 해결 방법은 단순했습니다.

총 두 가지 CSS 코드를 추가하여 문제를 해결했습니다.

/* GlobalStyle.tsx */
overscroll-behavior: contain;

/* Layout.tsx */
height: 100dvh;

먼저, 스크롤 관련 이벤트와 스크롤 동작이 겹치는 것 같은 문제는 height: 100dvh를 통해 해결할 수 있었습니다.

기존 PC 환경에선 height: 100vh로 설정해서 화면 전체 높이를 확보했지만 이 설정 그대로 모바일 브라우저로 넘어가게 되면 주소창 영역까지 포함되어 레이아웃에 영향을 주게 되었습니다. 해당 부분을 수정하니 스크롤의 문제가 거의 해결되었습니다.

overscroll-behavior: contain은 스크롤이 끝에 도달했을 때의 기본 동작을 제한합니다. IOS Safari에서는 바운스 및 새로고침을 제한합니다. auto, none의 다른 옵션도 존재합니다. auto가 기본값으로 설정되어 있습니다.

이렇게 모바일 브라우저 환경에서 프로젝트를 테스트 해보면서 시행착오를 겪게 되었습니다. 만족할만한 해결책도 있었고 기대에 미치지 못한 해결책도 있었습니다. 단순히 PC 환경에서만 사용하는 서비스를 개발하는 것이 목표가 아니기 때문에 여러 환경에서 테스트 해보면서 해당 환경의 지식을 쌓아가는 것이 중요하다는 것을 배울 수 있었습니다.

profile
개발 기록

0개의 댓글