2026년 5월 12일
제목에서 "사용하지 못하는 이유"라고 작성했지만 모바일에서도 100vh는 사용 가능하다.
하지만, 정상적으로 동작하는 PC와 달리 모바일은 Notch, Home indicator 등 실제 높이에 비해 사용할 수 있는 높이가 모두 다르다.
그래서 Apple에서도 이러한 문제를 개선하기 위해 Safe Area라는 환경 변수를 제공 하는데, css에서 사용할 때는 env() 와 constant() 를 사용하면 된다.
constant()는env()와 흡사하지만 iOS 11.0을 대응하기 위한 환경 변수입니다.
스크롤이 생겨도 상관 없는 페이지라면 그대로 사용해도 괜찮지만 상단의 이미지와 같이 스크롤이 절대로 생기면 안되는 페이지라면 env도 사실상 무용지물 일 때도 있다.
이러한 경우 공간 차지가 적은 요소가 렌더링 돼도 화면의 100%를 채우기 위해서 100vh를 사용하게 되는데, position: absolute; 상태에서 env()와 constant()를 적용해도 safe-area의 값을 기준으로 컨테이너의 높이가 더욱 증가해서 아무런 요소가 없어도 스크롤이 발생하는 경우가 있다.
fill-available는 css에서 사용 가능한 값이다.
사용 방법은 간단한데, 아래와 같이 작성하면 된다.
/* Element의 전체 높이 */
.my-div-name {
height: 100vh; /* fill-available 자체가 없을 때 사용 가능 */
height: -moz-available; /* 파이어폭스에서 사용 가능 */
height: -webkit-fill-available; /* 웹킷 기반 브라우저에서 사용 가능 */
height: fill-available;
}
하지만 이 값을 body에 적용하여 높이를 지정했을 때는 100vh처럼 % 단위 사용이 불가능 하다.
그래서 window.innerHeight를 갱신하여 높이를 지정하는 방법이 있다.
이 포스트에서 작성된 코드는 React를 기준으로 작성 되었습니다.
EvnetListener 를 등록해서 height 값을 변경하면 되는데, React에서는 아래와 같이 적용하면 된다.// Hooks
import { useState, useEffect } from "react";
export const useWindowSize = () => {
const [width, setWidth] = useState(window.innerWidth);
const [height, setHeight] = useState(window.innerHeight);
useEffect(() => {
const setWindowSize = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
}
window.addEventListener("resize", setWindowSize);
return () => {
window.removeEventListener("resize", setWindowSize);
}
},[])
return {
width,
height
}
}
// Render
// 보다 직관적인 예시를 위해 styled-components에 적용하는 예시로 작성됨
import React from "react";
import { useWindowSize } from "./useWindowSize"; // 경로는 직접 지정하면 됩니다.
import styled from "styled-components"
const Wrapper = styled.div`
width: 100%;
${props => props.height ? `height: ${props.height}px;` : ""}
`
const App = () => {
const { height } = useWindowSize();
return(
<Wrapper height={height}>
// Your Children
</Wrapper>
)
}
export default App;
SSR기반에서는 사용이 불가능하다.
그 이유는 위 이미지와 같이(Gatsby 빌드 시) window의 객체가 없다는 에러가 발생하게 되는데, 이러한 경우에는 window의 객체가 있는지 확인 후 객체가 없다면 해당 useEffect를 중단하는 형식으로 작성하면 된다.// Hooks
/*
* window객체가 없으면 state들은 모두 0으로 지정하고
* useEffect는 상단 return에 의해 아래 함수가 실행 되기 전 취소가 되는 로직이다.
*/
import { useState, useEffect } from "react";
export const useWindowSize = () => {
const isBrowser = typeof window !== "undefined"; // 변경됨
const [width, setWidth] = useState(isBrowser ? window.innerWidth : 0); // 변경됨
const [height, setHeight] = useState(isBrowser ? window.innerHeight : 0); // 변경됨
useEffect(() => {
if (!isBrowser) return; // 변경됨
const setWindowSize = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
}
window.addEventListener("resize", setWindowSize);
return () => {
window.removeEventListener("resize", setWindowSize);
}
},[isBrowser]) // 변경됨
{
width,
height
}
}
이러한 문제가 발생되는 대표적인 예시가 Gatsby 프로젝트 빌드인데, 이러한 검증 과정을 거치게 되면 문제 없이 빌드 됩니다.
// Hooks
/*
* window객체가 없으면 state들은 모두 0으로 지정하고
* useEffect는 상단 return에 의해 아래 함수가 실행 되기 전 취소가 되는 로직이다.
*/
import { useState, useEffect } from "react"
export const useWindowSize = () => {
const isBrowser = typeof window !== "undefined" *// 변경됨*
const [width, setWidth] = useState(isBrowser ? window.innerWidth : 0) *// 변경됨*
const [height, setHeight] = useState(isBrowser ? window.innerHeight : 0) *// 변경됨*
useEffect(() => {
if (!isBrowser) return // 변경됨
const setWindowSize = () => {
setWidth(window.innerWidth)
setHeight(window.innerHeight)
}
window.addEventListener("resize", setWindowSize)
return () => {
window.removeEventListener("resize", setWindowSize)
}
}, [isBrowser]) *// 변경됨*
{
width,
height,
}
}
이러한 검증 과정을 거치게 되면 SSR에서도 문제없이 사용할 수 있습니다.
height 100% 적용그냥 깡으로 html, body 그리고 React 기준으로 #root에 height: 100%를 적용 해버리면 단번에 해결되기도 합니다.