STUDY/[ React ]
CSS opacity와 brightness 비교 + filter 속성
Lim임
2025. 11. 20. 20:47


어쩌다가?
장기 프로젝트의 프론트를 맡던 중
비활성화 css가 제대로 안들어가는 것을 보고 ai에게 문의한 결과
opacity를 filter: brightness 로 변경하라는 조언을 듣고 해결했다
엥 왜요?
한 줄 요약
opacity는 합성 레이어를 생성해서 border가 어긋날 수 있지만, brightness는 색상만 변경해서픽셀 위치가 완벽하게 유지됩니다!
CSS Filter 속성 학습 문서
1. opacity vs brightness 비교
opacity
.element {
opacity: 0.7; /* 0 (완전 투명) ~ 1 (완전 불투명) */
}
특징:
- 전체 요소의 투명도를 조절
- 요소와 그 모든 자식 요소에 영향
- 새로운 합성 레이어(compositing layer) 생성
- 안티앨리어싱(anti-aliasing) 방식이 변경됨
- 서브픽셀 렌더링(sub-pixel rendering) 영향
렌더링 과정:
- 요소를 별도 레이어로 분리
- 해당 레이어 전체에 투명도 적용
- 다른 레이어와 합성(blend)
- 이 과정에서 픽셀 위치가 미세하게 이동할 수 있음
사용 예시:
/* 전체 요소를 반투명하게 */
.modal-overlay {
opacity: 0.8;
background: black;
}
/* 요소 전체를 숨기기/보이기 애니메이션 */
.fade-out {
opacity: 0;
transition: opacity 0.3s;
}
brightness
.element {
filter: brightness(70%); /* 0% (완전 검정) ~ 100% (원본) ~ 200%+ (더 밝게) */
}
특징:
- 요소의 색상 값만 조절
- 픽셀 위치는 변경하지 않음
- 레이어 분리 없이 색상 필터만 적용
- 픽셀 퍼펙트(pixel-perfect) 정렬 유지
- RGB 값에만 영향 (구조는 그대로)
렌더링 과정:
- 각 픽셀의 RGB 값 계산
- 밝기 비율만큼 RGB 값 조정
- 픽셀 위치는 원본과 동일하게 유지
사용 예시:
/* 비활성화된 요소 어둡게 */
.disabled {
filter: brightness(70%);
pointer-events: none;
}
/* 호버 시 밝게 */
.button:hover {
filter: brightness(110%);
}
2. Border 정렬 문제의 원인
문제 상황
/* ❌ 문제가 있던 코드 */
.piece-position.unable {
opacity: 0.7;
}
왜 border가 안 맞았을까?
- 합성 레이어 생성
opacity는 요소를 별도 레이어로 분리- 이 레이어는 GPU에서 별도로 처리됨
- 서브픽셀 렌더링 변화
- 레이어 합성 과정에서 안티앨리어싱 재계산
- 1px border가 0.8px이나 1.2px처럼 보일 수 있음
- 픽셀 경계가 흐릿하거나 어긋나 보임
- 시각적 효과
원본: ┌─────┐ │ │ (선명한 1px border) └─────┘ opacity: ┌═════┐ ║ ║ (약간 두껍거나 흐릿한 border) └═════┘ opacity: 0.7 ┌─────────┐ │ ░░░░░░░ │ ← 전체가 투명해져서 경계가 흐릿 │ ░PIECE░ │ 픽셀이 미세하게 어긋남 │ ░░░░░░░ │ └─────────┘ brightness(70%) ┌─────────┐ │ ████████│ ← 경계는 선명하고 정확 │ █PIECE█ │ 색만 어둡게 │ ████████│ └─────────┘
해결 방법
/* ✅ 해결된 코드 */
.piece-position.unable {
filter: brightness(70%);
pointer-events: none;
}
왜 brightness는 괜찮을까?
- 픽셀 위치 유지
- 합성 레이어 생성 없음
- 원본 픽셀 위치 그대로 유지
- 색상만 변경
원본 border: rgb(139, 69, 19) → brightness(70%) → rgb(97, 48, 13) 위치: (10px, 10px) → (10px, 10px) (동일) 두께: 1px → 1px (동일)- 시각적 일관성
- border의 위치, 두께, 선명도 모두 동일
- 색상만 어두워짐
3. CSS Filter 속성 전체 목록
3.1 brightness (밝기)
filter: brightness(50%); /* 어둡게 */
filter: brightness(100%); /* 원본 */
filter: brightness(150%); /* 밝게 */
- 범위: 0% ~ ∞
- 기본값: 100%
- 효과: RGB 값을 곱셈으로 조정
3.2 contrast (대비)
filter: contrast(50%); /* 대비 낮게 (회색조에 가깝게) */
filter: contrast(100%); /* 원본 */
filter: contrast(200%); /* 대비 높게 (색상 강조) */
- 범위: 0% ~ ∞
- 기본값: 100%
- 효과: 중간 회색(50%)을 기준으로 어두운 색은 더 어둡게, 밝은 색은 더 밝게
3.3 grayscale (회색조)
filter: grayscale(0%); /* 원본 색상 */
filter: grayscale(50%); /* 반만 회색 */
filter: grayscale(100%); /* 완전 회색 */
- 범위: 0% ~ 100%
- 기본값: 0%
- 효과: 모든 색상을 회색조로 변환
사용 예시:
/* 비활성화된 이미지 */
.image-disabled {
filter: grayscale(100%);
}
/* 호버 시 색상 복원 */
.image-disabled:hover {
filter: grayscale(0%);
transition: filter 0.3s;
}
3.4 sepia (세피아 톤)
filter: sepia(0%); /* 원본 */
filter: sepia(50%); /* 반만 세피아 */
filter: sepia(100%); /* 완전 세피아 (갈색 빛) */
- 범위: 0% ~ 100%
- 기본값: 0%
- 효과: 오래된 사진 같은 갈색 톤
3.5 saturate (채도)
filter: saturate(0%); /* 무채색 (grayscale과 유사) */
filter: saturate(100%); /* 원본 */
filter: saturate(200%); /* 채도 2배 (선명한 색상) */
- 범위: 0% ~ ∞
- 기본값: 100%
- 효과: 색상의 선명도 조절
3.6 hue-rotate (색조 회전)
filter: hue-rotate(0deg); /* 원본 */
filter: hue-rotate(90deg); /* 90도 회전 (빨강→노랑) */
filter: hue-rotate(180deg); /* 180도 회전 (빨강→청록) */
filter: hue-rotate(360deg); /* 한 바퀴 (원본과 동일) */
- 범위: 0deg ~ 360deg
- 기본값: 0deg
- 효과: HSL 색상환을 기준으로 색상 변경
색상 변화:
- 0deg → 원본
- 90deg → 빨강→노랑, 초록→청록, 파랑→보라
- 180deg → 색상 반전 (보색)
- 270deg → 빨강→청록, 초록→보라, 파랑→노랑
3.7 invert (색상 반전)
filter: invert(0%); /* 원본 */
filter: invert(50%); /* 중간 회색 */
filter: invert(100%); /* 완전 반전 (네거티브) */
- 범위: 0% ~ 100%
- 기본값: 0%
- 효과: 색상 네거티브 효과
변화 예시:
- 흰색(#ffffff) → 검정(#000000)
- 빨강(#ff0000) → 청록(#00ffff)
- 파랑(#0000ff) → 노랑(#ffff00)
3.8 blur (흐림 효과)
filter: blur(0px); /* 선명 */
filter: blur(5px); /* 약간 흐림 */
filter: blur(10px); /* 많이 흐림 */
- 범위: 0px ~ ∞
- 기본값: 0px
- 효과: 가우시안 블러 (Gaussian blur)
사용 예시:
/* 배경 블러 */
.backdrop {
filter: blur(8px);
}
/* 로딩 중 콘텐츠 */
.loading {
filter: blur(3px);
pointer-events: none;
}
3.9 drop-shadow (그림자)
filter: drop-shadow(2px 2px 4px rgba(0,0,0,0.5));
/* drop-shadow(offset-x offset-y blur-radius color) */
- 매개변수:
offset-x: 수평 거리 (양수=오른쪽, 음수=왼쪽)offset-y: 수직 거리 (양수=아래, 음수=위)blur-radius: 흐림 정도 (선택사항, 기본 0)color: 그림자 색상 (선택사항)
box-shadow와 차이점:
/* box-shadow: 박스 형태 그림자 */
.box {
box-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}
/* drop-shadow: 실제 형태를 따라가는 그림자 */
.shape {
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
filter: drop-shadow(2px 2px 4px rgba(0,0,0,0.5));
}
중요: clip-path를 사용한 요소는 box-shadow가 작동하지 않으므로 drop-shadow를 사용해야 함!
3.10 opacity (투명도) - filter 버전
filter: opacity(50%); /* 반투명 */
filter: opacity(100%); /* 불투명 */
- 범위: 0% ~ 100%
- 기본값: 100%
- 차이점:
opacity속성과 거의 동일하지만 filter 체인에 포함 가능
4. Filter 조합 사용
여러 필터 동시 적용
.element {
filter: brightness(110%) contrast(120%) saturate(130%);
}
순서 중요:
/* 순서에 따라 결과가 다름 */
.example1 {
filter: grayscale(100%) sepia(100%);
/* 1. 회색으로 변환 → 2. 세피아 톤 적용 */
}
.example2 {
filter: sepia(100%) grayscale(100%);
/* 1. 세피아 톤 적용 → 2. 회색으로 변환 (결국 회색) */
}
실용적인 조합 예시
비활성화 효과
.disabled {
filter: brightness(70%) grayscale(50%);
/* 어둡게 + 색상 제거 */
}
빈티지 사진 효과
.vintage {
filter: sepia(60%) contrast(110%) brightness(90%);
}
네온 효과
.neon {
filter: brightness(150%) saturate(200%) contrast(120%);
}
다크모드 이미지
.dark-mode img {
filter: invert(100%) hue-rotate(180deg);
/* 색상 반전 + 색조 복원 */
}
5. 성능 고려사항
하드웨어 가속
다음 필터는 GPU 가속이 가능:
blur()drop-shadow()opacity()
나머지는 CPU에서 처리됨.
성능 팁
/* ❌ 나쁜 예: 매 프레임마다 재계산 */
.animated {
animation: complexFilter 1s infinite;
}
@keyframes complexFilter {
0% { filter: brightness(100%) contrast(100%) saturate(100%); }
50% { filter: brightness(120%) contrast(110%) saturate(120%); }
100% { filter: brightness(100%) contrast(100%) saturate(100%); }
}
/* ✅ 좋은 예: 단순한 필터 사용 */
.animated {
animation: simpleFilter 1s infinite;
}
@keyframes simpleFilter {
0% { filter: brightness(100%); }
50% { filter: brightness(120%); }
100% { filter: brightness(100%); }
}
6. 브라우저 호환성
대부분의 현대 브라우저에서 지원:
- Chrome/Edge: 18+
- Firefox: 35+
- Safari: 9.1+
- iOS Safari: 9.3+
주의사항:
- IE 11 이하: 지원 안 함
- 오래된 브라우저를 지원해야 한다면 fallback 제공:
.element {
/* 구형 브라우저용 fallback */
opacity: 0.7;
/* 현대 브라우저용 */
filter: brightness(70%);
}
/* 또는 @supports 사용 */
@supports (filter: brightness(70%)) {
.element {
filter: brightness(70%);
opacity: 1; /* filter 지원 시 opacity 무효화 */
}
}
7. 실전 활용 예시
장기 프로젝트에서의 활용
비활성화된 기물
.piece-position.unable {
filter: brightness(70%);
pointer-events: none;
}
이유: 픽셀 정렬 유지하면서 어둡게 표현
선택된 기물 강조
.piece-position.selected {
filter: brightness(120%) drop-shadow(0 0 8px rgba(255,255,0,0.6));
}
효과: 밝게 + 노란 그림자로 강조
호버 효과
.piece:hover {
filter: brightness(110%);
transition: filter 0.2s;
}
효과: 부드러운 밝기 변화
적 기물 구분
.piece.enemy {
filter: saturate(80%) brightness(95%);
}
효과: 약간 덜 선명하고 어둡게
요약
| 속성 | opacity | filter: brightness() |
|---|---|---|
| 적용 대상 | 요소 전체 (자식 포함) | 요소의 색상만 |
| 레이어 생성 | ✅ 생성 | ❌ 없음 |
| 픽셀 위치 | 미세하게 이동 가능 | 완전히 유지 |
| border 정렬 | 어긋날 수 있음 | 정확히 유지 |
| 성능 | 약간 느림 (합성) | 빠름 |
| 사용 용도 | 투명도 조절, 페이드 효과 | 밝기 조절, 비활성화 |
결론: Border나 픽셀 정렬이 중요한 경우 filter: brightness()를 사용하고, 진짜 투명도가 필요한 경우에만 opacity를 사용하자!