feat(site): 리퀴드 글래스 굴절을 올바른 변위 지도로 다시 붙인다
All checks were successful
ci / build (push) Successful in 29s
All checks were successful
ci / build (push) Successful in 29s
앞선 커밋에서 "backdrop-filter 체인에서 displacement 가 안 먹는다" 고 적고 데모를 뺐다. 틀렸다. svg-filters.md 에 이미 적혀 있던 두 규칙을 안 지킨 것이다. - 변위 지도는 노이즈가 아니다. feTurbulence 로 만들면 값이 중립(128)으로 수렴해 scale 을 아무리 키워도 곱해지는 값이 0 이다. - feImage 의 크기는 요소 크기와 같아야 한다. 자동으로 안 맞춰준다. 가장자리에서 안쪽으로 미는 그라디언트를 직접 그렸다. linearGradient 한 장으로는 R 과 G 를 따로 못 만들므로 지도를 두 장 놓고 displacement 를 두 번 건다. 쓰지 않는 축에는 128 고정인 B 채널을 물려 이동량을 0 으로 만든다. 실측: 평균 채널차 1.66(난류 지도) → 9.29(그린 지도), 최대 126. 배경에 격자를 깔아 확인했다 — 패널 가장자리에서 격자 셀이 눌린다. svg-filters.md 에 지도 두 장 기법과 확인 방법을 기록했다.
This commit is contained in:
parent
ae2980872b
commit
9a09678dee
4 changed files with 154 additions and 6 deletions
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
import { Image } from "astro:assets";
|
||||
import paper from "../assets/bg/bg-paper.webp";
|
||||
import split from "../assets/bg/bg-split.webp";
|
||||
|
||||
/**
|
||||
* 필터 데모의 소재. 같은 소재를 두 번 그리고 한쪽에만 필터를 건다.
|
||||
|
|
@ -8,7 +9,7 @@ import paper from "../assets/bg/bg-paper.webp";
|
|||
* 보이는 차이가 필터 때문인지 소재 때문인지 알 수 없다.
|
||||
*/
|
||||
interface Props {
|
||||
kind: "scrim" | "cvd" | "goo";
|
||||
kind: "scrim" | "cvd" | "goo" | "glass";
|
||||
on: boolean;
|
||||
}
|
||||
const { kind, on } = Astro.props;
|
||||
|
|
@ -48,6 +49,18 @@ const { kind, on } = Astro.props;
|
|||
)
|
||||
}
|
||||
|
||||
{
|
||||
kind === "glass" && (
|
||||
<div class="art art-glass">
|
||||
<Image src={split} alt="" widths={[300, 600]} sizes="300px" loading="lazy" />
|
||||
{/* 격자를 함께 깐다. 굴절은 직선이 휘어야 보인다 —
|
||||
사진만 놓으면 무엇이 어떻게 밀렸는지 알 수 없다 */}
|
||||
<span class="glass-grid" />
|
||||
<span class:list={["glass-panel", { on }]}>산미 8.5</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<style>
|
||||
.art {
|
||||
height: var(--art-h, 118px);
|
||||
|
|
@ -130,6 +143,42 @@ const { kind, on } = Astro.props;
|
|||
}
|
||||
|
||||
|
||||
/* ---- 유리 ----------------------------------------------------
|
||||
feImage 는 요소 크기에 맞춰 자동 조정되지 않는다.
|
||||
그래서 패널 크기를 필터의 지도 크기(216×72)와 똑같이 고정한다 */
|
||||
.art-glass :global(img) {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.glass-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
repeating-linear-gradient(90deg, rgb(255 255 255 / 0.5) 0 1px, transparent 1px 20px),
|
||||
repeating-linear-gradient(0deg, rgb(255 255 255 / 0.5) 0 1px, transparent 1px 20px);
|
||||
}
|
||||
.glass-panel {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 216px;
|
||||
height: 72px;
|
||||
margin: -36px 0 0 -108px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid rgb(255 255 255 / 0.28);
|
||||
background: rgb(255 255 255 / 0.05);
|
||||
color: #fff;
|
||||
font-weight: var(--weight-medium);
|
||||
/* 굴절을 못 읽는 엔진은 여기서 멈춘다 */
|
||||
backdrop-filter: blur(1px);
|
||||
}
|
||||
.glass-panel.on { backdrop-filter: blur(1px) url(#f-lens); }
|
||||
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
.cap { text-shadow: 0 1px 3px rgb(0 0 0 / 0.9); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,26 @@ import type { Content } from "../i18n/content";
|
|||
|
||||
interface Props { c: Content["materials"]; }
|
||||
const { c } = Astro.props;
|
||||
|
||||
/* 변위 지도 두 장. 가장자리에서 안쪽으로 미는 값만 담고 가운데는 중립(128)이다.
|
||||
`#` 이 data URI 를 끊으므로 색은 rgb() 로 적는다. */
|
||||
const W = 216;
|
||||
const H = 72;
|
||||
const map = (vertical: boolean, from: string, to: string) => {
|
||||
const dir = vertical ? 'x1="0" y1="0" x2="0" y2="1"' : 'x1="0" y1="0" x2="1" y2="0"';
|
||||
const svg =
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}">` +
|
||||
`<defs><linearGradient id="g" ${dir}>` +
|
||||
`<stop offset="0" stop-color="${from}"/>` +
|
||||
`<stop offset="0.30" stop-color="rgb(128,128,128)"/>` +
|
||||
`<stop offset="0.70" stop-color="rgb(128,128,128)"/>` +
|
||||
`<stop offset="1" stop-color="${to}"/>` +
|
||||
`</linearGradient></defs>` +
|
||||
`<rect width="${W}" height="${H}" fill="url(#g)"/></svg>`;
|
||||
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
|
||||
};
|
||||
const MAP_X = map(false, "rgb(255,128,128)", "rgb(0,128,128)");
|
||||
const MAP_Y = map(true, "rgb(128,255,128)", "rgb(128,0,128)");
|
||||
---
|
||||
|
||||
{/*
|
||||
|
|
@ -113,6 +133,28 @@ const { c } = Astro.props;
|
|||
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
|
||||
</filter>
|
||||
|
||||
|
||||
<!--
|
||||
리퀴드 글래스. 변위 지도는 **노이즈가 아니다** — 유리는 무작위로 휘지 않고
|
||||
가장자리에서 안쪽으로 휜다. feTurbulence 로 만들면 지도가 중립값(128)으로
|
||||
수렴해 scale 을 아무리 키워도 아무 일도 안 일어난다(실측: 채널차 1.66).
|
||||
|
||||
R 채널이 X, G 채널이 Y 이동량이고 128 이 "움직이지 마라" 다.
|
||||
한 장의 그라디언트로 R 과 G 를 따로 만들 수 없으니, 지도 두 장을 놓고
|
||||
displacement 를 두 번 건다. 안 쓰는 축은 B(=128 고정)를 물려 0 으로 만든다.
|
||||
|
||||
feImage 의 width/height 는 요소 크기와 같아야 한다 — 자동으로 안 맞춰준다.
|
||||
그래서 이 패널만 크기를 고정해 뒀다.
|
||||
-->
|
||||
<filter id="f-lens" color-interpolation-filters="sRGB"
|
||||
x="0" y="0" width="100%" height="100%">
|
||||
<feImage href={MAP_X} x="0" y="0" width="216" height="72" result="mx" />
|
||||
<feDisplacementMap in="SourceGraphic" in2="mx" scale="30"
|
||||
xChannelSelector="R" yChannelSelector="B" result="dx" />
|
||||
<feImage href={MAP_Y} x="0" y="0" width="216" height="72" result="my" />
|
||||
<feDisplacementMap in="dx" in2="my" scale="30"
|
||||
xChannelSelector="B" yChannelSelector="G" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export interface Content {
|
|||
h2: string;
|
||||
lead: string;
|
||||
demos: {
|
||||
id: "scrim" | "cvd" | "goo";
|
||||
id: "scrim" | "cvd" | "goo" | "glass";
|
||||
name: string;
|
||||
problem: string;
|
||||
beforeLabel: string;
|
||||
|
|
@ -230,9 +230,9 @@ export const ko: Content = {
|
|||
},
|
||||
materials: {
|
||||
eyebrow: "SVG 필터",
|
||||
h2: "이미지 편집기를 열지 않고 고치는 세 가지",
|
||||
h2: "이미지 편집기를 열지 않고 고치는 네 가지",
|
||||
lead:
|
||||
"질감을 만드는 필터 데모는 많다. 손이 실제로 줄어드는 자리는 따로 있다. 아래 셋은 왼쪽이 매번 손으로 맞추던 것, 오른쪽이 같은 소재에 선언 하나만 얹은 것이다. 아래 수치는 이 화면을 그대로 캡처해 잰 값이다.",
|
||||
"질감을 만드는 필터 데모는 많다. 손이 실제로 줄어드는 자리는 따로 있다. 아래 넷은 왼쪽이 매번 손으로 맞추던 것, 오른쪽이 같은 소재에 선언 하나만 얹은 것이다. 아래 수치는 이 화면을 그대로 캡처해 잰 값이다.",
|
||||
demos: [
|
||||
{
|
||||
id: "scrim",
|
||||
|
|
@ -261,6 +261,15 @@ export const ko: Content = {
|
|||
how: "크게 블러한 뒤 feColorMatrix 로 알파만 18배 세워 잘라낸다. 붙을지 말지는 눈대중이 아니라 산수다 — 두 원 사이 중간점의 블러 알파가 문턱(7÷18 ≒ 0.39)을 넘어야 한다. 간격 18px 에 블러 7 로는 0.2 근처라 못 넘었고, 12px 에 10 으로 넘겼다.",
|
||||
verdict: "가로로 세어 덩어리 3개 → 1개 · JS 0줄",
|
||||
},
|
||||
{
|
||||
id: "glass",
|
||||
name: "배경을 휘게 하는 오버레이",
|
||||
problem: "반투명 패널은 뒤를 흐리게만 만든다. 실제 유리는 빛을 휜다 — 그 차이가 패널을 화면 위가 아니라 화면 안에 놓는다.",
|
||||
beforeLabel: "backdrop-filter: blur",
|
||||
afterLabel: "blur + url(#lens)",
|
||||
how: "변위 지도를 backdrop-filter 에 넘긴다. R 이 X, G 가 Y 이동량이고 128 이 중립이다. 지도를 feTurbulence 로 만들면 값이 128 로 수렴해 아무 일도 일어나지 않는다 — 가장자리에서 안쪽으로 미는 그라디언트를 직접 그려야 한다.",
|
||||
verdict: "가장자리에서 격자가 눌린다 · 지도 크기가 요소와 다르면 조용히 아무 일도 안 일어난다 · Chromium 전용",
|
||||
},
|
||||
],
|
||||
},
|
||||
metrics: {
|
||||
|
|
@ -485,9 +494,9 @@ export const en: Content = {
|
|||
},
|
||||
materials: {
|
||||
eyebrow: "SVG filters",
|
||||
h2: "Three things you fix without opening an image editor",
|
||||
h2: "Four things you fix without opening an image editor",
|
||||
lead:
|
||||
"There is no shortage of filter demos that make texture. The places where filters actually save you work are elsewhere. In all three below, the left is what you used to hand-tune every time and the right is the same material with one declaration added. The numbers come from capturing this screen and measuring it.",
|
||||
"There is no shortage of filter demos that make texture. The places where filters actually save you work are elsewhere. In all four below, the left is what you used to hand-tune every time and the right is the same material with one declaration added. The numbers come from capturing this screen and measuring it.",
|
||||
demos: [
|
||||
{
|
||||
id: "scrim",
|
||||
|
|
@ -516,6 +525,15 @@ export const en: Content = {
|
|||
how: "Blur hard, then stand the alpha up 18× with feColorMatrix. Whether they fuse is arithmetic, not taste — the blurred alpha midway between two circles has to clear the threshold (7÷18 ≒ 0.39). At 18px apart with a blur of 7 it sat near 0.2 and never merged; 12px and 10 clears it.",
|
||||
verdict: "counted across the middle row: 3 blobs → 1 · 0 lines of JS",
|
||||
},
|
||||
{
|
||||
id: "glass",
|
||||
name: "An overlay that bends the backdrop",
|
||||
problem: "A translucent panel only blurs what is behind it. Real glass bends light — that difference puts the panel inside the picture instead of on top of it.",
|
||||
beforeLabel: "backdrop-filter: blur",
|
||||
afterLabel: "blur + url(#lens)",
|
||||
how: "Hand a displacement map to backdrop-filter. R is the X offset, G is the Y offset, 128 is neutral. Build the map with feTurbulence and every value collapses toward 128, so nothing moves — the map has to be a gradient you draw, pushing inward from the edges.",
|
||||
verdict: "the grid compresses at the edges · size the map wrong and nothing happens, silently · Chromium only",
|
||||
},
|
||||
],
|
||||
},
|
||||
metrics: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue