Blur craft
웹상의 대부분 유리 효과는 세 가지 이유로 싸구려로 보입니다. 유리 뒤에 흥미로운 것이 없거나, 소재가 단순한 색조에 하나의 블러만 적용되어 있거나, 그 위의 텍스트가 아무도 측정하지 않은 탁한 회색이기 때문입니다. 고급스러워 보이는 유리(Apple의 소재, visionOS, 최고의 제품 사이트)는 다음 세 가지를 올바르게 처리합니다. 순서대로 적용하세요:
- 유리 뒤의 빛이 설계되어 있습니다. 유리는 뒤에 있는 것을 드러내므로, 그곳에 빛을 배치하세요.
- 유리는 색조가 아닌 소재입니다. 채도가 높은 블러, 그라데이션 채우기, 테두리 조명, 반짝임, 깊이감, 질감.
assets/glass.css를 참조하세요. - 텍스트가 측정되었습니다. 반투명 유리는 뒤의 모든 픽셀에 따라 대비가 변하므로, 스크립트를 통해 최악의 지점에서 대비를 확인합니다. 세 가지 상태로 확인합니다(
scripts/glass-contrast.mjs).
빠른 경로
| 만들고 있는 대상… | 사용 | 참고 |
|---|---|---|
| 스티키 내비게이션 바 | .gs-bar (surfaces.css) | 확장 배경; gs-bar--on-scroll을 추가하면 스크롤 후에만 유리 효과 적용 |
| 아트 위의 플레이어, 독 또는 필 | .gs-dock.glass.glass--clear + liquid-glass.js | 투명 액체 유리, 화면당 하나; 아트가 많이 변하면 적응형 톤 |
| 텍스트가 있는 플로팅 카드 또는 패널 | .glass.glass--regular | 디테일한 사진 위에서는 thick |
| 메뉴, 팝오버 또는 명령어 바 | .gs-menu.glass | 32px, 약 86% 불투명도, 0.5px 상단 하이라이트 |
| 다이얼로그 | .gs-veil + .gs-dialog.glass.glass--thick | 결제, 삭제, 서명 시 glass--solid; 유리 크롬이 아래에 있을 때는 gs-veil--flat + .gs-modal-open |
| 토스트 또는 바텀 시트 | .gs-toast.glass.glass--regular / .gs-sheet.glass.glass--thick | gs- 클래스는 레이아웃 전용; 소재는 .glass에서 옴 |
| 바 아래로 사라지는 콘텐츠 | 프로그레시브 블러 (레시피 2) | 뷰당 하나의 스크롤-가장자리 효과 |
| 나타나는 헤드라인 또는 변경되는 값 | 블러 리빌 (4) / 2px 크로스페이드 (4b) | 화면당 한 번 |
| 아트의 색상으로 채워진 페이지 | 아트 배경, 베이크드 (레시피 8) | 고정 유리 아래에서는 절대 라이브 블러 사용 금지 |
| Tailwind v4 프로젝트 | glass.css + glass.tailwind.css (glass-blur-24, …) | Tailwind의 자체 backdrop-blur-*는 Sonoma Safari에서 깨짐 |
| 네이티브 앱 (SwiftUI, Flutter, React Native, Compose) | references/native.md | 동일한 방법, 각 툴킷의 소재 |
| 사진, 비디오 또는 디자인 편집기의 크롬 | .glass.glass--thick.glass--neutral | 중립: 채도 증가 없음, 유리가 작업물을 변색시켜선 안 됨 |
그런 다음: 빛을 구성하고(§1), 소재를 설정하고(§2), 확인하세요(§5). assets/demo.html은 모든 표면을 컨트롤과 함께 라이브로 보여줍니다; 선택하기 전에 각 클래스가 무엇을 하는지 확인하려면 열어보세요.
1. 먼저 빛을 구성하세요
각 유리 표면 바로 뒤에 무엇이 올지 결정하고, 그곳을 가장 빛나는 영역으로 만드세요.
-
유리 아래에 빛을 배치하세요: 글로우, 아트 속의 태양, 제품의 색상, 라이브 WebGL 컴포넌트. 블러 처리된 단색은 여전히 단색일 뿐입니다; 빈 공간 위의 유리는 회색 상자에 불과합니다.
-
빛이 가장자리를 가로지르게 하세요. 최고의 유리는 절반은 빛 위에, 절반은 어둠 위에 놓여 색상이 폭에 따라 변합니다.
-
테두리 조명을 빛 방향으로 맞추세요.
--glass-light를 빛의 색상(일몰의 경우255 200 160)으로,--glass-rim-angle을 방향으로 설정하세요. 테두리의 밝은 쪽이 빛을 향합니다:빛 방향 좌측 상단 상단 우측 상단 우측 우측 하단 하단 좌측 하단 좌측 각도 135deg 180deg 225deg 270deg 315deg 0deg 45deg 90deg 유리 아래의 빛은 가장 가까운 가장자리의 각도 + 센터 방향으로 약 15deg를 취합니다. 각도는 등록된
@property이므로, 포인터나 스크롤을 따라 부드럽게 이동할 수 있습니다(Apple의 하이라이트는 빛이 이동함에 따라 소재 주변을 여행합니다). -
유리를 주인공처럼 크게 잡으세요. 유리 아래의 장면 변화를 보여줄 만큼 충분히 클 때 소재로 읽힙니다. Apple은 더 큰 유리를 더 두꺼운 유리처럼 동작하게 만듭니다: 더 깊은 그림자, 더 부드러운 산란.
2. 소재 설정
assets/glass.css(복사 시 glass 접두사 이름 변경)와 assets/surfaces.css를 미리 만들어진 표면용으로 사용하거나, 컴포넌트(Glass.tsx (React), Glass.vue (Vue 3), Glass.svelte (Svelte 5))를 사용하세요. 한 줄짜리 backdrop-filter 카드를 직접 작성하지 마세요.
backdrop-filter를 리터럴 값으로 작성하고, var()를 절대 사용하지 마세요. 접두사 없는 것을 먼저, -webkit-을 마지막에 두세요. macOS Sonoma의 Safari 18 및 26은 필터가 사용자 정의 속성으로 빌드될 때 블러를 렌더링하지 않습니다(WebKit 297620, 열림). Tailwind v4의 backdrop-blur-*는 정확히 그 방식으로 빌드됩니다. 동일한 버그 보고서는 접두사 없는 속성이 리터럴 값에서도 실패함을 보여주므로, 접두사가 있는 선언이 마지막에 와야 Safari가 적용합니다(Chrome과 Firefox는 무시합니다). glass.css는 두께 × 톤당 리터럴 필터를 가지며 클래스로 전환합니다; 변수는 채우기, 차광 및 색상에 괜찮습니다.
두께. 유리가 담는 텍스트 양에 따라 선택하세요. 값은 demo.html에서 밝은 빛 위에서 대비 체커로 보정되었습니다:
| 클래스 | 블러, 차광 | 밝은 빛 위에서 4.5:1 통과? | 사용 대상 |
|---|---|---|---|
glass--clear | 16px, 22% | 아니요: 굵거나 큰 텍스트 및 아이콘만 가능 | 독, 재생 버튼, 미디어 위의 필 (Apple: 클리어는 굵고 밝은 콘텐츠와 차광 레이어 필요) |
glass--thin | 24px, 30% | 가장 밝은 빛 위에서는 불가 | 중간 정도 배경의 컨트롤, 칩, 작은 카드 |
glass--regular | 32px, 48% | 캘리브레이션 페이지에서는 예; 플레이어 뒤의 밝은 일몰은 0.62에서 thick 필요 | 플레이어, 패널, 토스트: 기본값. 블러-오프(틴트 전용) 상태가 일반적으로 먼저 실패 |
glass--thick | 44px, 56% | 예 | 메뉴, 시트, 다이얼로그, 텍스트가 많은 모든 것 |
glass--solid | 4px, 92% | 예 | 결제, 삭제, 서명 (약 85% 이상 채우면 큰 블러가 보이지 않으므로 4px) |
밝은 유리와 잉크 텍스트를 위해 glass--light를 추가하고, 색상이 중요한 앱(사진, 비디오 및 디자인 편집기)에서는 glass--neutral을 추가하세요: 채도 증가나 톤 변화가 없어야 유리가 작업물 모양을 변경하지 않습니다. 디테일한 사진 위에서는 --glass-dim: rgb(10 10 18 / 0.65–0.75)로 thick부터 시작하세요(숫자가 아닌 색상임): 생생한 유리와 읽기 쉬운 텍스트는 서로 상충되므로, 생생함은 테두리, 광택 및 유리 주변에 번지는 빛에 사용하세요.
iOS 26.1과 같은 두 가지 레벨: prefers-contrast: more(Safari 사용자가 접근할 수 있는 설정; Safari에는 prefers-reduced-transparency 없음)에 대해 틴트 레벨(더 많은 불투명도, 절대 적지 않음)이 켜집니다. glass--tinted를 사용하거나, 사이트 전체에 <html data-glass="tinted">를 사용하세요. glass.css는 Windows High Contrast(단색 Canvas 표면), 인쇄(테두리 있는 일반 상자), 스크린샷 도구용 캡처 모드 및 RTL도 처리합니다.
유리는 제어 레이어에 속합니다 (내비게이션, 툴바, 독, 메뉴, 시트, 다이얼로그). 콘텐츠 자체 위에 떠 있어야 하며, 절대 유리 위에 유리를 두지 마세요(Apple, WWDC25). 기본 작업에만 색조를 입히세요. 브리프의 레지스터에 맞추세요: 몰입형 브리프는 강한 빛과 히어로 컨트롤의 투명 유리를 사용하고; 신뢰 기반(금융, 건강)은 두껍거나 단색 유리와 거의 빛을 사용하지 않습니다.
references/material.md에는 나머지가 있습니다: 각 레이어가 하는 일과 이유, Apple.com, Linear, Raycast, Radix, Material 및 Fluent에서 읽어온 프리셋, Apple의 전체 WWDC25 규칙 목록 및 레지스터 테이블.
3. 시그니처 효과
references/recipes.md의 코드:
- 액체 유리 굴절: 테두리가 뒤의 것을 렌즈처럼 구부리며, 선택적 색수차(0.6–1) 포함. 두 가지 버전:
liquid-glass.ts/.js는 모든 페이지 콘텐츠를 굴절시키지만 Chromium에서만 작동합니다(Safari는 SVG 배경 필터에 대해 아무것도 그리지 않음, WebKit 245510);liquid-glass-webgl.js는 페이지 자체가 그리는 배경(<canvas>,<img>또는<video>, 예: 히어로 배경화면)을 모든 브라우저에서 굴절시키며, Chromium 및 WebKit에서 렌더링이 확인되었습니다. 다른 곳에서는 젖빛 소재가 자체적으로 완성된 모습을 보여야 합니다. 굴절과 젖빛은 트레이드오프가 있습니다: 굽힘은 투명 유리(blur0–6)를 통해 보이고; 젖빛 처리(12–20)는 가장자리에서 가장자리로 서리가 내리며 굽힘은 부드러운 색상 변화가 됩니다. 단어나 아이콘에는 투명, 문장에는 젖빛, 텍스트는 가장자리에서 베젤 너비만큼 안쪽으로 배치합니다. 베일 위의 다이얼로그에서는 절대 사용하지 마세요: 구부릴 날카로운 것이 남아 있지 않습니다. - 스티키 바용 확장 배경: 콘텐츠가 아래로 스크롤되기 전에 유리 안에 색상이 도착합니다.
gs-bar--on-scroll은 순수 CSS(스크롤 기반 애니메이션)를 사용하여 스크롤 후에만 바를 유리처럼 만듭니다. - 프로그레시브 블러: 콘텐츠가 잘리는 대신 바 아래에서 용해됩니다. CSS가
linear-blur()를 얻을 때까지 스택 마스크 레이어가 유일한 방법입니다(제안됨, csswg #13285). - 블러-브리지 크로스페이드: 상태 전환 중 2px 블러(Emil Kowalski).
- 블러 리빌:
blur(8px)에서 시작하여, 한 번, 도착 순간으로 사용. - 포커스 베일: 다이얼로그 뒤의 페이지가 4–8px 뒤로 물러납니다.
- 글래스 모프 및 프레스: 근처 유리 모양 사이의
layoutId모프(Cursor: 0.34초,[0.32, 0.72, 0, 1]) 및 프레스 시 내부 글로우 펄스(스케일 아님). - 유리 모양 병합: 구이 필터링된 SVG 마스크로 잘린 하나의 유리 레이어. 근처 모양이 Apple의 유리 컨테이너처럼 액체 목으로 연결됩니다(레시피 11, Chromium에서 테스트됨).
- 비디오의 앰비언트 글로우: 재생 중인 비디오의 10×6 캔버스 샘플을 확대하여 사용(레시피 8b).
- 틴팅: 구조용 유리는 크로마 ~0.03–0.05, 틴트는 상대 색상 알파 사용(레시피 12).
- 피사계 심도, 속도 모션 블러, 아트 배경(베이크드), 잠긴 콘텐츠, 그레인.
4. 속도 유지하기
먼저 디자인을 완성한 다음, 다음 사항을 준수하세요:
- 길거나 반복되는 애니메이션은 블러 반경이 아닌 불투명도와 변형(transform)에 적용하세요. 짧은 일회성 애니메이션은 괜찮습니다: Apple.com은 메뉴 커튼의 블러를 0 → 20px로 320ms 동안 애니메이션합니다.
- 두 개의 겹친 표면을 동시에 블러 처리하지 마세요(Apple의 내비게이션은 플라이아웃이 열려 있는 동안 자체 블러를 해제합니다). 다이얼로그가 유리 크롬 위에 열리면
<html>에.gs-modal-open을 추가하고(크롬이 블러를 중단하고 거의 불투명해짐)gs-veil--flat을 사용하거나 이미지 또는 캔버스 배경에서 베일을 구우세요. 사진 편집기 테스트에서 측정: 다섯 개의 유리 패널 위의 라이브 전체 화면 베일 블러는 31fps로 실행되었고, 없으면 60fps였습니다. - 고정 또는 스티키 블러는 모든 스크롤 프레임에서 다시 실행됩니다. 화면당 하나의 큰 블러만 사용하고, 그 아래에 큰 블러 이미지를 구우세요(측정: 라이브 블러 60 → 22fps, 구운 블러 60fps).
- 휴대폰: 일곱 개의 18px 패널은 Mali-G57에서 프레임당 약 3.5ms를 소비합니다(제시간에 도착한 프레임 75–80% vs 없을 때 99%). 패널이 몇 개 이상이면 레이아웃의 휴대폰 중단점에서
backdrop-filter를 제거하고 반투명 채우기를 유지하거나(또는 glass-tier.js가 기기별로 결정하도록 함). - 블러 영역을 예산으로 잡고, 반경이나 개수가 아닙니다. 이 스킬에 대해 측정됨(Chromium, 소프트웨어 렌더링 2배, 움직이는 배경, vsync 켜짐): 반경 4 → 64px 및 동일한 총 면적에서 1 → 9개 표면은 차이가 없었습니다; 화면의 90%를 블러 처리하면 51fps로 떨어졌습니다(p95 33ms). 이는 Skia의 대형 블러 다운샘플링과 일치합니다. 숫자는 상대적(소프트웨어 렌더링하는 빠른 Mac)이며, 순위는 이식 가능합니다.
html에 불투명한 배경색을 지정하세요: 그러면 Safari가 유리가 있는 페이지에 대해 추가 전체 화면 합성 레이어를 건너뜁니다(WebKit 271340: 수정 사항은 불투명 문서에 대해서만 레이어를 건너뜁니다).- 불투명 채우기 아래에서는 블러를 적용하지 마세요. Framer와 Dia는 단색 배경에
backdrop-filter를 제공하여 아무도 볼 수 없는 블러에 비용을 지불합니다. 약 85% 이상 채우면 ≤ 4px를 사용하거나 사용하지 마세요(Arc의 토스트: 85% 및 3px). - 기기별로 유리 계층화:
assets/glass-tier.js는data-glass-tier를 full, lite(하나의 8px 블러) 또는 flat(블러 없음, 더 많은 색조)으로 설정합니다. 기기 메모리, Save-Data, 프레임 속도 프로브(iOS 저전력 모드는 의도적으로 애니메이션을 30fps로 제한함) 및 Compute Pressure를 기반으로 합니다. Android, Fluent 및 Apple이 약하거나 전력 절약 장치에서 블러를 제거하는 방식과 동일합니다. - 비디오 위에서는 재생 중인 비디오를 블러 처리하지 마세요: 작은 캔버스에 샘플링하고(레시피 8b), 전체 화면에서는 유리를 제거하세요. Safari는 비디오 픽셀을 SVG 필터에 전달하지 않습니다.
- 배경 루트:
opacity < 1,filter, 마스크,clip-path,mix-blend-mode,backdrop-filter또는 이들 중 하나에 대한will-change가 있는 부모는 내부의 유리를 평평하게 만듭니다. 여기에는 Framer Motion 부모 페이드 아웃 및 유리 블롭의 부모에 대한 구이filter가 포함됩니다. 유리 자체를 페이드하고 완료되면 리빌 필터(filter: none)를 지우세요. backdrop-filter는 엘리먼트를position: fixed자식의 포함 블록으로 만듭니다: 포털 메뉴, 토스트 및 다이얼로그는 유리 컨테이너 밖에 있어야 합니다.- 조상에 상시
view-transition-name이 있으면 Chromium에서 유리가 평평해집니다; 전환 중에만 이름을 설정하세요. 래퍼의 프레임워크 페이드 전환도 동일한 작업을 수행합니다: 유리 자체를 페이드하세요. - 유리의 스크린샷, OG 이미지 또는 PDF를 찍을 때: html2canvas, Satori 및 인쇄는 블러를 제거하거나 깨뜨립니다. 캡처하는 동안
<html>에data-glass-capture를 설정하고(glass.css는 유리를 불투명하게 만듦) quirks.md를 참조하세요.
5. 확인 (필수)
유리를 완료했다고 판단하기 전에 다음을 모두 실행하세요:
- 서리 확인:
node <skill>/scripts/frost-check.mjs <url 또는 file.html>는 각 유리 표면에 대해 블러가 제거하는 디테일의 양(블러 켜짐 vs 꺼짐)을 측정합니다. NO BLUR는 배경 루트, 깨진 접두사 또는var(), 또는 오래된 Liquid Glass 맵을 의미합니다; FLAT는 뒤에 측정할 것이 없음을 의미합니다. 눈으로 확인하려면 유리 뒤에 굵은 빨간색 텍스트를 놓고,deviceScaleFactor: 1에서 캡처하여 확대하세요. - 측정된 대비, 최악의 배경에서:
node <skill>/scripts/glass-contrast.mjs <url 또는 file.html>. 가장 밝은 실제 상태를 가리키세요. 예쁜 기본값이 아닙니다: 가장 밝은 사진, 일광 프레임, +2 EV로 밀어낸 이미지, 바 아래로 스크롤되는 흰색 페이지 섹션(스크립트가 로드할 수 있도록 URL 매개변수나 테스트 상태를 추가하세요). 한 편집기에서 기본 사진은 8.7:1로 통과했고 밝은 프레임은 4.4:1로 실패했습니다. 휴대폰 너비도 확인하세요(--width 390 --height 844). 텍스트를 숨기고, 각 줄 아래에서 가장 밝은 픽셀과 가장 어두운 픽셀을 찾아 세 가지 상태에서 4.5:1(큰 텍스트는 3:1)을 확인합니다: 일반, 틴트 전용(iOS 저전력 모드처럼 블러 꺼짐) 및prefers-contrast: more. 실패를 수정하려면 두께를 한 단계 올리거나,--glass-dim의 알파를 높이거나(예:0.48→0.55), 더 큰 글꼴 크기를 사용하거나, 고정된 디자인에서는 백플레이트 뒤에 불투명 패치를 넣으세요.--glass-text-color를 조정하지 마세요: 가독성 있는 텍스트는 배경과 독립적이어야 하며, 배경과 일치시키려는 것은 잘못된 방향입니다.
assets 구조
assets/
glass.css # 핵심 유리 소재: 두께, 톤, 테두리 조명, 성능 계층, 접근성
surfaces.css # 미리 만들어진 표면: .gs-bar, .gs-dock, .gs-menu, .gs-veil, .gs-dialog, .gs-toast, .gs-sheet
glass.tailwind.css # Tailwind v4 유틸리티: .glass-blur-24, .glass-bg-48 등
glass-tier.js # 기기 감지: full / lite / flat 설정, 데이터 속성 설정, Compute Pressure 사용
demo.html # 모든 표면과 컨트롤을 보여주는 라이브 데모 페이지
scripts/
glass-contrast.mjs # 각 유리 표면 아래의 최악의 텍스트 대비를 측정합니다.
frost-check.mjs # 각 유리 표면이 실제로 블러를 적용하는지 확인합니다.
references/
material.md # 소재 심층 분석: Apple, Linear, Raycast, Radix, Material, Fluent 프리셋; WWDC25 규칙; 레지스터
native.md # SwiftUI, Flutter, React Native, Compose 가이드
recipes.md # 시그니처 효과 코드: Liquid Glass, 프로그레시브 블러, 블러 리빌 등
quirks.md # 브라우저별 문제 및 해결 방법