본문으로 건너뛰기

CSS Feature Query

무료2018-08-18#CSS#CSS特性查询#特性查询最佳实践#@supports best practice#CSS Grid polyfill#Modernizer vs CSS Feature Query

Grid 레이아웃에 polyfill이 있나요?

1. 역할

media query(미디어 쿼리)와 유사하게, feature query(기능 쿼리)도 조건부 스타일의 일종으로, 특정 스타일 규칙을 지원하는 환경에서만 지정된 스타일 세트를 적용합니다.

The @supports CSS at-rule lets you specify declarations that depend on a browser's support for one or more specific CSS features. This is called a feature query.

잠깐만요, 이런 능력은 CSS가 처음부터 가지고 있었던 것 같습니다:

새로운 속성과 값이 향후 기존 속성에 추가될 수 있도록 하기 위해, 사용자 에이전트는 알 수 없는 속성을 포함한 선언, 유효하지 않은 값을 포함한 선언, 알 수 없는 @키워드를 포함한 @규칙 등 유효하지 않은 스타일 시트의 일부를 무시해야 합니다.

P.S. 자세한 내용은 4.2 구문 분석 오류 처리 규칙을 참조하세요.

CSS는 설계 초기부터 결함 허용(fault-tolerant) 방식으로 만들어졌으며, 현재 환경에서 지원되는 스타일 규칙은 올바르게 적용되고 지원되지 않는 규칙은 자동으로 무시됩니다:

Browsers simply skip over code they don’t understand, without throwing an error.

예를 들어 자주 볼 수 있는 예시입니다:

.card {
  margin: 10px;
  border: 1px solid #ddd;
  box-shadow: 3px 5px 5px #eee;
}

우리는 box-shadow를 지원하는 환경에서는 그림자가 나타나 공중에 떠 있는 카드처럼 보이고, 지원하지 않는 환경에서는 마진과 테두리만 남아 평평한 일반 직사각형 블록으로 보인다는 것을 알고 있습니다. 이는 자연스러운 스타일 폴백(fallback)입니다. 이러한 포용 능력 덕분에 새로운 기능을 적용할 때의 우려가 줄어듭니다(지원이 안 되면 그냥 폴백 방안으로 돌아가면 되니까요).

그렇다면, feature query는 어떤 능력을 가져다줄까요?

내장된 친화적인 점진적 향상(progressive enhancement) 메커니즘과 같습니다. 이전에는 보통 Modernizr를 사용하여 처리하던 일들에 대해 이제 또 다른 선택지가 생겼습니다. 예를 들어:

Override one layout method with another.

이전과의 차이점은 영향 범위에 있습니다. 결함 허용 폴백은 해당 스타일을 지원하지 않는 요소에만 영향을 미치는 반면, 기능 쿼리 폴백은 임의의 요소 그룹에 영향을 미칠 수 있습니다. 예를 들어:

.card {
  margin: 10px;
  border: 1px solid #ddd;
}
@supports (box-shadow: 3px 5px 5px #eee) {
  /* 다른 요소에 영향을 미침, 이전의 CSS 결함 허용 폴백으로는 불가능했던 방식 */
  body:before {
    content: 'box-shadow is supported!';
    background-color: green;
  }

  .card {
    box-shadow: 3px 5px 5px #eee
  }
}

P.S. 엄밀히 말하면, 결함 허용 폴백은 일반적으로 선언 레벨(일부 선언이 무시되거나 적용됨)에서 이루어지지만, 기능 쿼리 폴백은 규칙 세트 레벨(일부 규칙 세트가 무시되거나 적용됨)에서 이루어집니다:

선언은 비어 있거나 콜론(:)과 속성값이 뒤따르는 속성명으로 구성되며, 그 사이에는 공백 문자가 올 수 있습니다.

규칙 세트('규칙'이라고도 함)는 선언 블록이 뒤따르는 선택자로 구성됩니다.

2. 문법

문법 측면에서 feature query는 @supports CSS at-rule이라고 불리며, media query와 비교하면 다음과 같습니다:

@supports (display: grid) {
  div {
    display: grid;
  }
}

@media screen and (min-width: 900px) {
  article {
    padding: 1rem 3rem;
  }
}

매우 비슷하게 생겼으며, 둘 다 @키워드 뒤의 조건이 충족될 때 {...} 내부의 스타일 규칙을 적용한다는 것을 의미합니다. 둘 다 조건부 그룹 규칙(conditional group rules)입니다:

/* 일반적인 구조 */
@IDENTIFIER (RULE) {/* CSS block */}

/* @supports CSS at-rule */
@supports <supports-condition> {
  <group-rule-body>
}

또한 조건 부분은 AND(and), OR(or), NOT(not) 논리 연산을 지원합니다:

@supports (display: grid) and (not (display: inline-grid))
@supports (transform-style: preserve) or (-moz-transform-style: preserve)

P.S. 속성명-값 쌍 형식의 조건만 지원하며, 다음과 같은 다른 형식은 지원하지 않습니다:

@supports (@charset "utf-8") {
  /* 스타일 규칙 */
}

3. 사용법

실제 상황에서 일반적인 패턴(베스트 프랙티스)은 다음과 같습니다:

/* 폴백 스타일 - 저사양 환경 대상 */

@supports (최신 기능) {
  /* 향상된 스타일 - 고사양 환경 대상 */
  /* 필요한 경우 일부 폴백 스타일을 덮어씁니다 */
}

주의해야 할 점은, 특정 기능을 지원하지 않는 것이 부정 형식의 feature query(@supports not)와 동일하지 않다는 것입니다:

@supports not (height: 100vh) {
  /* vh를 지원하지 않는 환경에서만 이 스타일 규칙을 적용하기를 기대함 */
}

예상대로 vh를 지원하지 않는 환경을 걸러낼 수 없습니다. 왜냐하면 @supports 자체를 지원하지 않는다면 이 폴백 스타일을 포함한 전체 @규칙이 무시되기 때문입니다. 즉, 이 판단은 신뢰할 수 없으며 일부 환경(@supportsvh를 모두 지원하지 않는 환경)을 놓치게 됩니다.

마찬가지로, 긍정 형식의 feature query도 완전히 신뢰할 수 있는 것은 아닙니다. 예를 들어:

@supports (height: 100vh) {
  /* vh를 지원하는 환경에서만 이 스타일 규칙을 적용하기를 기대함 */
}

@supports는 지원하지 않지만 vh는 지원하는 환경에서는 예상과 다르게 동작합니다. 하지만 일반적으로 지원 여부를 판단해야 하는 기능은 feature query보다 더 최신 기능이기 때문에 보통은 크게 걱정할 필요가 없습니다.

4. 우아한 성능 저하와 점진적 향상

브라우저 간 불일치 문제에 대응하는 두 가지 유사한 전략으로, 차이점은 다음과 같습니다:

  • 우아한 성능 저하(Graceful degradation): 고사양 환경 우선, 저사양 환경에 맞게 타협(모든 특수 효과를 사용하되 저사양 환경에서는 효과를 줄임, 최소한의 가용성 보장)

  • 점진적 향상(Progressive enhancement): 저사양 환경 우선, 고사양 환경에 특별한 대우(최소한의 가용성을 먼저 보장한 후 특수 기능 추가 고려)

Graceful degradation starts complex with a goal of providing a simple experience when needed. Progressive enhancements starts simple and then adds on to that with the desired feature-rich experience.

Modernizr

Modernizr, 일반적인 기능 감지 솔루션으로, JS를 통해 실행 환경이 특정 기능을 지원���는지 확인합니다:

Modernizr checks if a feature is available in the browser and returns true or false.

요약하자면, Modernizr는 고사양 환경과 저사양 환경을 구분하는 데 도움을 주어 저사양 환경에 대해 폴백(fallback)을 제공하거나 폴리필(polyfill)을 적용할 수 있게 해줍니다. 예를 들어:

if (Modernizr.awesomeNewFeature) {
  showOffAwesomeNewFeature();
} else {
  getTheOldLameExperience();
}

JS 솔루션으로서의 장점은 CSS 기능뿐만 아니라 JS로 감지 가능한 모든 기능을 지원할 만큼 매우 유연하다는 것입니다. 예를 들어:

// 미디어 특징
var query = Modernizr.mq('(min-width: 900px)');
if (query) {
  // 브라우저 창이 900px보다 큰 경우
}
// DOM 이벤트
Modernizr.hasEvent('blur') // true;
// 플러그인 특성
Modernizr.on('flash', function( result ) {
  if (result) {
  // 브라우저에 플래시가 있음
  } else {
    // 브라우저에 플래시가 없음
  }
});

하지만 몇 가지 문제점이 있습니다:

  • 성능: 추가적인 JS를 도입해야 하며, 감지해야 할 새로운 기능이 많아질수록 파일 크기가 커져 성능 부담이 발생합니다.

  • 확장성: 제3자 지원에 의존하므로 최신 기능에 대한 감지 기능이 추가될 때까지 기다려야 할 수 있으며, 이는 (Modernizr 버전을 업데이트하는) 수동 확장을 의미합니다.

  • 사용 편의성: 대상 기능의 이름(예: batteryapi, flexbox 등)을 표에서 찾아야만 기능을 확인할 수 있어 다소 불편합니다.

  • 기능 세분화: 위에서 언급한 기능 이름을 최소 감지 단위로 사용하는 것이 항상 적절한 것은 아닙니다. 예를 들어 justify-content: space-evenly에 해당하는 특정 기능 이름이 없을 수도 있습니다.

  • 신뢰성: 보조 수단에 의존하여 기능을 감지하는 것이 100% 신뢰할 수 있는 것은 아닙니다. 예를 들어 일부만 구현된 버전의 경우 정확히 구분해내지 못할 수 있습니다.

CSS Feature Query

브라우저에 내장된 CSS 기능 감지 지원입니다. 특정 스타일 규칙을 지원하는지 여부는 브라우저 자신이 가장 잘 알고 있으며, feature query는 단지 이러한 내부 상태를 외부로 노출한 것뿐입니다.

Modernizr와 비교했을 때 몇 가지 장점이 있습니다:

  • 우수한 성능: 순수 CSS 솔루션으로 JS가 필요하지 않습니다.

  • 우수한 확장성: 브라우저의 기본 능력이므로 새로운 기능이 출시되면 즉시 감지할 수 있으며 수동 확장이 필요하지 않습니다.

  • 자연스러운 문법: 기능 이름을 표에서 찾을 필요 없이 스타일 선언을 직접 쿼리 조건으로 사용합니다.

  • 세분화: 속성명-값 쌍 단위로 지원되어 매우 유연합니다.

  • 신뢰성: 지원 여부를 브라우저가 직접 판단하므로 절대적으로 신뢰할 수 있습니다.

물론 단점은 스타일 기능 쿼리만 지원하며 CSS 이외의 기능에는 무력하다는 것입니다. 따라서 기능 면에서는 Modernizr가 CSS feature query의 상위 집합(superset)입니다.

5. 호환성

  • 데스크톱: Firefox, Chrome, [Safari 9+], [Edge 12+] ([IE 11-]은 모두 미지원)

  • 모바일: [iOS 9.0+], [Android 4.4+]

P.S. 자세한 내용은 Can I use를 참조하세요.

모바일 환경에서는 기본적으로 안심하고 사용할 수 있습니다. @supports를 지원하지 않더라도 실질적인 영향은 없으며(단지 해당 스타일 그룹을 무시할 뿐이므로 저사양 환경과 동일하게 동작함),

특별히 주의해야 할 몇 가지 사항이 있습니다:

  • feature query는 버그가 있는 기능 구현이나 일부 불완전한 기능 구현(예: 특정 메커니즘은 지원하지 않지만 속성명/값으로는 구분할 수 없는 경우)을 식별하는 데 도움이 되지 않습니다.

  • feature query 자체의 호환성 문제로 인해 일부 상황이 예상과 다르게 동작할 수 있지만(예: 특정 기능을 지원함에도 @supports를 지원하지 않아 무시되는 경우), 심각한 영향을 미치지는 않습니다.

전형적인 예로 Safari 8은 flexbox를 지원하지만 feature query를 지원하지 않아 다음과 같은 문제가 발생할 수 있습니다:

Safari 8은 Internet Explorer보다 기능 쿼리와 관련하여 더 큰 문제일 수 있습니다. Safari 8이 지원하는 최신 속성(예: Flexbox)이 많이 있습니다. 아마도 Safari 8이 이러한 속성을 사용하는 것을 막고 싶지는 않을 것입니다.

예를 들어:

body {
  background-color: red;
}

@supports (display: flex) {
  body {
    display: flex;
    background-color: green;
  }
}

Safari 8에서는 flexbox를 지원함에도 불구하고 폴백 스타일이 나타나게 됩니다.

6. 활용 사례

활용 사례 측면에서 feature query는 새로운 기능의 호환성에 대한 우려를 해결하기 위한 점진적 향상의 수단으로 사용됩니다. 일반적인 사용법은 다음과 같습니다:

/* 호환성이 보장된 스타일: 접근성 보장 */

@supports (/* 호환성이 다소 떨어지는 최신 기능 */) {
  /* 향상: 지원하는 경우 더 간단하고 효율적이며 강력한 솔루션 사용 */
}

점진적 향상은 다양한 환경에서의 불일치를 수용한다는 것을 의미합니다. 사실 그림자, 둥근 모서리, 애니메이션과 같은 효과들은 이러한 불일치를 수용하기 쉽지만(지원하지 않는 환경에서 이러한 부가적인 효과를 제거함), Flexbox나 Grid 같은 레이아웃 솔루션은 레이아웃이 보통 필수적인 요소이기 때문에 단순히 점진적 향상과 연결 짓기 어려워 보일 수 있습니다.

Grid 기능을 점진적으로 사용하기

Is There A CSS Grid Polyfill?

Grid does things that are pretty much impossible with older layout methods. So, in order to replicate Grid in browsers that don’t have support, you would need to do a lot of work in JavaScript.

안타깝게도 Grid 레이아웃에는 CSS 폴리필이 없습니다. 그렇다면 이 강력한 기능을 사용하기 위해 수년 뒤까지 기다려야만 할까요?

당연히 아닙니다. 적어도 두 가지 선택지가 있습니다:

  • FremyCompany/css-grid-polyfill과 같은 JS 폴리필 사용

  • 점진적으로 (지원이 되는 환경에서만) Grid 기능을 사용하기 (즉, 환경에 따른 레이아웃 차이를 수용하기)

JS 패치 솔루션은 더 설명할 것이 없으며, 점진적 솔루션의 핵심은 이러한 차이를 수용하는 것에 있습니다:

Websites do NOT need to look the same on every browser.

레이아웃 효과도 (그림자나 둥근 모서리처럼) 향상된 스타일의 일종으로 간주하여, 저사양 환경에서는 다른 방식의 폴백(레이아웃) 효과를 보여주는 것을 허용합니다. 예를 들어:

<div class="grid">
  <div class="one">One</div>
  <div class="two">Two</div>
  <div class="three">Three</div>
</div>

해당 스타일은 다음과 같습니다:

* { box-sizing: border-box; }

.grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-auto-rows: 100px;
  grid-gap: 20px;
}

.grid > * {
  padding: 10px;
  border: 5px solid rgba(214,129,137,.5);
  border-radius: 5px;
  background-color: rgba(233,78,119,.5);
  color: #fff;
  float: left;
  width: 33%;
}

@supports (display:grid) {
  .grid > * {
    width: auto;
  }
}

(display: feature queries demo에서 발췌)

Grid를 지원하는 환경에서는 경계가 뚜렷한 아름다운 3열 등비 레이아웃으로 보이고, 지원하지 않는 환경에서는 다소 빽빽한 float 레이아웃으로 폴백됩니다:

[caption id="attachment_1785" align="alignnone" width="625"]grid-layout-css-polyfill grid-layout-css-polyfill[/caption]

완벽하지는 않지만 구조를 변경하거나 JS의 도움을 받지 않는 상황에서 float 방식으로는 이 정도가 한계입니다. 만약 이러한 차이를 수용할 수 있다면, feature query를 통한 점진적 사용으로 새로운 기능의 호환성 문제는 더 이상 중요하지 않게 될 것입니다.

사용자 정의 속성 지원 여부 확인

@supports (--foo: green) {
  :root {
    --theme-color: gray;
  }

  .variable {
    color: var(--theme-color);
  }
}

CSS 변수를 이용하면 테마 변경 기능을 향상된 효과로 쉽게 제공할 수 있습니다.

첫 글자 드롭 캡(Drop Cap) 효과

initial-letter를 지원하는 환경(예: Safari)에서는 이러한 흔한 타이포그래피 효과(단락 첫 글자를 4행 높이로 내림)를 쉽게 구현할 수 있습니다:

@supports (initial-letter: 4) or (-webkit-initial-letter: 4) {
  p::first-letter {
    -webkit-initial-letter: 4;
    initial-letter: 4;
    color: #FE742F;
    font-weight: bold;
    margin-right: .5em;
  }
}

지원하지 않는 경우에는 일반적인 방식으로 구현합니다:

p::first-letter {
  float: left;
  font-size: 4em;
  color: #FE742F;
  font-weight: bold;
  margin-right: .5em;
}

P.S. 더 많은 사례는 참고 자료 중 feature query 관련 내용(예: mix-blend-mode 등)을 확인하세요.

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성