본문 바로가기

개발

모바일 크로스 브라우징 경험기

회사에서 앱 내 iframe으로 삽입되는 방식의 프로덕트를 맡게 되었는데, 운영체제(iOS, AOS)별로 특정하게 발생하는 이슈를 겪었다. 앱 환경은 처음이다보니 디버깅이 쉽지 않았다.

 

우선은 사내 테스트용 기기들에서는 해결된 것으로 판단을 했지만, 운영체제 버전이나 기기 종류에 따라 완전한 해결책이 아닐 수도 있을 듯하다. 지속적으로 업데이트할 예정.

 

내가 겪은 대부분의 이슈가 채널톡 블로그(링크)에 잘 설명되어 있으나 해결 방법은 일부 수정을 해야했다.

iOS 한글 입력

채팅 입력창에 한글을 입력하고 전송 후 다음 입력시에 앞의 입력이 남아있다. 예를 들어,

 

  1. ‘가나다’ 입력
  2. 전송
  3. ‘라’ 입력
  4. 입력창에는 ‘다라’로 표시(입력)됨

iOS에서 한글 입력시 값이 버퍼에 남게 되어 생기는 현상이라고 알려져 있다. 구글링해보면,

 

  1. 실제 입력창이 아닌 input을 만들어두고
  2. 실제 입력창에 입력 및 전송이 된 후에
  3. 1번 input에 focus
  4. 실제 입력창 input에 다시 focus

이렇게 버퍼를 초기화(clear)시키고, 1번 input을 visibility: hidden 상태로 둬서 사용자에게는 보이지 않게 하는 방식이 거의 유일한 해결 방법으로 나온다.

 

그런데 막상 적용해보니 visibility: hidden인 input에 focus가 되지 않아서 문제 해결이 되지 않았다. 관련해서 직접 테스트를 해보고 찾아도 봤지만 visibility: hidden이면 focus 할 수 없었다. (참고 링크) 위 방법으로 해결했다는 글이 꽤 되는데 어떻게 된 영문인지 잘 모르겠다.

 

아무튼 결국 1번 input을 opacity: 0, z-index: -1 등 다른 속성으로 보이지 않게 했더니 focus가 가능해지면서 해결이 되었다. 웹 접근성 등의 측면에서는 좋지 않은 방법인 것 같으나 아직까지는(?) 딱히 대안을 찾지 못했다.

iOS 스크롤

iOS에서 키보드 활성화 상태에서 스크롤시 document가 키보드 뒤로 감춰지는 이슈다. 위에 링크한 채널톡 블로그에 원인부터 해결 방법까지 잘 정리되어 있어서 그대로 적용했다.

 

다만 처음 읽었을 때 잘 이해되지 않았던 것이 있어서 해당 부분에 주석으로 설명만 보충한다.

 

<div id="wrapper">  
  <div id="content">...</div>
  <div id="make-scrollable"></div>
</div>
// 실제 scrollable해지는 element
#wrapper {
  position: relative;
  width: 100%;
  height: 200px;
  overflow-y: auto;
}

// wrapper가 '항상' scroll이 가능한 상태가 되도록 만들어주는 역할
// 실제 scroll되는 element
#make-scrollable {
  position: absolute;
  top: 0;  // top 또는 left (혹은 둘다) 속성을 사용하여
  left: 0; // wrapper를 덮음
  width: 1px;
  height: calc(100% + 1px); // height를 100%보다 1px높게 잡아 실제로 scroll이 되도록 만듭니다.
}

// 실제 내용
#content {
  width: 100%;
  height: auto;
}

AOS 스크롤

AOS의 경우 키보드 활성화시에 내부적으로 viewport를 조절(키보드 뺀 부분 영역 높이로)한다고 한다. 그래서 iOS처럼 키보드 뒤로 화면이 숨는 문제는 발생하지 않는다.

 

그런데 사내 프로덕트에서 문제는 viewport가 변경됨에 따라 스크롤 위치도 조정되는 점이었다. (서비스 특성상 스크롤 위치가 가장 하단으로 유지되어야 했다)

 

마찬가지로 채널톡 블로그를 참고하여 커스텀 hook을 작성했다.

 

  • window의 resize 이벤트를 이용하여 키보드 활성화 여부 판단
  • 키보드 활성화시 동작은 hook을 사용하는 쪽에서 주입
import { useState, useEffect, useCallback } from "react";
import { isAndroid } from "react-device-detect";

interface IParams {
  onFocus: () => void;
}

const useDetectAosKeyboardFocus = ({ onFocus }: IParams) => {
  const [isKeyboardFocused, setIsKeyboardFocused] = useState(false);
  const [initialClientHeight, setInitialClientHeight] = useState(Infinity);

  useEffect(() => {
    if (typeof window === "undefined") {
      return;
    }

    // 최초 화면 height 초기화
    setInitialClientHeight(window.innerHeight);
  }, []);

  const handleResize = useCallback(() => {
    if (initialClientHeight === Infinity) {
      return;
    }

    if (window.visualViewport === null || window.visualViewport === undefined) {
      return;
    }

    // 현재 뷰포트가 초기 height보다 작으면 키보드가 활성화된 것으로 판단
    if (window.visualViewport.height < initialClientHeight) {
      setIsKeyboardFocused(true);
    } else {
      setIsKeyboardFocused(false);
    }
  }, [initialClientHeight]);

  useEffect(() => {
    // Android인 경우에만 이벤트 리스너를 달아준다
    if (isAndroid) {
      window.addEventListener("resize", handleResize);
    }

    return () => {
      if (isAndroid) {
        window.removeEventListener("resize", handleResize);
      }
    };
  }, [handleResize]);

  useEffect(() => {
    if (isKeyboardFocused) {
      onFocus();
    }
  }, [isKeyboardFocused, onFocus]);
};

export default useDetectAosKeyboardFocus;

 

위와 같은 훅을 작성해두고, 사용하는 쪽에서는 onFocus에 실행될 함수를 넣어준다.