04 — Filter & backdrop-filter
한 줄 답:
filter는 요소 자신의 픽셀을,backdrop-filter는 요소 뒤의 픽셀을 GPU에서 변환한다. 둘 다 합성 단계에 처리되어 빠르지만,backdrop-filter는 뒤 영역을 매 프레임 다시 raster화하기 때문에 iOS Safari에서 자주 떨어진다. Filter는 stacking context를 만들고position: fixed자식을 잘 망가뜨리는 부작용이 있다.
Why — 왜 filter가 별도 도구인가
box-shadow·opacity 같은 시각 효과는 그릴 때 결정된다 (paint 단계). 그런데 다음과 같은 효과는 이미 그려진 픽셀을 변환해야 한다:
- 사진을 흑백으로
- 모달 뒤 배경 흐리게 (frosted glass)
- 비활성 버튼 채도 낮추기
- SVG 아이콘 색만 바꾸기
이걸 paint 단계에서 처리하면 매번 다시 그려야 한다. filter는 합성 단계에 들어와서 — 그린 결과 위에 GPU 셰이더로 변환을 건다. 그래서 transition·animation이 60fps 유지된다.
How — Filter 파이프라인
| 도구 | 변환 대상 | 비용 |
|---|---|---|
filter | 요소 + 자식의 합성 결과 | blur는 비용이 blur² 비례 |
backdrop-filter | 요소 뒤의 합성된 픽셀 | 매 프레임 뒤 영역 raster, 상당히 비쌈 |
<svg><filter> | SVG 그래픽 | 가장 복잡, 가장 강력 |
What — Filter 함수 10가지
CSS filter 함수
/* 단일 */
filter: blur(8px);
filter: brightness(1.5);
filter: contrast(150%);
filter: grayscale(100%);
filter: hue-rotate(90deg);
filter: invert(1);
filter: opacity(0.5);
filter: saturate(2);
filter: sepia(1);
filter: drop-shadow(0 4px 8px rgb(0 0 0 / 0.2));
/* 합성 (왼쪽부터 순서대로) */
filter: contrast(120%) saturate(1.2) brightness(0.9);| 함수 | 단위 | 효과 |
|---|---|---|
blur(r) | px | 가우시안 블러, 반경 r |
brightness(p) | 숫자/% | 1=원본, >1=밝게, <1=어둡게 |
contrast(p) | 숫자/% | 1=원본 |
grayscale(p) | 0~1/% | 1=완전 흑백 |
hue-rotate(a) | deg | 색상환 회전 |
invert(p) | 0~1/% | 1=완전 반전 |
opacity(p) | 0~1/% | CSS opacity와 거의 동일, 합성 단계 |
saturate(p) | 숫자/% | 1=원본, 0=흑백, >1=과채도 |
sepia(p) | 0~1/% | 1=완전 세피아 |
drop-shadow(...) | shadow | 알파 채널 기준 그림자 |
url(#filter) | SVG ref | 커스텀 SVG filter |
backdrop-filter — Frosted Glass
.glass {
background: rgb(255 255 255 / 0.2);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%); /* iOS Safari */
}배경의 뒤 영역에 filter를 건다. macOS Big Sur 이후 glassmorphism의 핵심 기법.
SVG Filter — 가장 강력
<svg width="0" height="0" style="position:absolute">
<filter id="liquid">
<feTurbulence baseFrequency="0.02" numOctaves="3" />
<feDisplacementMap in="SourceGraphic" scale="30" />
</filter>
</svg>
<div style="filter: url(#liquid)">왜곡된 텍스트</div>SVG filter는 primitive 그래프를 직접 짜는 방식. feGaussianBlur·feColorMatrix·feTurbulence·feDisplacementMap 등 30+ primitive 조합으로 Photoshop 수준의 효과가 가능. 단, 성능과 호환성 비용이 크다.
자주 쓰는 패턴
1) Disabled 효과
.btn:disabled {
filter: grayscale(0.6) opacity(0.6);
cursor: not-allowed;
}색을 따로 정의하지 않고 원본을 변환. 디자인 토큰 수가 절반으로 줄어든다.
2) Hover Saturate
.card img {
filter: saturate(0.8);
transition: filter 0.3s;
}
.card:hover img {
filter: saturate(1.1);
}saturate transition은 합성 단계라 부드럽다.
3) Frosted Glass Modal
.modal-backdrop {
position: fixed; inset: 0;
background: rgb(0 0 0 / 0.3);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}4) SVG 아이콘 색을 CSS color로
.icon {
width: 24px; height: 24px;
background: currentColor;
-webkit-mask: url(/icon.svg) center / contain no-repeat;
mask: url(/icon.svg) center / contain no-repeat;
}엄밀히는 filter가 아니라 mask지만, SVG의 색을 CSS color로 제어하는 모던 패턴. filter hue-rotate로는 어색하다.
5) drop-shadow for SVG/투명 PNG
.cutout {
clip-path: polygon(...);
filter: drop-shadow(0 8px 16px rgb(0 0 0 / 0.2));
}03-shadows에서 본 알파 채널 기반 그림자.
What-if — 잘못 쓰면
1) Filter는 stacking context를 만든다
.parent { filter: blur(0); } /* 0이라도 적용되면 stacking context 생성 */
.parent .modal {
position: fixed; /* 의도: 뷰포트 기준 */
top: 0;
/* 실제: parent 기준으로 잡힘 → 모달이 부모 안에 갇힘 */
}filter·backdrop-filter·transform·will-change·isolation: isolate는 모두 stacking context + containing block을 만든다. position: fixed 자식이 부모 기준으로 contain되는 함정이 있다 → 자세히는 05-blend-modes, 00-foundations/05-position-context.
2) backdrop-filter의 iOS Safari 떨림
- GPU 비용: 매 프레임 뒤 영역을 다시 raster해야 한다.
- iOS Safari: 스크롤 중 backdrop-filter가 떨어졌다 돌아오는 현상이 빈번. 작은 영역 + blur 반경 작게 + 스크롤 위 고정은 상대적으로 안정.
- prefix: iOS Safari는
-webkit-backdrop-filter도 같이 적어야 한다. isolation: isolate우회: 부모에isolation: isolate를 주면 일부 Safari 버그 회피 가능.
.glass {
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
@supports not (backdrop-filter: blur(20px)) {
.glass { background: rgb(255 255 255 / 0.85); } /* 불투명 fallback */
}3) Filter blur의 페인트 비용
/* 전체 화면 blur — 비쌈 */
.bg { width: 100vw; height: 100vh; filter: blur(40px); }
/* 트릭: 작게 그리고 scale */
.bg {
width: 200px; height: 200px;
background: url(...) 0 0 / cover;
filter: blur(20px);
transform: scale(10);
transform-origin: top left;
}blur 비용은 raster 크기 × blur²에 비례. 작게 그리고 transform scale로 키우는 게 사실상 표준.
4) drop-shadow 다중 적용 시 차이
/* 같은 모양에 두 개 그림자 — filter는 chain */
filter: drop-shadow(0 2px 4px rgb(0 0 0 / 0.1))
drop-shadow(0 8px 16px rgb(0 0 0 / 0.05));box-shadow는 쉼표로 합성하지만, filter는 함수 chain. 순서가 의미 있다 (다음 함수의 입력이 이전 함수의 출력).
5) forced-colors 모드와 filter
Windows 고대비 모드에서는 대부분의 filter가 무시된다. 의미 전달을 filter에만 의존하면 접근성 실패. @media (forced-colors: active)에서 대체 시각 단서를 준비해야 한다.
6) filter: contrast + filter: brightness 트릭 — Gooey
.gooey {
filter: contrast(20) blur(5px);
}
.gooey > * {
background: black;
filter: blur(10px);
}대비를 극도로 높이고 blur를 합치면 물방울이 합쳐지는 듯한 gooey 효과. SVG feGaussianBlur + feColorMatrix로도 같은 효과 — 모던 사이트의 인터랙티브 텍스트 트랜지션에 종종.
Insight — Filter의 GPU 시대
“CSS Filter는 SVG Filter의 후예다”
filter 명세는 2014년 SVG Filter Effects를 CSS에 들이는 작업으로 시작됐다. 그래서 filter: url(#svgFilter)로 SVG primitive를 그대로 쓸 수 있는 것이다 — CSS는 그 위에 흔히 쓰는 10개를 단축 함수로 노출한 셈.
backdrop-filter는 더 최근(2017 ED) 표준이다. 흥미로운 점은 Apple이 가장 먼저 구현하고 가장 먼저 망쳤다는 것 — macOS의 모든 메뉴바·Dock·Notification Center가 backdrop-filter를 OS 수준에서 쓰는데, 브라우저 구현은 그것의 1/10 성능. iOS Safari의 떨림은 batter life 절약을 위해 GPU 합성 우선순위가 낮은 게 원인이라는 설.
2024년 분기점은 color() 함수 + filter의 P3 호환이다. 이전엔 filter가 sRGB로 강제 변환되어 광역색이 손실됐다. 이제 P3 입력 → P3 합성 → P3 출력이 가능해져, 광역색 사진의 채도가 filter를 통과해도 살아남는다.
또 하나의 반전: filter: opacity(0.5)와 opacity: 0.5는 거의 동일하다. 명세도 효과는 동일하다고 명시. 차이는 어디서 합성되느냐 — filter는 filter chain의 일부로, opacity는 별도 합성 속성으로. 모바일 GPU에서는 opacity 단독이 약간 더 빠르다는 측정도 있다.
요약 + Mermaid
filter는 요소 자신,backdrop-filter는 요소 뒤를 변환.- 합성 단계 처리 — paint 단계 효과보다 빠름.
- iOS Safari
backdrop-filter는 스크롤 떨림·성능 저하 — prefix +@supportsfallback 필수. - Filter는 stacking context를 만든다 →
position: fixed자식 함정. - Blur 비용은 blur² 비례 — 작게 그리고
scale로 키우는 트릭. - SVG filter는 Photoshop 수준 가능, 성능·호환성 비용 큼.
다음: 05-blend-modes — filter가 요소 안의 변환이라면 blend는 요소들 사이의 합성이다.