CSS linear() Easing — 자바스크립트 없이 스프링 애니메이션 만들기

9 min read
CSSanimationlinear()스프링 애니메이션easing
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)**다. 목표 값을 넘어갔다가 돌아오는 움직임이다.

styles/easing-compare.css — cubic-bezier로 스프링을 흉내 내는 시도
/* 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()의 원리는 간단하다. 여러 개의 값을 나열하면, 그것을 순서대로 선형 보간으로 이어붙인다.

styles/linear-basics.css — 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의 스프링 유틸리티를 활용해 커브를 추출하는 방법이다.

scripts/generate-spring.js — 스프링 커브 → linear() 값 변환
// 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에 적용

styles/spring-modal.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()는 두 방식 모두에서 쓸 수 있다.

styles/spring-usage.css — transition과 keyframes 비교
/* 방법 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;
}
방식언제특징
transitionhover, focus, 상태 전환트리거에 반응
@keyframes진입 애니메이션, 반복독립적으로 실행

자주 쓰는 스프링 프리셋

직접 계산하기 번거로우면 아래 프리셋을 복붙해서 시작할 수 있다.

styles/spring-presets.css — 용도별 스프링 프리셋
: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이면 충분하다.

styles/spring-with-fallback.css — 미지원 브라우저 폴백
.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 한 줄짜리 변수 선언으로 스프링 애니메이션을 만들 수 있다.


참고:

관심 있을 만한 포스트

CSS animation-timeline — 스크롤 오버플로우를 감지해 Border-Radius 동적 조절하기

CSS scroll() 타임라인으로 컨테이너 오버플로우를 감지하고 border-radius를 자동 조절하는 순수 CSS 트릭을 정리한다.

CSSanimation-timeline

12컬럼 그리드 없이 반응형 레이아웃 — CSS Grid와 Flexbox 비교

Bootstrap의 12컬럼 시스템을 CSS Grid와 Flexbox만으로 대체하는 현대적 레이아웃 패턴을 비교한다.

CSSGrid

SVG 아이콘 — 코드 배포 없이 프로덕트 팀이 직접 관리하는 법

CSS mask-image와 S3를 조합해 개발자 개입 없이 아이콘을 교체하는 패턴을 소개한다.

SVGCSS

CSS 한 줄로 업그레이드 — 지금 바로 적용할 수 있는 12가지 모던 CSS 속성

aspect-ratio부터 scrollbar-gutter까지, 한 줄 추가로 스타일시트를 현대화하는 12가지 CSS 속성을 정리한다.

CSS모던 CSS

모던 CSS 컴포넌트 아키텍처 — 네이티브 기능만으로 설계하는 컴포넌트 시스템

CSS Nesting, Cascade Layers, Container Queries, :has() 등 네이티브 CSS 기능으로 컴포넌트 기반 아키텍처를 구축하는 방법을 정리한다.

CSSContainer Queries

CSS @property — 커스텀 속성에 타입을 부여하는 방법

CSS @property at-rule로 커스텀 속성에 타입 정의, 상속 제어, 폴백을 추가해 렌더링 안정성과 애니메이션 가능성을 확보하는 방법을 다룬다.

CSS@property

CSS :near() — 마우스가 '가까이' 오면 반응하는 새로운 의사 클래스

CSS Working Group에 제안된 :near() 의사 클래스는 포인터 근접성을 감지해 호버 전에 UI를 활성화하는 새로운 상호작용 패턴을 연다.

CSS:near()

CSS만으로 커스텀 셀렉트 박스 — JavaScript 150줄이 사라지는 순간

Chrome 135에 도입된 appearance: base-select와 sibling-index()로 JavaScript 없이 완전한 커스텀 드롭다운을 구현하는 방법을 분석한다.

CSSbase-select