notes
CSS - @property
CSS @property의 커스텀 프로퍼티 타입·초기값·상속 설정 메모.
@property는 CSS 변수(--name)에 타입· 초기값·상속 규칙을 등록하는 at-rule입니다. 등록된 변수만 브라우저가 “숫자/색/길이”처럼 의미 있게 해석할 수 있어, calc()·애니메이션·보간(interpolation)이 안정적으로 동작합니다.
1. 왜 필요한가?
일반 CSS 변수는 기본적으로 문자열입니다.
:root {
--progress: 0.5;
}
.box {
width: calc(100px * var(--progress));
}
calc() 안에서는 대개 잘 됩니다.
하지만 값이 바뀔 때 부드럽게 보간하거나, @property 없이는 타입이 애매한 경우 브라우저마다 동작이 들쭉날쭉할 수 있습니다.
@property로 등록하면:
- 이 변수는
<number>다라고 명시 - 초기값·상속 여부 지정
- 애니메이션/transitions 시 숫자처럼 보간 가능
2. 문법
@property --변수명 {
syntax: "<number>"; /* 타입 */
inherits: true; /* 상속 여부 */
initial-value: 0; /* 초�값 */
}
syntax (자주 쓰는 것)
inherits
true: 자식이 부모 값을 물려받음false: 자식은initial-value부터 시작
initial-value
- 변수가 아직 설정되지 않았을 때 쓰는 기본값
@property등록 시 필수인 경우가 많음
3. 신이의 GNB 코드에서 쓰는 방식
@property --gnb-move {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --gnb-shape {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --gnb-out {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
의미:
-gnb-move: 스크롤 진행률 (0 → 1)-gnb-shape: pill 형태 변화 (0 → 1)-gnb-out: 로고 fade (0 → 1)
React에서 헤더에 이렇게 주입합니다:
style={{
"--gnb-move": move,
"--gnb-shape": shape,
"--gnb-out": out,
}}
CSS에서는 한 번 정의해 두고 여러 곳에서 재사용합니다:
.header {
height: calc(138px - 82px * var(--gnb-move));
}
.searchSlotDocked {
top: calc(56px - 49px * var(--gnb-move));
width: calc(100% - 32px - 100px * var(--gnb-shape));
padding-left: calc(14px * (1 - var(--gnb-shape)));
}
.gnbRow > :global(div) > :global(div:first-child) {
opacity: calc(1 - var(--gnb-out));
}
핵심 패턴:
JS는 프레임마다 숫자 3개만 넣고, 위치·높이·폭·투명도·padding은 전부 CSS calc()가 --gnb-*로부터 파생합니다. HTML 프로토타입과 같은 구조입니다.
4. @property 없이 vs 있을 때
없을 때
.header {
--gnb-move: 0;
height: calc(138px - 82px * var(--gnb-move));
}
calc()만 쓰면 대부분 동작함- 변수 타입이 문자열이라, transition/animation으로
-gnb-move자체를 animate하려 할 때 브라우저가 보간을 못 하거나 이상하게 동작할 수 있음
있을 때
-gnb-move가 숫자 타입으로 등록됨- 나중에 CSS만으로
transition: --gnb-move 0.2s같은 것도 이론상 가능 (브라우저 지원 범위 내) calc(138px - 82px * var(--gnb-move))에서 곱셈·보간이 더 예측 가능
지금 구현은 JS가 scrollY → move/shape/out 계산, CSS는 표현만 담당합니다. @property는 “이 변수들은 0~1 숫자다”라고 브라우저에 알려주는 역할입니다.
5. inherits: true를 쓰는 이유
헤더(.header)에 --gnb-move 등을 넣으면, 같은 서브트리 안의 자식도 var(--gnb-move)를 쓸 수 있습니다.
.header { --gnb-move: 0.7; } /* style prop으로 덮어씀 */
.searchSlotDocked {
top: calc(56px - 49px * var(--gnb-move)); /* 0.7 상속·사용 */
}
inherits: false면 자식이 부모 값을 안 물려받고 initial-value: 0부터 시작합니다. GNB처럼 한 헤더 아래 여러 요소가 같은 진행률을 쓸 때는 true가 자연스럽습니다.
6. @property와 Houdini
@property는 CSS Houdini API의 일부입니다.
- Paint API, Layout API 등과 함께 “CSS를 JS처럼 확장”하는 실험적 방향이었음
@property만은 Chrome / Edge / Safari(최신) 등에서 실용적으로 많이 씀- Firefox는 버전에 따라 지원이 달라, 미지원 시에도
calc(var(--x))는 대체로 동작하지만@property등록 이점(transition 등)은 줄어듦
프로덕션에서는:
- 핵심 레이아웃은
@property+calc()(지금처럼) - 미지원 fallback: 같은 변수를 그냥
-gnb-move: 0으로 두면 대부분 표시는 됨
7. JS inline style과 함께 쓸 때 주의
React:
style={{ "--gnb-move": move } as CSSProperties}
move는 숫자0.42→ DOM에는-gnb-move: 0.42@property의syntax: "<number>"와 맞음"0.42px"처럼 단위를 붙이면<number>와 안 맞을 수 있음
색/길이 변수를 등록할 때는 syntax를 "<color>", "<length>"로 맞춰야 합니다.
8. @property로 CSS transition까지 하려면 (참고)
등록된 변수는 (지원 브라우저에서) transition 대상이 될 수 있습니다:
@property --gnb-move {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
.header {
transition: --gnb-move 0.15s ease-out;
}
지금 GNB는 스크롤 1:1 연동이라 JS에서 매 프레임 --gnb-move를 갱신합니다. transition을 추가하면 스크롤과 한 박자 늦게 따라올 수 있어, 현재처럼 transition 없이 직접 넣는 편이 맞습니다.