Bezier Curve 활용하기
2023.06.22
목차
• Bezier Curve란 무엇인가?
• Bezier Curve를 활용한 요구사항 구현
• 끝맺음
Bezier Curve란 무엇인가?
Bezier Curve는 컴퓨터 그래픽스에서 사용되는 수학적인 곡선입니다. Bezier Curve는 시작점, 끝점, 그리고 그 사이를 이어주는 여러 개의 control point(조절점)로 이루어져 있습니다. 이 때, control point는 곡선의 모양과 방향을 조절하는 데에 사용됩니다. 따라서, control point의 위치와 방향에 따라 곡선의 모양이 결정됩니다. Bezier Curve는 선형적인 선과는 달리 부드러운 곡선을 그릴 수 있으며, 그래픽 디자인, 컴퓨터 그래픽스, 애니메이션, CAD 등 다양한 분야에서 사용됩니다. 또한, CSS, JavaScript, SVG, Canvas 등 웹 기술에서도 Bezier Curve를 이용하여 곡선 애니메이션 등을 구현할 수 있습니다.
여기서 주목해야 하는 것은 control point 입니다.
control point를 이어서 만든 도형을 컨벡스 헐(convex hull, 볼록 껍질) 이라고 합니다. 곡선의 차수는 control point 의 개수에서 1을 뺀 값입니다. control point 이 2개일 때는 직선, 3개일 때는 2차 곡선이 되는 것입니다. 이 곡선은 그럼 어떻게 그려지는 것이냐, 바로 컨벡스 헐을 따라 그려지게 됩니다.
이렇게 곡선을 그려 svg를 만들 수 있습니다. svg 코드를 보면 path
요소에서 d
라는 속성을 확인할 수 있습니다.
<svg width="200" height="200"> <path d="M 100 20 C 147 20, 180 53, 180 100 C 180 147, 147 180, 100 180 C 53 180, 20 147, 20 100 C 20 53, 53 20, 100 20 M 100 60 C 124 60, 140 76, 140 100 C 140 124, 124 140, 100 140 C 76 140, 60 124, 60 100 C 60 76, 76 60, 100 60" fill="none" stroke="white" /> </svg>
위 코드는 중심점이 (100, 100)인 도넛 모양을 그리는 코드입니다. path
요소에서 d
속성은 경로를 나타내며, 여러 개의 C
명령어를 이용하여 곡선을 그립니다. 위 코드를 실행하면, 중심점이 (100, 100)인 도넛 모양이 그려집니다. 이 때, control point의 위치와 방향에 따라 곡선의 모양이 결정됩니다. 따라서, control point를 조절하면 다양한 모양의 도넛 모양을 그릴 수 있습니다. 이렇듯이 Bezier Curve를 이용하여 SVG에서 도넛 모양을 그릴 수 있으며, 이를 활용하여 다양한 모양의 도형을 그릴 수 있습니다.
Bezier Curve를 활용한 요구사항 구현
P = (1 - t) ** 3 * p1 + 3 * (1 - t) ** 2 * t * p2 + 3 * (1 - t) * t ** 2 * p3 + t ** 3 * p4;
위 식은 control point가 4개인 벡터 방정식입니다. t
는 일정한 속도로 증감하는 값을 의미합니다. 웹 환경에서 일정한 속도로 증감하는 값을 얻을 수 있는 방법은 여러가지가 있지만 그중에서 requestAnimationFrame
을 통해 얻을 수 있습니다.
const endNum = 100; const counting = (timestamp: number) => { if (start === undefined) { start = timestamp; } const elapsed = timestamp - start; if (previousTimeStamp !== timestamp) { const count = Math.min(elapsed / duration, endNum); if (count >= endNum) done = true; } if (elapsed < duration) { previousTimeStamp = timestamp; if (!done) { window.requestAnimationFrame(counting); } } }; useEffect(() => { window.requestAnimationFrame(counting); }, []);
requestAnimationFrame
의 파라미터의 콜백은 task queue, microtask queue와는 별도의 animation frame queue를 통해 비동기로 처리합니다. requestAnimationFrame
을 사용하면 파라미터로 들어오는 콜백을 브라우저 렌더링 주기에 맞춰, repaint가 실행 되기전 호출합니다. 모니터 주사율과 일치, 보통 60fps 입니다. 그리고 최신 브라우저에서는 성능을 위해 백그라운드 탭에 위치한 경우에는 호출이 중단되는 특징이 있습니다.
콜백 argument로 DOMHighResTimeStamp
를 받습니다. DOMHighResTimeStamp
단일 프레임에 실행되기전에 animation frame queue에 쌓여있는 콜백들을 모두 처리하는데 콜백들의 실행 시간을 계산하는 동안 시간이 흐르더라도 각 콜백은 동일한 timestamp를 받습니다. 이를 활용해서 불필요한 콜백 실행을 줄일 수 있습니다. 이 timestamp는 밀리초 단위의 십진수이고 최소 정밀도는 1ms입니다. 참고로 함수 파라미터에서 값을 받지 않고 performance.now()
****api를 통해 대체할 수 있습니다.
이렇게 t
값을 통해 이제 x
, y
의 값을 얻어낼 수 있습니다.
const getPoint = ( count: number, p1: Point, p2: Point, p3: Point, p4: Point ) => { return { x: (1 - count) ** 3 * p1.x + 3 * (1 - count) ** 2 * count * p2.x + 3 * (1 - count) * count ** 2 * p3.x + count ** 3 * p4.x, y: (1 - count) ** 3 * p1.y + 3 * (1 - count) ** 2 * count * p2.y + 3 * (1 - count) * count ** 2 * p3.y + count ** 3 * p4.y, }; };
getPoint( count, { x: 0, y: 0 }, easingConfig[easingType].p2, easingConfig[easingType].p3, { x: 1, y: 1 }, ));
3차 곡선을 그리기 위해서는 위에서 봤던 것처럼 네개의 점이 필요합니다. 곡선의 시작점은 (0,0) 끝점은 (1, 1) 입니다. 그리고 easingConfig
는 css transition 속성에서 기본적으로 제공하는 옵션들인 ease-in
, ease
, ease-in-out
, ease-out
, linear
를 중간점1 (p2), 중간점2 (p3) 의 좌표를 달리해서 구현할 수 있습니다.
const easingConfig: {[key in Props["easingType"]]: EasingValue} = { ease: {p2: {x: 0.25, y: 0.1}, p3: {x: 0.25, y: 1}}, "ease-in": {p2: {x: 0.42, y: 0}, p3: {x: 1, y: 1}}, "ease-in-out": {p2: {x: 0.42, y: 0}, p3: {x: 0.58, y: 1}}, "ease-out": {p2: {x: 0, y: 0}, p3: {x: 0.58, y: 1}}, linear: {p2: {x: 0.5, y: 0.5}, p3: {x: 0.5, y: 0.5}}, };
getPoint
함수의 반환값을 통해 다양한 animation 기능을 구현할 수 있습니다. box element의 크기의 증감을 좀 더 dynamic 하게 한다거나 숫자를 느리게 증가하다가 빠르게 증가하도록 구현할 수 있습니다.
요구사항인 로딩창에서 0에서 시작해 100까지 처음에는 느리게 증가했다가 다시 빠르게 증가하고 숫자 후반부에서는 다시 느리게 증가하는 ease-in-out
방식으로 로딩 퍼센트를 보여줘야 합니다. getPoint
의 y축 값을 활용해서 로딩 퍼센트 증가값을 dynamic하게 할 수 있습니다.
끝맺음
평소 css를 통해 animation을 줄 때 transition 옵션인 ease-in
, ease
, ease-in-out
, ease-out
, linear
를 자주 사용했었던 기억이 있습니다. 그 때는 단순히 어떤 옵션은 animation 효과가 천천히 나타나다가 일정한 시간이 지나면 빠르게 나타난다는 특징만 알고 있었는데요. Bezier Curve를 직접 구현해보면서 어떻게 해당 옵션들이 동작하는지 알게 되었습니다. 또한 다양한 방법으로 활용할 수 있게 되었습니다.