CSS linear() Easing — 자바스크립트 없이 스프링 애니메이션 만들기
CSS로 스프링을 만들 수 있을까
버튼을 눌렀을 때 통통 튀는 느낌, 모달이 등장할 때 살짝 오버슈트하는 탄성. 이런 스프링 애니메이션은 보통 JavaScript나 Framer Motion 같은 라이브러리에 맡기는 영역이었다. CSS의 ease, ease-in-out, cubic-bezier()만으로는 스프링의 진동을 표현할 수 없었기 때문이다.
CSS linear() easing 함수가 이 공백을 메운다.
[💡 잠깐! 이 용어는?]
linear() easing: CSS transition-timing-function이나 animation-timing-function에 쓸 수 있는 함수. 여러 개의 타이밍 포인트를 선형 보간으로 이어붙여 복잡한 커브를 만든다. 최신 크롬, 사파리, 파이어폭스 모두 지원한다.
cubic-bezier의 한계
스프링 애니메이션의 핵심은 **오버슈트(overshoot)**다. 목표 값을 넘어갔다가 돌아오는 움직임이다.
/* cubic-bezier는 제어점이 4개뿐 */
/* 오버슈트는 0~1 범위를 벗어난 y값으로 만들 수 있지만 */
.fake-spring {
transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
/* 살짝 튀는 느낌은 나지만, 진짜 스프링 진동은 불가능 */
}한 번 오버슈트하고 되돌아오는 건 가능하다. 하지만 여러 번 진동하는 물리적 스프링은 cubic-bezier만으로는 표현할 수 없다.
linear()로 스프링 커브 만들기
linear()의 원리는 간단하다. 여러 개의 값을 나열하면, 그것을 순서대로 선형 보간으로 이어붙인다.
/* 0.1초 간격으로 값을 쭉 나열 */
.bounce {
transition: transform 0.6s linear(
0, 0.1, 0.4, 0.9,
1.2, /* 오버슈트: 1을 넘음 */
1.05,
1.15,
0.98,
1.02,
1 /* 최종값 */
);
}값이 1을 초과하면 목표를 넘어서는 오버슈트다. 값이 1 미만으로 돌아오면 진동이다. 이걸 여러 번 반복하면 스프링처럼 보인다.
문제는 이 값들을 손으로 계산하기가 거의 불가능하다는 것이다. 스프링 물리 공식을 직접 계산해야 한다.
실용적인 방법: 스프링 값 생성하기
실제로는 스프링 파라미터를 설정하면 linear() 값을 자동으로 만들어주는 도구를 쓴다.
스프링 파라미터 이해
스프링은 두 가지 파라미터로 제어한다.
| 파라미터 | 역할 | 낮을수록 | 높을수록 |
|---|---|---|---|
| Stiffness (강성) | 스프링의 단단함 | 느리게 반응, 부드러움 | 빠르게 반응, 딱딱함 |
| Damping (감쇠) | 진동 감소 속도 | 오래 진동, 바운스 강함 | 빠르게 안정, 바운스 적음 |
비유하면 stiffness는 스프링의 굵기, damping은 스프링 주변의 오일 저항이다.
JavaScript로 스프링 값 계산
Svelte의 스프링 유틸리티를 활용해 커브를 추출하는 방법이다.
// Svelte spring으로 0→1 애니메이션을 시뮬레이션
import { spring } from 'svelte/motion'
function generateLinearEasing({ stiffness = 150, damping = 10, steps = 100 }) {
const values = []
// 스프링 시뮬레이션
let value = 0
let velocity = 0
const dt = 1 / steps
for (let i = 0; i <= steps; i++) {
const force = -stiffness * (value - 1) - damping * velocity
velocity += force * dt
value += velocity * dt
values.push(Math.round(value * 10000) / 10000)
}
return `linear(${values.join(', ')})`
}
// stiffness 200, damping 15 스프링
console.log(generateLinearEasing({ stiffness: 200, damping: 15 }))
// → linear(0, 0.0039, 0.0153, ...)생성된 값을 CSS에 적용
:root {
/* stiffness=200, damping=15 스프링 커브 */
--spring-easing: linear(
0, 0.0039, 0.0153, 0.0339, 0.0592, 0.0909, 0.1282,
0.1707, 0.2176, 0.2682, 0.3219, 0.3778, 0.4354,
0.4938, 0.5523, 0.6102, 0.6668, 0.7213, 0.773,
0.8212, 0.8653, 0.905, 0.9399, 0.9699, 0.9951,
1.0154, 1.031, 1.042, 1.0487, 1.0515, 1.0506,
1.0465, 1.0397, 1.0307, 1.02, 1.0081, 0.9954,
0.9824, 0.9695, 0.9571, 0.9456, 0.9352, 0.9261,
0.9185, 0.9124, 0.9079, 0.905, 0.9036, 0.9036,
0.9049, 0.9073, 0.9107, 0.9148, 0.9193, 0.9241,
0.9289, 0.9335, 0.9377, 0.9415, 0.9447, 0.9474,
0.9494, 0.9509, 0.9518, 0.9522, 0.9523, 0.9520,
0.9515, 0.9510, 0.9504, 0.9498, 0.9494, 0.9490,
0.9488, 0.9488, 0.9489, 0.9491, 0.9495, 0.9499,
0.9504, 0.9509, 0.9514, 0.9519, 0.9524, 0.9528,
0.9532, 0.9535, 0.9537, 0.9539, 0.9540, 0.9541,
1
);
}
.modal {
transform: scale(0.8);
opacity: 0;
transition:
transform 0.5s var(--spring-easing),
opacity 0.3s ease;
}
.modal.open {
transform: scale(1);
opacity: 1;
}transition vs @keyframes
linear()는 두 방식 모두에서 쓸 수 있다.
/* 방법 1: transition — 상태 변화에 반응 */
.button {
transform: scale(1);
transition: transform 0.4s var(--spring-easing);
}
.button:active {
transform: scale(0.92);
}
/* 방법 2: @keyframes — 독립적인 반복 애니메이션 */
@keyframes spring-in {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.notification {
animation: spring-in 0.5s var(--spring-easing) both;
}| 방식 | 언제 | 특징 |
|---|---|---|
transition | hover, focus, 상태 전환 | 트리거에 반응 |
@keyframes | 진입 애니메이션, 반복 | 독립적으로 실행 |
자주 쓰는 스프링 프리셋
직접 계산하기 번거로우면 아래 프리셋을 복붙해서 시작할 수 있다.
:root {
/* 부드럽고 자연스러운 진입 (모달, 드롭다운) */
--spring-gentle: linear(
0, 0.006, 0.025, 0.055, 0.097, 0.147, 0.204,
0.264, 0.326, 0.386, 0.443, 0.495, 0.542, 0.583,
0.619, 0.649, 0.675, 0.697, 0.715, 0.731, 0.744,
0.756, 0.765, 0.773, 0.780, 0.786, 0.791, 0.795,
0.799, 0.802, 0.805, 0.808, 0.810, 0.812, 0.813, 1
);
/* 통통 튀는 바운스 (버튼, 알림) */
--spring-bouncy: linear(
0, 0.01, 0.04, 0.10, 0.19, 0.31, 0.46, 0.63,
0.82, 1.03, 1.22, 1.37, 1.46, 1.49, 1.46, 1.37,
1.23, 1.05, 0.87, 0.71, 0.58, 0.48, 0.42, 0.40,
0.42, 0.47, 0.55, 0.65, 0.76, 0.88, 1
);
/* 빠르고 날카로운 반응 (툴팁, 팝오버) */
--spring-snappy: linear(
0, 0.05, 0.19, 0.43, 0.72, 1.00, 1.20, 1.29,
1.27, 1.18, 1.06, 0.96, 0.90, 0.88, 0.90, 0.96,
1.02, 1.06, 1.07, 1.06, 1.04, 1.02, 1.01, 1
);
}브라우저 지원
| 브라우저 | linear() 지원 |
|---|---|
| Chrome 113+ | ✅ |
| Firefox 112+ | ✅ |
| Safari 17+ | ✅ |
| Edge 113+ | ✅ |
2026년 기준으로 주요 브라우저 모두 지원한다. 미지원 브라우저를 위한 폴백은 ease-out이면 충분하다.
.modal {
/* 폴백: ease-out */
transition: transform 0.3s ease-out;
}
/* @supports로 linear() 지원 브라우저에만 스프링 적용 */
@supports (transition-timing-function: linear(0, 1)) {
.modal {
transition: transform 0.5s var(--spring-bouncy);
}
}정리
linear()는 여러 타이밍 포인트를 선형 보간으로 이어붙여 복잡한 커브를 만든다.- 스프링 물리 시뮬레이션으로 값을 생성하면 순수 CSS로 스프링 애니메이션을 구현할 수 있다.
stiffness(강성)와damping(감쇠)으로 바운시한 정도를 제어한다.transition과@keyframes모두에 사용 가능하다.- 2026년 기준 주요 브라우저 모두 지원한다.
JavaScript 없이, 라이브러리 없이. CSS 한 줄짜리 변수 선언으로 스프링 애니메이션을 만들 수 있다.
참고:
- Pqina — Creating CSS Spring Animations With The Linear Easing Function: https://pqina.nl/blog/css-spring-animation-with-linear-easing-function/
- MDN — linear() easing: https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function/linear
- Jake Archibald — linear() generator: https://linear-easing-generator.netlify.app/
관심 있을 만한 포스트
CSS animation-timeline — 스크롤 오버플로우를 감지해 Border-Radius 동적 조절하기
CSS scroll() 타임라인으로 컨테이너 오버플로우를 감지하고 border-radius를 자동 조절하는 순수 CSS 트릭을 정리한다.
12컬럼 그리드 없이 반응형 레이아웃 — CSS Grid와 Flexbox 비교
Bootstrap의 12컬럼 시스템을 CSS Grid와 Flexbox만으로 대체하는 현대적 레이아웃 패턴을 비교한다.
SVG 아이콘 — 코드 배포 없이 프로덕트 팀이 직접 관리하는 법
CSS mask-image와 S3를 조합해 개발자 개입 없이 아이콘을 교체하는 패턴을 소개한다.
CSS 한 줄로 업그레이드 — 지금 바로 적용할 수 있는 12가지 모던 CSS 속성
aspect-ratio부터 scrollbar-gutter까지, 한 줄 추가로 스타일시트를 현대화하는 12가지 CSS 속성을 정리한다.
모던 CSS 컴포넌트 아키텍처 — 네이티브 기능만으로 설계하는 컴포넌트 시스템
CSS Nesting, Cascade Layers, Container Queries, :has() 등 네이티브 CSS 기능으로 컴포넌트 기반 아키텍처를 구축하는 방법을 정리한다.
CSS @property — 커스텀 속성에 타입을 부여하는 방법
CSS @property at-rule로 커스텀 속성에 타입 정의, 상속 제어, 폴백을 추가해 렌더링 안정성과 애니메이션 가능성을 확보하는 방법을 다룬다.
CSS :near() — 마우스가 '가까이' 오면 반응하는 새로운 의사 클래스
CSS Working Group에 제안된 :near() 의사 클래스는 포인터 근접성을 감지해 호버 전에 UI를 활성화하는 새로운 상호작용 패턴을 연다.
CSS만으로 커스텀 셀렉트 박스 — JavaScript 150줄이 사라지는 순간
Chrome 135에 도입된 appearance: base-select와 sibling-index()로 JavaScript 없이 완전한 커스텀 드롭다운을 구현하는 방법을 분석한다.