본문 바로가기

CSS

(CSS) 카카오톡 브라우저 주소창과 네비게이션 바 고정

카카오톡을 통해 링크에 접속할 경우, 인앱 브라우저가 실행됩니다. 이 때, 화면을 위로 스크롤하면 인앱 브라우저의 주소창과 네비게이션 바가 자동으로 숨겨집니다. 이러한 동작으로 인해 브라우저의 창 크기에 혼동이 생길 수 있습니다.

모바일 버전에서는 문제를 해결하기 위해 메뉴 버튼을 우측 상단에 배치하고, 해당 버튼을 클릭할 때 메뉴가 화면 전체를 채우는 효과를 주었습니다.

하지만 문제는 메뉴가 표시된 상태에서도 화면에 고정되어야 한다는 점입니다. 스크롤 방지 효과를 적용해도 인앱 브라우저의 주소창과 네비게이션 바가 자동으로 숨겨지는 현상이 발생합니다.

이러한 상황에서 주소창과 네비게이션 바를 고정하기 위해서는 CSS 효과를 사용할 수 있습니다. 자세한 기술적인 내용은 다음과 같습니다

 

html {
  widht: 100%;
  height: 100%;
  overflow: hidden;
}

body {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

#container {
  width: 100%;
  height: 100%;
  overflow: hidden auto;
}

 

그런데 Next.js 의 태그 구조를 보면 body 아래 <div id=" __next"> 가 보입니다.

 

 

css 효과의 conatiner 태그에 주었던 효과를 '__next'에 주어야 한다.

 

document.getElementById('#__next')!.style.cssText = `
  width: 100%;
  height: 100%;
  overflow: hidden auto;
 `

작동하지 않는다. 페이지를 구동하면 style이 null이라는 오류가 뜬다.

document.getElmentaryById로 접근이 안된다는 것이다.

 

아래는 해결 방법이다.

document.getElementsByTagName('html')[0].style.cssText = `
  overflow: hidden;
`
document.body.getElementsByTagName('div')[0].style.cssText = `
  width: 100%;
  height: 100%;
  overflow: hidden auto;
`

 

getElementByTagName 으로 접근을 하고 있다.

body 아래 id='__next'는 첫째 div 태그여서 [0] 로 접근하여 style을 입력해 주었다.

 

전체 코드이다.

 

useModal.tsx

import { useRef, useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
import styled from 'styled-components'

/** HeaderModal custom hooks */
const useModal = () => {
  const [modalOpened, setModalOpened] = useState(false)

  const openModal = () => {
    setModalOpened(true)
    document.body.style.overflow = 'hidden'
    document.getElementsByTagName('html')[0].style.cssText = `
      overflow: hidden;`
    document.body.getElementsByTagName('div')[0].style.cssText = `
      width: 100%;
      height: 100%;
      overflow: hidden auto;`
  }

  const closeModal = () => {
    setModalOpened(false)
    document.body.style.overflow = 'unset'
    document.getElementsByTagName('html')[0].style.cssText = `
      overflow: unset;`
    document.body.getElementsByTagName('div')[0].style.cssText = `
      width: 100%;
      height: 100%;
      overflow: none;`
  }

  interface IProps {
    children: React.ReactNode
  }

  const ModalPortal: React.FC<IProps> = ({ children }) => {
    const ref = useRef<Element | null>()
    const [mounted, setMounted] = useState(false)

    useEffect(() => {
      setMounted(true)
      if (document) {
        const dom = document.querySelector('#root-modal')
        ref.current = dom
      }
    }, [])

    if (ref.current && mounted && modalOpened) {
      return createPortal(
        <Container>
          <div
            className="modal-background"
            role="presentation"
            onClick={closeModal}
          ></div>
          {children}
        </Container>,
        ref.current,
      )
    }
    return null
  }

  return {
    openModal,
    closeModal,
    ModalPortal,
  }
}

export default useModal

const Container = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1000;

  .modal-background {
    position: absolute;
    width: 100%;
    height: 100%;
  }
`