0.4, 0, 0.2, 1은 어떤 움직임인가

0.4, 0, 0.2, 1은 어떤 움직임인가

시리즈 베지어 곡선 읽기 2 / 2
  1. 1베지어 곡선: 곡선은 옆에 놓인 점을 따라온다
  2. 20.4, 0, 0.2, 1은 어떤 움직임인가
0.4, 0, 0.2, 1은 어떤 움직임인가

개발자: “모달 곡선 값 뭘로 할까요?”

디자이너: “0.4, 0, 0.2, 1로 해주세요.”

이 네 숫자를 넘겨 놓고 화면에서 어떤 움직임이 나오는지는 서로 확인하지 않는다. 1편에서 이 숫자가 무엇인지는 봤다. 짧게 되짚으면 이렇다. 네 숫자는 좌표 두 개이고, (0.4, 0)과 (0.2, 1)은 곡선에서 떨어진 자리에 놓인 점 둘의 위치다. 이 글에서는 그 둘을 가운데 두 제어점이라고 부른다. 곡선은 그 둘에 닿지 않고, 나가고 들어오는 방향과 세기만 그 둘이 정한다. 곡선이 실제로 지나는 양 끝 점은 언제나 (0, 0)과 (1, 1)이라 적을 필요가 없고, 그래서 적어 줄 값이 네 개다.

여기까지는 종이 위의 모양이다. 이 글은 그 모양이 화면에서 어떤 움직임이 되는지를 본다. 축에 시간과 진행률이라는 이름이 붙는 순간 무엇이 달라지는지, 왜 네 숫자 가운데 둘만 0과 1 사이에 갇히는지, 그리고 0.4, 0, 0.2, 1이 결국 어떤 움직임인지 순서대로 본다.

CSS의 베지어 곡선은 가로가 시간이고 세로가 진행률이다

0.3초짜리 이동에서 가로 0.5는 0.15초가 지난 순간이고, 세로 0.5는 갈 길의 절반을 갔다는 뜻이다. 펜툴로 그리는 베지어 곡선이 종이 위의 모양이라면, CSS가 쓰는 베지어 곡선은 가로가 시간이고 세로가 진행률인 그래프다. 계산은 똑같고 두 축에 붙은 이름만 다르다. 가로축의 0부터 1까지는 애니메이션이 시작해서 끝날 때까지의 시간이고, 세로축의 0부터 1까지는 목적지까지 얼마나 갔느냐다. 두 축이 이렇게 정해져 있으면 적어 줄 값의 개수까지 정해진다. 그 까닭은 조금 뒤에 본다.

여기서 그래프를 화면으로 착각하기 쉽다. 곡선이 위로 볼록하게 솟았다고 화면의 물건이 위로 뜨는 게 아니다. 솟았다는 것은 같은 시각에 이미 많이 갔다는 뜻일 뿐이고, 물건은 원래 가기로 한 방향으로만 간다.

모달이 화면 아래에서 올라오는 장면을 놓고 보면 어디서 헷갈리는지가 드러난다. 모달은 화면에서 위아래로 움직이고 그래프에도 세로축이 있다. 그런데 그래프의 세로축은 화면의 위아래가 아니라 갈 길 중 얼마나 갔느냐다. 같은 모달을 옆으로 미끄러지게 바꿔도 그래프는 똑같이 생긴다.

왼쪽 그래프는 가로가 흐른 시간, 세로가 진행률이다. 가로축 아래 눈금 다섯이 오른쪽 자국을 찍은 다섯 시각인데, 눈금은 고르게 놓였고 자국은 안 고르다. 자국 사이가 오른쪽으로 갈수록 어떻게 좁아지는지 왼쪽부터 따라가 보라. 다섯 자국은 전부 같은 높이에 있다. 여기 쓴 곡선은 끝까지 느려지기만 하는 값이다. 본문 예시처럼 처음에도 느리고 끝에도 느린 값이면 자국 사이가 넓어졌다 좁아졌다 해서 이 대비가 안 보인다.

그럼 왜 숫자가 네 개인가. 점은 네 개인데 CSS가 받는 값은 두 점의 좌표뿐이다. 시작점이 언제나 (0, 0)이고 끝점이 언제나 (1, 1)이기 때문이다. 0초에 0%에서 나와 주어진 시간을 다 쓰면 100%에 닿는다는 것은 어느 곡선에서도 바뀌지 않는다. 안 바뀌는 값이라 적을 필요가 없고, 남은 가운데 두 제어점의 좌표 네 개가 그대로 cubic-bezier(x1, y1, x2, y2)다. x가 가로, 곧 시간이고 y가 세로, 곧 진행률이라, 네 숫자는 시간, 진행률, 시간, 진행률 순으로 늘어선다.

곡선 모양이 서로 다른 두 경우다. 두 패널에서 가운데 두 제어점만 자리가 다르고, 양 끝 두 점은 아래위 두 가로선 위에 나란히 놓여 있다.

CSS 키워드도 결국 같은 좌표다. 스펙이 밝혀 둔 값을 보면 ease-in-out은 (0.42, 0, 0.58, 1)이고 ease-out은 (0, 0, 0.58, 1)이다.W3C, CSS Easing Functions Level 1, Cubic Bézier easing functions 섹션. ease-in-out Equivalent to cubic-bezier(0.42, 0, 0.58, 1). / ease-out Equivalent to cubic-bezier(0, 0, 0.58, 1). 2026년 7월에 w3.org/TR/css-easing-1/에서 원문을 확인했다. 키워드를 쓰든 괄호에 숫자를 적든 그리는 것은 같은 곡선이고, CSS는 이 곡선을 이징 곡선이라고 부른다.

아래 슬라이더로 네 값을 직접 옮겨 보면 좌표와 곡선과 움직임이 한 화면에서 같이 바뀐다.

cubic-bezier(0.42, 0, 0.58, 1)

네 숫자를 밀어 보자. 위는 가로가 시간, 세로가 진행률인 곡선이고, 아래 사각형은 그 타이밍으로 왼쪽에서 오른쪽으로만 간다. 재생을 누르면 둘이 같이 움직인다.

방금 민 슬라이더 네 개는 좌표다. 그런데 곡선 위의 점을 실제로 구할 때 쓰는 값이 하나 더 있다. 두 점 사이를 어디서 나눌지 정하는 비율인데, 1편에서 이 비율을 0.3으로 놓고 세 번 나눠 봤더니 나온 점의 가로 좌표가 0.3이 아니라 0.2412였다. 둘 다 0에서 1까지라 같아 보여도 비율과 가로 좌표는 서로 다른 값이다.둘은 서로 다른 값이라, 비율을 0.5로 놓아도 가로 좌표가 0.5라는 보장이 없다. 어느 시점의 진행률을 알려면, 그 시점에 해당하는 비율을 거꾸로 찾은 다음 그 비율로 세로 좌표를 내야 한다.

가로축이 시간이라서 네 숫자에는 규칙이 하나 붙는다.

시간인 x만 0과 1 사이에 갇힌다

영상 재생 바의 손잡이는 왼쪽에서 오른쪽으로만 가고 저절로 되돌아가지 않는다. 이징 곡선의 가로축도 한 방향으로만 간다. 그래서 네 숫자 가운데 x 자리인 첫째와 셋째는 0과 1 사이를 벗어나면 값 자체가 무효이고, y 자리인 둘째와 넷째는 1을 넘어도 되고 음수여도 된다.

스펙은 가운데 두 제어점의 x가 0과 1 사이여야 하고 아니면 정의가 무효라고 적어 뒀다. y에는 같은 제한이 없다.W3C, CSS Easing Functions Level 1, Cubic Bézier easing functions 섹션. The x coordinates of P1 and P2 are restricted to the range [0, 1] / Both x values must be in the range [0, 1] or the definition is invalid. y에 제한이 없다는 것은 문법 줄이 정한다. cubic-bezier( <number [0,1]> , , <number [0,1]> , )로 x 두 자리에만 범위가 붙어 있다. 2026년 7월 확인.

x가 0과 1 밖으로 나가면 곡선이 한 번 왼쪽으로 되돌아가는 구간이 생길 수 있다. 그러면 0.3초 자리에 세로줄을 하나 그었을 때 곡선이 그 줄을 두 번 지나가서 진행률 값이 두 개가 되고, 그 순간에 무엇을 그려야 할지 정할 수가 없다. 재생 바의 손잡이가 저절로 왼쪽으로 되돌아가는 재생이 없는 것과 같은 이유다.

그래서 스펙은 되돌아가는 구간이 절대 안 생기는 범위를 잡아 x를 그 안에 가둬 뒀다. 0과 1 사이에 있기만 하면 어떤 값을 넣어도 안전하다.0과 1 사이를 잘게 나눠 x1과 x2의 조합 4만 쌍 남짓을 전부 훑어 봤더니 되돌아가는 구간이 한 번도 안 나왔다(2026년 7월, 직접 계산). 거꾸로 밖의 값이 전부 되돌아가는 것은 아니다. x1을 0 아래로 내리거나 x2를 1 위로 올리면 바로 나오지만, x1을 1 위로 올리는 정도로는 안 나오는 값도 있다. 스펙의 제한은 안 되는 값을 전부 골라낸 것이 아니라 언제나 되는 범위를 잡아 둔 것이다.

y는 사정이 다르다. 서랍을 세게 밀면 끝까지 나갔다가 살짝 도로 들어온다. y가 1을 넘으면 화면에서도 같은 일이 벌어져서, 물건이 목적지를 지나쳤다가 되돌아오는 구간이 생긴다. 이 움직임을 모션에서 오버슛이라고 부른다. cubic-bezier(0.34, 1.56, 0.64, 1)이 그런 값인데, 둘째 숫자가 1.56이라 곡선이 목적지를 한 번 지나쳤다 돌아온다. 이 글이 예로 고른 값이고, 앞의 슬라이더에 오버슛 프리셋으로 들어 있다.둘째 숫자는 앞의 제어점이 가진 세로 좌표다. 그 값이 1을 넘어서 오버슛이 나는데, 곡선 자체가 1.56까지 솟는 것은 아니다. 제어점은 곡선이 들르는 자리가 아니어서, 실제로 곡선이 닿는 가장 높은 값은 1.1에 조금 못 미친다.

반대로 y가 음수면 물건이 나가는 방향과 반대쪽으로 살짝 물러났다가 간다. 버튼이 눌리기 전에 잠깐 움츠리는 듯한 움직임이 이렇게 나온다. 진행률이 잠깐 0 아래로 내려간다는 뜻인데, 시간과 달리 진행률은 음수여도 그릴 그림이 하나로 정해진다. 0.1초에 어디 있는지만 답이 나오면 되고, 그 자리가 시작점 뒤여도 상관없다.

가로선 두 줄이 진행률 0과 진행률 1이고, 두 패널에 걸쳐 이어져 있다. 왼쪽은 y가 1을 넘을 때다. 곡선이 위 가로선 위로 솟았다 내려오는 자리를 보라. 오른쪽은 y가 음수일 때다. 곡선이 아래 가로선 밑으로 내려갔다 올라오는 자리를 보라. 두 패널 모두 가운데 두 제어점을 함께 찍고 네 점을 점선으로 이어 뒀다. 그 점선이 곡선보다 훨씬 바깥으로 나가 있다.

앞의 슬라이더에서 오버슛 프리셋을 눌러 보면 y만 1 위로 올라가고 x는 0과 1 사이를 안 벗어난다. 스펙이 건 제한이 슬라이더가 움직이는 범위로 그대로 드러나 있다.

그래서 0.4, 0, 0.2, 1은 어떤 움직임인가

여기까지 왔으니 처음의 그 값을 판에 올려놓을 수 있다.

가로는 시간이고 세로는 진행률이다. 판의 네 변이 0과 1이다. 왼쪽 아래 (0, 0)에서 시작해 오른쪽 위 (1, 1)에서 끝나는 굵은 선이 cubic-bezier(0.4, 0, 0.2, 1) 곡선이고, 점선으로 이은 가운데 두 점이 (0.4, 0)과 (0.2, 1)이다. 두 점선 손잡이가 서로 엇갈려 있다. 곡선이 왼쪽 아래에서 가로로 붙어 떠나고 오른쪽 위에서 가로로 붙어 들어오는 것을 보라. 출발과 도착이 둘 다 느리다는 뜻이다.

두 점선 손잡이가 엇갈려 있는 것이 이 값의 성격이다. 아래쪽 손잡이는 오른쪽으로 길게 뻗어 출발을 붙잡고, 위쪽 손잡이는 왼쪽으로 길게 뻗어 도착을 붙잡는다. 곡선이 양쪽 끝에서 가로선에 붙어 나가고 들어오는 게 그 결과다.

숫자로 보면 이렇다. 0.3초짜리 이동이라 치고 시간을 따라가 보자.

  • 시작하고 10%(0.03초)가 지났을 때 겨우 2.6% 갔다. 거의 제자리다.
  • 시간이 절반(0.15초) 지났을 때 이미 77.6% 가 있다.
  • 그리고 마지막 20%(0.06초) 동안 남은 2.5%를 간다.

앞은 뜸을 들이고, 가운데서 확 가고, 끝에서 아주 길게 미끄러져 멈춘다. 흔히 부드럽다고 말하는 움직임이 이 모양이다. 시간의 5분의 1을 남은 2.5%에 쓰기 때문에 멈추는 순간이 눈에 안 띈다.

핸드오프에서 이 네 숫자를 받았을 때 물어볼 것도 여기서 나온다. 출발이 느린 게 맞나, 도착이 느린 게 맞나, 그 둘이 얼마나 차이 나야 하나. 앞의 판에서 슬라이더를 밀어 보면 첫째와 둘째 숫자가 출발을, 셋째와 넷째가 도착을 쥐고 있다는 것이 손에 잡힌다.

정리: 네 숫자를 좌표 두 개로 읽기

네 숫자는 외울 값이 아니라 읽을 수 있는 자리다. 가운데 두 제어점을 어디에 놓았는지가 그 넷에 적혀 있다.

  • 가운데 두 제어점은 곡선이 지나가는 곳이 아니라 나가고 들어오는 방향과 세기를 정한다.
  • CSS로 넘어오면 가로가 시간이라 그래프가 위로 부풀어도 물건이 위로 뜨지 않고, 양 끝 두 점은 늘 같은 자리라 적어 줄 값이 네 개다.
  • 시간을 나타내는 x만 갇혀 있는 것은 같은 순간에 진행률이 두 개일 수 없기 때문이다.

값을 어떻게 고를지는 모션 디자인 원칙: easing과 duration을 정하는 기준에서 다룬다.

다음에 핸드오프로 값을 받으면 네 숫자를 두 좌표로 끊어 읽고, 앞의 좌표가 출발을, 뒤의 좌표가 도착을 쥐고 있다고 보면 된다.

그 네 숫자가 뭘 가리키느냐고 누가 다시 물으면, 이제 제어점 두 개의 자리로 답할 수 있다.

이 글, 어떠셨어요?

이 글에 오류가 있나요?

사실이 틀렸거나 오탈자, 깨진 링크를 발견하셨다면 알려주세요. 확인하고 고치겠습니다.