JS 없이 CSS만으로 해결되는 10가지 인터랙션

JS 없이 CSS만으로 해결되는 10가지 인터랙션

Scroll Timeline부터 Anchor Positioning까지—라이브러리를 지우고 번들 사이즈를 줄이는 현대 CSS의 실전 지도

CSS JavaScript Scroll Timeline Container Queries Anchor Positioning 번들 사이즈 프론트엔드 성능 CSS 네이티브
광고

솔직히 말할게요. 저도 얼마 전까지 스크롤 프로그레스 바 하나 만들려고 requestAnimationFrame에 스크롤 이벤트 리스너 달고, 퍼센트 계산하는 함수 따로 빼고, 거기다 디바운스까지 붙이는 코드를 아무렇지 않게 짰습니다. 그게 당연한 줄 알았거든요. 근데 이제 그럴 필요가 없어요. CSS가 이미 다 하고 있습니다.

dev.to의 'CSS Features That Replace JavaScript'는 최근 2~3년 사이 브라우저에 안착한 CSS 기능들을 10가지 실전 예제로 정리한 글입니다. 핵심 메시지는 명확합니다. JavaScript 라이브러리가 처리하던 인터랙션 상당수를 이제 CSS 단독으로 대체할 수 있고, 그 결과는 번들 사이즈 감소, 메인 스레드 부하 경감, 그리고 코드 복잡도 하락으로 직결됩니다.


1. 스크롤 기반 인터랙션: 이제 JS가 끼어들 자리가 없다

animation-timeline: scroll()animation-timeline: view(). 이 두 줄이 AOS.js(14KB), GSAP ScrollTrigger, 커스텀 IntersectionObserver를 전부 대체합니다. 스크롤 프로그레스 바는 scaleX(0)에서 scaleX(1)로 가는 keyframe에 animation-timeline: scroll()만 붙이면 끝. 뷰포트 진입 시 카드 페이드인도 animation-range: entry 0% entry 100%로 범위를 지정하면 IntersectionObserver 없이 동일한 효과가 납니다.

다만 한 가지 짚고 넘어가야 합니다. 스크롤 기반 애니메이션은 브라우저 지원이 아직 완전하지 않아요. Chrome 115+, Safari 18+(2024년 9월)에서는 되지만, Firefox는 여전히 플래그 뒤에 숨어 있습니다. 프로덕션 투입 전에 Analytics로 Firefox 사용자 비중 먼저 확인하는 게 맞는 순서입니다. Progressive enhancement 관점에서 접근하되, 지원 범위를 팀에 공유해두는 게 중요합니다.


2. :has()와 Container Queries: 드디어 컴포넌트가 스스로 판단한다

:has()는 이미 크로스브라우저입니다(Chrome 105+, Safari 15.4+, Firefox 121+). 폼 유효성 검증 스타일링에 적용하면 input 이벤트 리스너, classList 토글, validity 체크 로직이 전부 사라집니다. .form:has(input:valid:not(:placeholder-shown)):not(:has(input:invalid:not(:placeholder-shown))) 이 셀렉터 하나가 모든 필드가 유효할 때 폼 테두리를 초록색으로 바꾸고, 유효하지 않은 필드가 있으면 서브밋 버튼을 dim 처리합니다. JavaScript 한 줄도 없이요.

Container Queries도 마찬가지입니다. 그동안 미디어 쿼리는 뷰포트만 봤잖아요. 같은 카드 컴포넌트가 사이드바에 들어갈 때와 메인 콘텐츠 영역에 들어갈 때 레이아웃이 달라야 하는 상황, 다들 ResizeObserver 달고 JS로 클래스 토글하는 방식으로 해결했을 겁니다. container-type: inline-size 하나면 이제 부모 컨테이너 너비에 반응하는 @container 쿼리로 해결됩니다. 이것도 크로스브라우저(Chrome 105+, Safari 16+, Firefox 110+). 지금 당장 쓸 수 있습니다.


3. Anchor Positioning과 @starting-style: 팝오버의 재정의

Popper.js(8KB), Floating UI—저 라이브러리들이 왜 필요했는지 알잖아요. getBoundingClientRect()로 트리거 엘리먼트 위치 잡고, 스크롤·리사이즈 이벤트 때마다 재계산하고, flip 로직까지 직접 구현하는 그 200줄짜리 JS요. anchor-nameanchor() 함수로 이 모든 게 CSS 선언 몇 줄로 압축됩니다. position-anchor: --btn; top: anchor(bottom); left: anchor(left); 이게 전부입니다. 브라우저 지원은 Chrome 125+, Safari 18+인데, Firefox가 여전히 플래그 상태라는 건 감안해야 합니다.

@starting-style은 개인적으로 가장 반가웠던 기능입니다. display: none에서 display: block으로 전환할 때 애니메이션이 안 먹히는 문제, 다들 경험해봤죠. setTimeout(0) 핵이나 double requestAnimationFrame 트릭으로 타이밍 억지로 맞추던 그 코드. @starting-style을 쓰면 엘리먼트가 처음 렌더될 때의 초기 스타일을 명시적으로 선언할 수 있어서, 다이얼로그 페이드인 같은 엔트리 애니메이션이 JS 없이 깔끔하게 작동합니다. Chrome 117+, Safari 17.5+, Firefox 129+로 이미 크로스브라우저 도달했습니다.


4. CSS Nesting과 color-mix(): 빌드 도구 의존도를 끊는다

Sass를 네스팅 때문에만 쓰고 있었다면, 이제 그 의존성 끊어도 됩니다. CSS 네이티브 네스팅이 크로스브라우저(Chrome 112+, Safari 16.5+, Firefox 117+)가 됐고, & 문법도 Sass와 동일하게 작동합니다. node-sass/dart-sass 의존성, 빌드 스텝 하나 사라지는 겁니다. 번들 파이프라인 단순화는 Lighthouse 점수보다 더 실질적인 유지보수 이익으로 돌아옵니다.

color-mix()는 디자인 시스템 구축할 때 특히 유용합니다. --brand 토큰 하나에서 color-mix(in oklch, var(--brand), white 30%)로 라이트 변형, color-mix(in oklch, var(--brand), black 25%)로 다크 변형을 즉석에서 파생시킬 수 있습니다. Sass의 lighten()/darken() 함수를, JS 컬러 라이브러리를, 손으로 하드코딩한 컬러 팔레트를 대체합니다. OKLCH 색공간을 사용하면 사람 눈에 더 자연스러운 밝기 분포를 얻는다는 부수 효과도 있고요.


5. View Transitions: FLIP 없이 레이아웃 전환을

View Transitions는 JS가 완전히 사라지진 않지만, 역할이 극적으로 줄어드는 사례입니다. document.startViewTransition() 안에서 클래스 토글만 하면, 그리드 뷰와 리스트 뷰 사이의 매끄러운 모프 애니메이션은 CSS가 처리합니다. Framer Motion, React Transition Group, 수동 FLIP 계산—이것들을 통째로 대체할 수 있는 패턴입니다. Firefox 144+(2025년 10월)가 지원을 완료하면서 이제 진정한 크로스브라우저 기능이 됐습니다.


실무 적용 전 반드시 확인할 것들

기능들을 브라우저 지원 기준으로 분류하면 이렇습니다. 지금 당장 쓸 수 있는 것: :has(), Container Queries, CSS Nesting, color-mix(), @starting-style, View Transitions. Progressive enhancement로 사용할 것: 스크롤 기반 애니메이션(Firefox 미지원), Anchor Positioning(Firefox 미지원), interpolate-size(Chromium 전용). 팀 내 지원 브라우저 정책과 사용자 데이터를 먼저 확인하고 들어가야 합니다.

접근성 관점도 빠뜨리면 안 됩니다. 스크롤 기반 애니메이션은 prefers-reduced-motion 미디어 쿼리와 반드시 함께 가야 합니다. @media (prefers-reduced-motion: reduce) 블록에서 animation-timeline 관련 애니메이션을 명시적으로 비활성화하지 않으면, 전정 장애가 있는 사용자에게 심각한 불편을 줄 수 있습니다. 아무리 CSS로 만들어도 접근성 체크는 생략할 수 없어요.


지금 팀에서 꺼내야 할 대화

이 기능들이 실질적으로 의미하는 바는 간단합니다. 지금 당신 프로젝트의 package.json에 AOS.js, Popper.js, 색상 계산용 JS 라이브러리가 들어 있다면, 제거 가능성을 검토할 시점입니다. 번들 사이즈는 줄고, 메인 스레드에서 실행되던 JS 로직이 브라우저 네이티브 렌더링 파이프라인으로 이동하면 Core Web Vitals—특히 INP(Interaction to Next Paint)에도 긍정적인 영향을 줍니다.

물론 모든 걸 CSS로만 해야 한다는 얘기가 아닙니다. 복잡한 상태 관리, 접근성이 까다로운 컴포넌트(Radix처럼 키보드 네비게이션, ARIA 처리까지 완결된 것들)는 여전히 라이브러리가 더 안전한 선택일 수 있어요. 하지만 "이거 JS로 해야 하나?"를 묻기 전에, CSS로 먼저 해결되는지 확인하는 습관—그게 지금 프론트엔드 개발자에게 필요한 감각의 전환입니다.

출처

더 많은 AI 트렌드를 Seedora 앱에서 확인하세요