Container Query
웹 어플리케이션을 제작하는 데 있어 반응형 디자인은 필수적이고, 일반적으로 이를 처리하기 위해 CSS Media Query를 사용한다. 그러나 이 방식은 뷰포트 크기를 기준으로 하기에 컴포넌트 내부 크기 변화에 민감하게 대응하지 못한다는 한계가 존재했다. 만약 컴포넌트 자체의 크기 변화에 따라 스타일을 다르게 처리하고 싶다면 Resize Observer와 useEffect 등 JS의 도움을 받아야 했다.
하지만 CSS Container Query의 등장으로 인해 JS가 아닌 CSS의 힘 만으로 개별 컴포넌트의 크기와 컨텍스트에 따라 스타일을 적용할 수 있게 되었다. 이로 인해 더 유연한 UI 설계가 가능해지며, 특히 복잡한 레이아웃을 가진 웹 어플리케이션에서 컴포넌트의 재사용성을 극적으로 끌어올릴 수 있게 한다.
사용자의 취향에 따라 사이드바를 좁히거나 늘릴 수 있는 ─ 가령 유튜브가 그런 식인데 ─ 서비스가 있다고 해보자. 이런 경우 브라우저 뷰포트는 변하지 않지만, 실질적인 컨텐츠가 배치되는 공간은 사이드바의 상태에 따라 가변한다.
만약 미디어 쿼리를 사용해 이런 디자인을 처리하려 한다면, 같은 디자인을 적용하더라도 사이드바가 확장 되어있는가 여부에 따라 복잡하게 코드를 처리해야 할 것이다. 하지만 컨테이너 쿼리를 사용한다면 오로지 실질적인 컨텐츠 공간의 너비를 기준으로 할 수 있기에 훨씬 직관적이고 유지보수에 용이해진다.
기존의 ResizeObserver가 그렇듯 컨테이너 쿼리도 너비 변화를 추적할 '컨테이너 요소'가 필요하다. container-type을 적용하면 해당 요소는 컨테이너가 되며, 너비 변화만을 추적하고 싶다면 inline-size를, 높이까지 추적하고자 하면 size를 사용하면 된다. 일반적인 반응형 디자인은 너비 변화에 따르기 때문에 container-type: inline-size가 가장 많이 사용된다.
// css
.root {
container-type: inline-size;
}
.description {
font-family: Pretendard;
font-size: 3.6rem;
}
@container (max-width: 700px) {
.description {
font-size: 2.4rem;
}
}
// component
return (
<div className={styles.root}>
<p className={styles.description}>{ ... }</p>
</div>
)
따로 선언하지 않는 경우 컨테이너 쿼리는 언제나 가장 가까운 조상 컨테이너 요소를 기준으로 스타일을 처리한다. 만약 가장 가까운 조상이 아닌 특정 컨테이너 요소를 기준으로 하고싶다면, container-name을 선언할 수 있다. 아래의 예시에서 description 클래스는 가장 가까운 조상 컨테이너 요소인 someWeirdContainer를 기준으로 하는 것이 아니라, 따로 선언된 profile-container를 이름으로 하는 root 클래스를 기준으로 스타일을 처리하게 된다.
// css
.root {
container-name: profile-container;
container-type: inline-size;
}
.someWeirdContainer {
container-type: inline-size;
}
.description {
font-family: Pretendard;
font-size: 3.6rem;
}
@container profile-container (max-width: 700px) {
.description {
font-size: 2.4rem;
}
}
// component
return (
<div className={styles.root}>
<div className={styles.someWeirdContainer}>
<p className={styles.description}>{ ... }</p>
</div>
</div>
)
CSS나 ModuleCSS에서는 위의 문법대로 사용하면 되고, Styled-Component나 Vanilla-Extract에서는 ─ 어차피 런타임에 문자열이나 객체가 CSS로 전환되는 형식이라 ─ 나름의 문법을 통해 컨테이너 쿼리를 지원한다. 테일윈드는 공식적으로 컨테이너 쿼리를 지원하고 있지 않지만 필요하다면 플러그인의 형식으로 추가할 수 있다. 하지만 플러그인을 사용해도 container-name을 사용할 수는 없는 듯하다.
// Styled-Component
import styled from 'styled-components';
const Container = styled.div`
container-type: size;
container-name: containerName,
width: 100%;
height: 100%;
max-width: 1200px;
margin: 0 auto;
`;
const ResponsiveText = styled.p`
fontSize: 16px,
@container containerName (min-width: 500px) {
font-size: 20px,
}
@container containerName (min-width: 800px) {
font-size: 24px,
}
`;
// Vanilla Extract
import { style } from '@vanilla-extract/css';
export const container = style({
containerType: 'inline-size',
containerName: 'containerName',
width: '100%',
padding: '20px',
});
export const responsiveText = style({
fontSize: '16px',
'@container containerName': {
'(min-width: 500px)': {
fontSize: '20px',
},
'(min-width: 800px)': {
fontSize: '24px',
},
},
});
블로그의 정보
Ayden's journal
Beard Weard Ayden