CSS filter 쓰면 position:fixed가 안 먹히는 이유

화면 오른쪽 아래에 항상 떠 있어야 할 버튼이 스크롤할 때마다 같이 흘러가거나, 배경 박스 밖으로 못 나오고 안에 갇혀 잘리는 경우가 있습니다. 코드에는 position: fixed가 분명히 있는데도 이런 일이 생기는 원인은 대부분 하나입니다.
그 요소를 감싼 조상 어딘가에 CSS filter가 걸려 있고, filter가 none이 아닌 값이면 스펙상 그 조상이 fixed 요소의 새 기준점이 되기 때문입니다.
요약 ① filter가 none이 아닌 값을 가진 요소는 그 자손 중 position:fixed·absolute 요소의 기준점(containing block)이 됩니다. ② 슬라이더를 하나라도 기본값에서 움직이면 결과가 filter: none;이 아니게 되어, 이 규칙이 그 순간부터 적용됩니다. ③ transform·perspective·backdrop-filter·will-change·contain도 같은 그룹이라, filter만 의심하면 원인을 놓치기 쉽습니다.
position:fixed가 스크롤에 끌려다니는 이유 — 왜 이게 문제인가
배경 이미지를 흐리게 하려고 큰 래퍼 요소 하나에 filter: blur(6px); 같은 CSS를 걸었다고 하면, 그 안에 스크롤과 무관하게 화면에 붙어 있어야 할 채팅 버튼이나 안내 배너를 함께 넣어 두는 구성이 흔합니다.
filter를 넣기 전에는 정상적으로 뷰포트에 고정돼 있던 그 버튼이, filter를 넣는 순간부터 스크롤을 따라 움직이거나 래퍼의 overflow: hidden 경계에 잘려 나갑니다. position 값은 그대로 fixed인데 결과만 달라진 것입니다.
이건 자바스크립트 버그도, 브라우저 오작동도 아닙니다. CSS 스펙이 filter 속성에 부여한 정해진 동작이 그대로 실행된 것뿐입니다.
원인 확인 — 이렇게 확인합니다
CSS 스펙의 "containing block(기준점)" 규정을 보면, 다음 속성 중 하나라도 none이 아닌 값을 가진 조상 요소는 그 자손에 있는 position:fixed·absolute 요소의 기준점이 됩니다. 원래 fixed는 뷰포트를, absolute는 가장 가까운 위치 지정 조상을 기준점으로 삼는데, 아래 속성이 있으면 그 조상이 우선합니다.
filter— none이 아닌 값 (예:blur(6px),brightness(110%))backdrop-filter— none이 아닌 값transform,perspective— none이 아닌 값will-change에 위 속성 이름을 지정한 경우contain: layout/paint/strict/content
즉 filter 하나만 의심하고 있으면 놓치기 쉽습니다. 같은 조상에 transform이나 will-change: transform이 걸려 있어도 결과는 똑같습니다.
여기서 눈에 잘 안 띄는 함정이 하나 있습니다. filter: blur(0)처럼 화면에는 아무 변화가 없는 값도 문법적으로는 none이 아니므로 기준점 규칙이 그대로 적용됩니다. 시각적으로 티가 안 나니 원인 파악이 더 어려워집니다.
코드를 고치지 않고도 원인 조상을 특정하는 방법이 있습니다.
- 문제되는 fixed 요소를 감싼 조상들을 브라우저 개발자 도구에서 바깥쪽부터 하나씩 선택합니다.
- Styles 패널에서 그 조상에 걸린
filter(또는transform,contain) 선언 옆 체크박스를 꺼서 일시적으로 무효화합니다. - 그 순간 fixed 요소가 다시 뷰포트 기준으로 붙는다면, 방금 끈 선언이 있는 그 조상이 원인입니다.
다만 스펙 문서에도 "perspective와 filter가 기준점 형성에 관여하는 부분은 브라우저마다 차이가 있다"는 단서가 붙어 있습니다. 그러니 위 실험은 실제로 배포할 브라우저(크롬·사파리·파이어폭스 등)에서 직접 해 보고 확인하는 편이 안전합니다.
저희 도구로 확인하기. 슬라이더가 전부 기본값이면 결과는 filter: none;이라 이 문제와 무관하지만, 슬라이더를 하나라도 움직이는 순간 값이 생겨 기준점 규칙이 켜집니다. 아래에서 슬라이더를 움직이며 그 경계를 직접 눈으로 확인할 수 있습니다.
무료 · 가입 불필요 · 슬라이더로 blur·밝기·대비 등을 조절해 CSS filter 코드를 바로 생성

자주 막히는 지점
| 증상 | 원인 |
|---|---|
| 스크롤하면 버튼이 화면에 안 붙고 같이 흘러감 | 조상 중 하나가 filter·transform·perspective 값(none 아님)을 가져 그 조상이 fixed의 기준점이 됨 |
filter: blur(0)만 줘도 증상이 재현됨 | 스펙 기준은 "none이 아닌 값"이라 blur(0)도 기준점 규칙을 켠다 |
| 애니메이션 시작 전부터 요소가 튐 | will-change: transform 등을 미리 선언해 둔 경우도 같은 그룹에 속한다 |
| absolute 요소 위치가 예상과 다른 조상 기준으로 계산됨 | 원래도 absolute는 위치 지정 조상을 찾는데, filter를 가진 조상이 그 후보에 추가된다 |
정리
- filter가 none이 아닌 값이면 그 조상은 자손 fixed·absolute 요소의 새 기준점이 됩니다.
- 이 도구에서 슬라이더를 하나도 안 움직이면
filter: none;이라 안전하지만, 하나라도 움직이면 값이 생겨 기준점이 바뀝니다. - 같은 그룹에 transform, perspective, backdrop-filter, will-change, contain도 있어 filter만 의심하면 원인을 놓칠 수 있습니다.
- 개발자 도구에서 후보 속성을 하나씩 꺼 보는 방법으로 원인 조상을 특정할 수 있습니다.
- 고치려면 filter를 래퍼 대신 요소 자신에게 걸거나, 고정돼야 하는 요소를 그 래퍼 바깥 DOM 위치로 옮기면 됩니다.
무료 · 가입 불필요 · 슬라이더로 blur·밝기·대비 등을 조절해 CSS filter 코드를 바로 생성
자주 묻는 질문
filter: blur(0)처럼 눈에 안 보이는 값도 기준점을 바꾸나요?
네. 스펙은 "none이 아닌 값"을 기준으로 삼으므로, blur(0)처럼 시각적으로 차이가 없는 값도 문법적으로는 none이 아니라서 조상을 기준점으로 만듭니다. 실제로 이 성질을 일부러 이용해 요소를 강제로 별도 레이어로 승격시킬 때 blur(0)을 쓰는 경우도 있습니다.
filter 대신 transform을 썼을 때도 같은 문제가 생기나요?
네. transform과 perspective도 none이 아닌 값을 가지면 filter와 똑같이 자손 fixed·absolute 요소의 기준점이 됩니다. will-change에 이 속성 이름만 지정해도, 실제 값을 아직 안 줬어도 같은 효과가 생깁니다.
코드를 수정하지 않고 원인 요소를 확인할 방법이 있나요?
개발자 도구에서 의심되는 조상 요소를 선택하고 Styles 패널의 filter(또는 transform) 선언 옆 체크박스를 꺼 보면 됩니다. 그 순간 fixed 요소가 다시 뷰포트 기준으로 붙는다면 방금 끈 속성이 원인입니다. 코드를 고치지 않고도 원인 조상을 확정할 수 있습니다.
absolute로 지정한 요소도 영향을 받나요?
받습니다. 다만 absolute는 원래도 가장 가까운 위치 지정(relative·absolute·fixed) 조상을 기준점으로 찾으므로, filter를 가진 조상은 그 후보 목록에 하나 더 추가되는 정도로 체감됩니다. 반면 fixed는 원래 뷰포트가 기준이었다가 갑자기 조상으로 바뀌는 것이라 증상이 훨씬 두드러집니다.
