CSS 컨테이너 쿼리: 사용 사례 및 마이그레이션 전략
게시 됨: 2022-03-10UI 요소에 대한 미디어 쿼리를 작성할 때 항상 화면 크기에 따라 해당 요소의 스타일이 지정되는 방식을 설명합니다. 이 접근 방식은 대상 요소 미디어 쿼리의 응답성이 뷰포트 크기에만 의존해야 할 때 잘 작동합니다. 다음 반응형 페이지 레이아웃 예를 살펴보겠습니다.
그러나 반응형 웹 디자인(RWD)은 페이지 레이아웃에 국한되지 않습니다. 개별 UI 구성 요소에는 일반적으로 뷰포트 크기에 따라 스타일을 변경할 수 있는 미디어 쿼리가 있습니다.
개별 UI 구성 요소 레이아웃은 종종 뷰포트 크기에만 의존하지 않는 경우가 많습니다. 페이지 레이아웃은 뷰포트 치수와 밀접하게 연결된 요소이며 HTML의 최상위 요소 중 하나인 반면 UI 구성 요소는 다양한 컨텍스트 및 컨테이너에서 사용할 수 있습니다. 생각해보면 뷰포트는 컨테이너일 뿐이며 UI 구성 요소는 구성 요소의 크기와 레이아웃에 영향을 주는 스타일을 사용하여 다른 컨테이너 내에 중첩될 수 있습니다.
동일한 제품 카드 구성 요소가 상단 및 하단 섹션 모두에서 사용되지만 구성 요소 스타일은 뷰포트 크기에 따라 달라질 뿐만 아니라 해당 구성 요소가 배치되는 컨텍스트 및 컨테이너 CSS 속성(예: 그리드)에 따라 달라집니다.
물론 레이아웃 문제를 수동으로 해결하기 위해 다양한 컨텍스트 및 컨테이너에 대한 스타일 변형을 지원하도록 CSS를 구성할 수 있습니다. 최악의 시나리오에서는 이 변형이 스타일 재정의와 함께 추가되어 코드 중복 및 특정성 문제로 이어집니다.
.product-card { /* Default card style */ } .product-card--narrow { /* Style variation for narrow viewport and containers */ } @media screen and (min-width: 569px) { .product-card--wide { /* Style variation for wider viewport and containers */ } }
그러나 이것은 적절한 솔루션이라기 보다는 미디어 쿼리의 한계에 대한 해결 방법에 가깝습니다. UI 요소에 대한 미디어 쿼리를 작성할 때 대상 요소가 레이아웃이 깨지지 않는 최소 치수를 가질 때 중단점에 대한 "마법의" 뷰포트 값을 찾으려고 합니다. 간단히 말해서 "마법 같은" 뷰포트 치수 값을 요소 치수 값에 연결합니다 . 이 값은 일반적으로 뷰포트 치수와 다르며 내부 컨테이너 치수 또는 레이아웃이 변경될 때 버그가 발생하기 쉽습니다.
다음 예는 이 정확한 문제를 보여줍니다. 반응형 제품 카드 요소가 구현되었고 표준 사용 사례에서는 좋아 보이지만 요소 크기에 영향을 미치는 CSS 속성이 있는 다른 컨테이너로 이동하면 깨진 것처럼 보입니다. 각 추가 사용 사례에는 추가 CSS 코드 를 추가해야 하므로 코드 중복, 코드 팽창 및 유지 관리가 어려운 코드가 발생할 수 있습니다.
이것은 컨테이너 쿼리가 수정을 시도하는 문제 중 하나입니다. 컨테이너 쿼리는 대상 요소 차원에 의존하는 쿼리로 기존 미디어 쿼리 기능을 확장 합니다. 이 접근 방식을 사용하면 세 가지 주요 이점이 있습니다.
- 컨테이너 쿼리 스타일은 대상 요소 자체의 차원에 따라 적용됩니다. UI 구성 요소는 주어진 컨텍스트 또는 컨테이너에 적응할 수 있습니다.
- 개발자는 뷰포트 미디어 쿼리를 특정 컨테이너 또는 특정 컨텍스트에 있는 UI 구성요소의 대상 차원에 연결하는 "매직 넘버" 뷰포트 차원 값을 찾을 필요가 없습니다.
- 다른 컨텍스트 및 사용 사례에 대해 추가 CSS 클래스 또는 미디어 쿼리를 추가할 필요가 없습니다.
"이상적인 반응형 웹사이트는 다양한 상황에서 서비스하도록 용도를 변경할 수 있는 유연한 모듈식 구성 요소 시스템입니다."
— "Container Queries: Once More Unto the Breach", Mat Marquis
컨테이너 쿼리에 대해 자세히 알아보기 전에 브라우저 지원을 확인하고 브라우저에서 실험 기능을 활성화하는 방법을 확인해야 합니다.
브라우저 지원
컨테이너 쿼리는 이 문서를 작성하는 시점에 현재 Chrome Canary 버전에서 사용할 수 있는 실험적인 기능입니다. 이 문서의 CodePen 예제를 따르고 실행하려면 다음 설정 URL에서 컨테이너 쿼리를 활성화해야 합니다.
chrome://flags/#enable-container-queries
컨테이너 쿼리를 지원하지 않는 브라우저를 사용하는 경우 의도한 작업 예제를 보여주는 이미지가 CodePen 데모와 함께 제공됩니다.
컨테이너 쿼리 작업
컨테이너 쿼리는 일반 미디어 쿼리만큼 간단하지 않습니다. 컨테이너 쿼리가 작동하도록 하려면 CSS 코드 줄을 UI 요소에 추가해야 하지만 그 이유는 다음에서 다루도록 하겠습니다.
격리 속성
CSS contain
속성은 대부분의 최신 브라우저에 추가되었으며 이 기사를 작성하는 시점에 적절한 75% 브라우저 지원을 제공합니다. contain
속성은 페이지의 어떤 부분(하위 트리)이 독립적으로 처리될 수 있고 트리의 다른 요소에 대한 변경 사항에 영향을 미치지 않을 것인지를 브라우저에 암시함으로써 성능 최적화에 주로 사용됩니다. 그렇게 하면 단일 요소에서 변경이 발생하면 브라우저는 전체 페이지 대신 해당 부분(하위 트리)만 다시 렌더링합니다. contain
속성 값을 사용하여 layout
, size
또는 paint
와 같이 사용하려는 포함 유형을 지정할 수 있습니다.
사용 가능한 옵션과 사용 사례를 훨씬 더 자세히 설명하는 contain
속성에 대한 훌륭한 기사가 많이 있으므로 컨테이너 쿼리와 관련된 속성에만 초점을 맞추겠습니다.
최적화에 사용되는 CSS 콘텐츠 속성은 컨테이너 쿼리와 어떤 관련이 있습니까? 컨테이너 쿼리가 작동하려면 브라우저는 요소의 자식 레이아웃에 변경 사항이 발생하는지 알아야 해당 구성 요소만 다시 렌더링해야 합니다. 브라우저는 구성 요소가 렌더링되거나 구성 요소의 차원이 변경될 때 일치하는 구성 요소에 컨테이너 쿼리의 코드를 적용하는 것을 알게 됩니다.
contain
에 대한 layout
style
을 사용하지만 변경이 발생할 축에 대해 브라우저에 신호를 보내는 추가 값도 필요합니다.
-
inline-size
인라인 축의 제약. 이 값에는 훨씬 더 많은 사용 사례가 있을 것으로 예상되므로 먼저 구현됩니다. -
block-size
블록 축에 포함. 아직 개발 중이며 현재 사용할 수 없습니다.
contain
속성의 사소한 단점 중 하나는 레이아웃 요소가 contain
요소의 자식이어야 한다는 것입니다. 즉, 추가 중첩 수준을 추가해야 합니다.
<section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
.card { contain: layout inline-size style; } .card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ }
이 값을 더 먼 부모와 같은 section
에 추가하지 않고 컨테이너를 영향을 받는 요소에 최대한 가깝게 유지하는 방법에 주목하세요.
“성과는 일을 피하고 당신이 하는 모든 일을 가능한 한 효율적으로 만드는 기술입니다. 대부분의 경우 브라우저에 대한 것이 아니라 브라우저와 함께 작업하는 것입니다."
— "렌더링 성능", Paul Lewis
그렇기 때문에 브라우저에 변경 사항에 대해 올바르게 신호를 보내야 합니다. 멀리 있는 부모 요소를 contain
속성으로 감싸는 것은 비생산적일 수 있고 페이지 성능에 부정적인 영향을 미칠 수 있습니다. contain
속성을 오용하는 최악의 시나리오에서는 레이아웃이 깨질 수도 있고 브라우저가 올바르게 렌더링하지 않을 수도 있습니다.
컨테이너 쿼리
contain
속성이 카드 요소 래퍼에 추가된 후 컨테이너 쿼리를 작성할 수 있습니다. card
클래스가 있는 요소에 contain
속성을 추가했으므로 이제 컨테이너 쿼리에 자식 요소를 포함할 수 있습니다.
일반 미디어 쿼리와 마찬가지로 min-width
또는 max-width
속성을 사용하여 쿼리를 정의하고 블록 내부에 모든 선택기를 중첩해야 합니다. 그러나 컨테이너 쿼리를 정의하기 위해 @media
대신 @container
키워드를 사용할 것입니다.
@container (min-width: 568px) { .card__wrapper { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { min-width: auto; height: auto; } }
card__wrapper
및 card__image
요소는 모두 contain
속성이 정의된 card
요소의 자식입니다. 일반 미디어 쿼리를 컨테이너 쿼리로 바꾸고, 좁은 컨테이너에 대한 추가 CSS 클래스를 제거하고, 컨테이너 쿼리를 지원하는 브라우저에서 CodePen 예제를 실행하면 다음과 같은 결과를 얻습니다.
컨테이너 쿼리는 현재 Chrome 개발자 도구에 표시되지 않으므로 컨테이너 쿼리 디버깅이 약간 어렵습니다. 향후 적절한 디버깅 지원이 브라우저에 추가될 것으로 예상됩니다.
컨테이너 쿼리를 통해 거의 모든 컨테이너 및 레이아웃에 적용할 수 있는 보다 강력하고 재사용 가능한 UI 구성 요소를 만드는 방법을 확인할 수 있습니다. 그러나 컨테이너 쿼리에 대한 적절한 브라우저 지원은 기능에서 아직 멀었습니다. 점진적 향상을 사용하여 컨테이너 쿼리를 구현할 수 있는지 살펴보겠습니다.
점진적 향상 및 폴리필
CSS 클래스 변형 및 미디어 쿼리에 대체를 추가할 수 있는지 봅시다. @supports
규칙과 함께 CSS 기능 쿼리를 사용하여 사용 가능한 브라우저 기능을 감지할 수 있습니다. 그러나 다른 쿼리는 확인할 수 없으므로 contain: layout inline-size style
값에 대한 확인을 추가해야 합니다. inline-size
속성을 지원하는 브라우저는 컨테이너 쿼리도 지원한다고 가정해야 합니다.
/* Check if the inline-size value is supported */ @supports (contain: inline-size) { .card { contain: layout inline-size style; } } /* If the inline-size value is not supported, use media query fallback */ @supports not (contain: inline-size) { @media (min-width: 568px) { /* ... */ } } /* Browser ignores @container if it's not supported */ @container (min-width: 568px) { /* Container query styles */ }
그러나 이 접근 방식은 컨테이너 쿼리와 미디어 쿼리 모두에 동일한 스타일이 적용되기 때문에 스타일이 중복될 수 있습니다. 점진적 향상으로 컨테이너 쿼리를 구현하기로 결정했다면 SASS와 같은 CSS 전처리기 또는 PostCSS와 같은 후처리기를 사용하여 코드 블록 복제를 피하고 대신 CSS 믹스인 또는 다른 접근 방식을 사용하고 싶을 것입니다.
이 컨테이너 쿼리 사양은 아직 실험 단계이므로 사양 또는 구현이 향후 릴리스에서 변경될 가능성이 있다는 점을 염두에 두는 것이 중요합니다.
또는 폴리필을 사용하여 안정적인 대체를 제공할 수 있습니다. 강조하고 싶은 두 가지 JavaScript 폴리필이 있습니다. 현재 활발하게 유지 관리되고 있으며 필요한 컨테이너 쿼리 기능을 제공합니다.
- 조나단 닐
cqfill
CSS 및 PostCSS용 JavaScript 폴리필 - Chris Garcia
react-container-query
React용 커스텀 후크 및 컴포넌트
미디어 쿼리에서 컨테이너 쿼리로 마이그레이션
미디어 쿼리를 사용하는 기존 프로젝트에서 컨테이너 쿼리를 구현하기로 결정했다면 HTML 및 CSS 코드를 리팩토링해야 합니다. 이것이 미디어 쿼리에 대한 안정적인 대체를 제공하면서 컨테이너 쿼리를 추가하는 가장 빠르고 간단한 방법이라는 것을 알았습니다. 이전 카드 예를 살펴보겠습니다.
<section> <div class="card__wrapper card__wrapper--wide"> <!-- Wide card content --> </div> </section> /* ... */ <aside> <div class="card__wrapper"> <!-- Narrow card content --> </div> </aside>
.card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ } .card__image { /* ... */ } @media screen and (min-width: 568px) { .card__wrapper--wide { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { /* ... */ } }
먼저 미디어 쿼리가 적용된 루트 HTML 요소를 contain
속성이 있는 요소로 래핑합니다.
<section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
@supports (contain: inline-size) { .card { contain: layout inline-size style; } }
다음으로 미디어 쿼리를 기능 쿼리로 래핑하고 컨테이너 쿼리를 추가합니다.
@supports not (contain: inline-size) { @media (min-width: 568px) { .card__wrapper--wide { /* ... */ } .card__image { /* ... */ } } } @container (min-width: 568px) { .card__wrapper { /* Same code as .card__wrapper--wide in media query */ } .card__image { /* Same code as .card__image in media query */ } }
이 방법을 사용하면 일부 코드가 부풀려지고 코드가 중복되지만 SASS 또는 PostCSS를 사용하면 개발 코드 중복을 방지할 수 있으므로 CSS 소스 코드를 유지 관리할 수 있습니다.
컨테이너 쿼리가 적절한 브라우저 지원을 받으면 @supports not (contain: inline-size)
코드 블록을 제거하고 계속해서 컨테이너 쿼리를 독점적으로 지원하는 것을 고려할 수 있습니다.
Stephanie Eckles는 최근 다양한 마이그레이션 전략을 다루는 컨테이너 쿼리에 대한 훌륭한 기사를 게시했습니다. 주제에 대한 자세한 내용은 확인하는 것이 좋습니다.
사용 사례 시나리오
이전 예에서 보았듯이 컨테이너 쿼리는 사용 가능한 컨테이너 공간에 따라 달라지고 다양한 컨텍스트에서 사용되고 페이지의 다른 컨테이너에 추가될 수 있는 레이아웃이 있는 재사용성이 높은 구성 요소에 가장 잘 사용됩니다.
다른 예에는 다음이 포함됩니다(예시에는 컨테이너 쿼리를 지원하는 브라우저가 필요함).
- 카드, 양식 요소, 배너 등과 같은 모듈식 구성 요소
- 적응 가능한 레이아웃
- 모바일과 데스크탑을 위한 다양한 기능의 페이지 매김
- CSS 크기 조정으로 재미있는 실험
결론
사양이 구현되고 브라우저에서 광범위하게 지원되면 컨테이너 쿼리가 판도를 바꾸는 기능이 될 수 있습니다. 이를 통해 개발자는 멀리 떨어져 있고 거의 관련이 없는 뷰포트 미디어 쿼리를 사용하는 대신 관련 구성 요소에 더 가깝게 쿼리를 이동하여 구성 요소 수준에서 쿼리를 작성할 수 있습니다. 이를 통해 다양한 사용 사례, 레이아웃 및 컨테이너에 적응할 수 있는 더 강력하고 재사용 가능하며 유지 관리 가능한 구성 요소가 생성됩니다.
컨테이너 쿼리는 아직 초기 실험 단계에 있으며 구현이 변경되기 쉽습니다. 지금 프로젝트에서 컨테이너 쿼리 사용을 시작하려면 기능 감지와 함께 점진적 향상을 사용하여 쿼리를 추가하거나 JavaScript 폴리필을 사용해야 합니다. 두 경우 모두 코드에 약간의 오버헤드가 발생하므로 이 초기 단계에서 컨테이너 쿼리를 사용하기로 결정했다면 기능이 널리 지원되면 코드 리팩토링을 계획해야 합니다.
참고문헌
- David A. Herron의 "컨테이너 쿼리: 빠른 시작 가이드"
- Ahmad Shadeed, "CSS 컨테이너 쿼리에 인사하기"
- "Chrome 52의 CSS 포함" Paul Lewis
- "CSS Contain 속성으로 브라우저 최적화 지원" Rachel Andrew