게시일: 2026년 2월 19일
2025년에 Chrome에서 출시한 CSS 기능 중 하나는 corner-shape입니다.
이렇게 하면 bevel, scoop과 같은 키워드를 사용하여 border-radius이 있는 모서리의 모양을 정의할 수 있습니다. -Infinity과 Infinity 사이의 값을 수신하는 superellipse 함수를 사용할 수도 있습니다.
이 기능과 작동 방식에 관한 개요는 Amit Sheen의 Frontend Masters의 광범위한 도움말을 참고하세요.
2025년 초에 이 기능을 구현하는 동안 다양한 복잡성을 지닌 몇 가지 흥미로운 문제에 직면했습니다. 초타원, Blink의 테두리 페인팅, 2D 그래픽에 벡터 수학 사용에 대해 많이 배웠습니다.
이 문서에서는 제가 배운 내용을 공유하며, 다른 사람에게도 흥미로울 수 있습니다.
볼록 및 오목 도형의 대칭
superellipse (k) 값은 일반적으로 0과 Infinity 사이이며, 0과 1 사이의 값은 오목하고 나머지는 볼록합니다 (1은 bevel). CSS 사양의 superellipse 값은 -Infinity와 Infinity 사이이며 2k을 나타냅니다. 이렇게 하면 양수 값이 음수 값의 거울 이미지처럼 보이므로 대칭이 생성됩니다.
하지만 기본적으로 superellipse 수식은 그렇게 작동하지 않습니다.
superellipse 공식은 xk + yk = 1입니다. 역 공식인 x1/k + y1/k = 1는 시각적으로 대칭적인 곡선을 생성하지 않습니다.
예를 들어 2의 k을 사용하면 다음과 같습니다.
- 파란색 곡선은 라운드
superellipse(y=xn)를 나타냅니다. - 빨간색 곡선은 표준 공식(
y=x1/n)이 적용된scoopsuperellipse을 나타냅니다. - 노란색 곡선은 파란색 곡선 (
y=1-(1-x)n)과 시각적으로 대칭인 곡선을 나타냅니다.
차트에서 볼 수 있듯이 모양이 동일하지 않습니다.
수학에 대해 자세히 설명하지는 않겠지만, 이 문제는 이중 노름과 곡률을 인식하는 방식과 관련이 있습니다.
사양 및 구현 측면에서 여기서는 시각적인 것을 나타내므로 오목한 모양을 계산할 때는 대칭 등가물을 사용합니다. 나머지 수학은 볼록 모양 (k>=1 또는 양수 초타원 값)에서 실행됩니다.
닫힌 형식 수식
다음 과제는 superellipse의 곡선 또는 둘레를 간단한 산술 연산으로 구성된 공식인 닫힌 형식으로 표현하는 것입니다.
이는 성능에 필수적이며, 이를 통해 시스템이 superellipse 렌더링을 그래픽 엔진에 전달할 수 있습니다.
Skia와 같은 그래픽 엔진은 베지어 곡선에 익숙하므로 둘레를 근사하는 베지어 곡선 몇 개로 superellipse를 표현하면 superellipse 곡선을 더 효율적으로 렌더링할 수 있습니다.
다행히 기호 회귀를 사용하면 볼록한 모서리의 절반을 단일 3차 베지어 곡선으로 나타내는 공식을 찾을 수 있습니다.
3차 베지어 곡선에는 다음 네 점이 있습니다.
- 첫 번째 점은 (
0, 1)입니다. - 마지막 점은 실제 슈퍼타원 절반 모서리인
0.51/k,0.51/k입니다. - 첫 번째 제어점은 시작점과 동일한 수준에서 늘어납니다(
a, 1). - 두 번째 제어점은 대각선 절반 모서리입니다(
(0.51/k - b,0.51/k + b)).
여기에서 사용되는 절반 모서리 값은 나중에 다른 계산에 사용할 매우 중요한 좌표입니다.
여기서 a과 b은 기호 회귀를 사용하여 k에서 계산됩니다.
이 네 점을 계산하고 그 사이에 3차 베지어 곡선을 렌더링하면 주어진 k를 갖는 폐쇄형 볼록 반쪽 모서리가 제공됩니다. 그런 다음 결과를 회전하여 나머지 모서리를 채우고 다른 모서리에 적용한 후 뒤집어 오목한 부분을 렌더링할 수 있습니다.
수학적 세부사항을 더 자세히 살펴보지 않고 a 및 b를 계산하는 공식은 다음과 같습니다.
p0 = 1.2430920942724248
p1 = 2.010479023614843
p2 = 0.32922901179443753
p3 = 0.2823023142212073
p4 = 1.3473704261055421
p5 = 2.9149468637949814
p6 = 0.9106507102917086
s = log2(k)
slope = p0 + (p6 - p0) * 0.5 * (1 + tanh(p5 * (s - p1)))
base = 1 / (1 + exp(slope * p1))
logistic = 1 / (1 + exp(slope * (p1 - s)))
a = (logistic - base) / (1 - base)
b = p2 * exp(-p[3] * (s ^ p4))
테두리 및 그림자
시스템은 모서리 둘레의 경로를 계산하는 것 외에도 안쪽 (테두리 또는 인셋 box-shadow) 또는 바깥쪽 (outline 또는 일반 box-shadow)으로 오프셋될 때의 모양을 계산합니다. 기존 그래픽 라이브러리에서는 스트로크를 통해 이 작업을 실행합니다.
하지만 CSS의 테두리와 그림자는 획과 다른 렌더링 특성이 있습니다.
- 테두리가 균일하지 않습니다.
- 예를 들어 상단 테두리는 10픽셀, 오른쪽 테두리는 5픽셀일 수 있으며 모서리는 이 두 테두리 사이에서 보간됩니다.
- 또한 양쪽으로 향하지 않고 안쪽으로 향합니다.
- 그림자와 윤곽선이 획과 정확히 동일하게 렌더링되지 않습니다.
- 대신 모서리가 선명하게 표시되도록 조정됩니다.
일반적인 테두리 및 그림자 렌더링 경로는 둥글거나 그보다 더 볼록한 corner-shape 값 (예: squircle)에 적합하며 scoop보다 더 오목한 모양의 경우 90도 회전할 수 있지만, 테두리나 그림자를 가장자리에 평행하게 오프셋하면 너비가 고르지 않은 모서리가 생성되므로 -1과 1 사이의 corner-shape 값에는 이 기본값이 적합하지 않습니다.
예를 들어 bevel 모서리를 가져와 양쪽으로 테두리를 몇 픽셀씩 오프셋하면 모서리의 중간이 양쪽보다 넓어 보이는 '배' 효과가 만들어집니다.
이를 고려하여 획과 같은 효과를 만드는 것이 목표입니다. 시작 부분에서 모서리 곡선의 법선을 찾아 border 또는 shadow-spread의 너비만큼 길게 만듭니다.
다행히도 이는 하위 타원 (bevel와 round 사이)에만 필요하며 squircle와 같은 하이퍼 타원은 예상대로 작동합니다.
하위 타원 곡선의 법선을 찾으려면 하위 타원과 2차 곡선 등가물이 서로 가까우므로 2차 곡선 등가물의 법선을 찾으면 됩니다.
이전에 계산한 동일한 절반 모서리를 사용하여 중간 지점이 동일한 2차 곡선을 찾고, 2차 제어점을 도출하면 거기에서 법선을 쉽게 계산할 수 있습니다.
법선은 border-width 또는 shadow-spread와 동일한 길이로 계속되고, 결과 곡선을 가장자리 (테두리의 경우 내부 가장자리, 그림자의 경우 외부 가장자리)로 클리핑하여 연속 경로를 만듭니다.
superellipse의 탄젠트를 계산하는 더 수학적으로 정확한 방법이 있지만 이 방법은 효율적이며 테두리와 그림자를 렌더링하는 데 적절한 결과를 생성합니다.
색상 결합
브라우저에서 발생하는 흥미로운 페인팅은 CSS에서 지정되지 않습니다. 색상이나 스타일이 균일하지 않은 테두리를 렌더링합니다. 예를 들어 요소의 상단 테두리가 녹색 실선이고 오른쪽 테두리가 노란색 점선인 경우입니다. 이 경우 마이터는 테두리 가장자리의 관련 모서리와 패딩 가장자리의 관련 모서리 사이를 지나는 절개선입니다. 인접한 가장자리 사이의 경계를 만듭니다.지정되지 않았지만 렌더링은 브라우저 간에 다소 일관됩니다.
Blink (및 기타 브라우저)에서 이 기능이 구현되는 방식은 다음과 같습니다. 그려질 가장자리는 마이터에서 교차하는 다각형처럼 대략적으로 클리핑됩니다. 이 다각형은 관련 가장자리를 포함하지만 다른 가장자리는 포함하지 않도록 계산됩니다. 이렇게 하면 다른 가장자리에 잘못된 스타일과 색상이 적용되는 블리딩이 방지됩니다.
이 다각형은 지금까지 비교적 간단하게 계산할 수 있었습니다. 일반적인 둥근 모서리에서는 모서리 영역이 겹칠 수 없기 때문입니다. 하지만 이는 hypo-ellipses, 특히 오목한 superellipses (음수 superellipse 값)의 경우 달라집니다. 이러한 요소는 단순한 교차 폴리곤이 겹치거나 '번짐'이 발생하기 쉬운 매우 흥미로운 모양을 만들 수 있습니다.
다음 CSS를 고려해 보세요.
.weird {
width: 200px;
height: 200px;
corner-shape: scoop round;
border-radius: 80% 20% / 50% 50%;
border-width: 10px;
border-color: orange purple black blue;
border-style: solid dotted;
}
각 가장자리 (주황색, 보라색 점선, 검은색, 파란색 점선)를 별도로 클리핑한 다음 경로를 그립니다.
다른 세 모서리와 겹치지 않도록 하려면 신중하게 클리핑해야 합니다.
예를 들어 주황색 (상단) 가장자리를 살펴보세요.
이 가장자리를 모두 포함하고 보라색, 노란색, 검은색 가장자리로 블리드되지 않는 정확한 다각형을 찾기가 어렵습니다. 다른 모양은 더 어려울 수 있습니다.
이 프로세스에는 클립 3개가 포함됩니다.
첫 번째 클립에는 전체 모서리가 포함되며, 전체 코너 (마이터 없음)가 포함됩니다. 예를 들면 다음과 같습니다.
이 모양은 두 개의 모서리 (하나는 scoop, 하나는 원형)로 구성되며, 모서리 사이의 가장자리는 최소화되어 있고 끝부분에서 연결됩니다.
이 모양에서 시작하면 반대쪽 가장자리와의 중복이 제거되므로 이제 두 개의 마이터만 문제가 됩니다.
이는 테두리 가장자리와 패딩 가장자리 사이를 지나고 가장자리와 교차하기 직전에 멈추는 다각형을 이 모서리에서 잘라내어 달성됩니다.
시스템은 테두리 가장자리에서 패딩 가장자리까지의 선이 관련 시작점에서 곡선의 접선과 교차하는 지점을 찾습니다 (곡선이 오목한 경우).
이 점이 렌더링된 영역 내에 있으면 프로세스가 중지되고 테두리 상자를 다시 만날 때까지 해당 접선을 따라 계속되어 사각형이 완성됩니다.
그렇지 않으면 간단한 삼각형이 잘릴 수 있습니다.
요약
웹 플랫폼은 웹 디자이너와 개발자에게 상당한 표현력을 제공합니다. 단일 숫자 값을 사용하는 CSS 속성은 정확하고 일관되게 렌더링하기 위해 내부적으로 상당한 복잡성을 숨기는 경우가 있습니다.
corner-shape 기능은 놀라울 정도로 복잡했습니다. 이 문서는 Blink, 다른 브라우저 또는 사양에서 이 기능을 작업하는 향후 개발자를 지원하기 위한 것입니다.