Skip to content

Commit c90c362

Browse files
committed
Allow each corner to have its own radius
1 parent 8f6d83d commit c90c362

File tree

2 files changed

+262
-39
lines changed

2 files changed

+262
-39
lines changed

README.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,19 @@ import { getSvgPath } from 'figma-squircle'
3636
const svgPath = getSvgPath({
3737
width: 200,
3838
height: 200,
39-
cornerRadius: 24,
39+
cornerRadius: 24, // defaults to 0
4040
cornerSmoothing: 0.8, // cornerSmoothing goes from 0 to 1
4141
})
4242

43+
const svgPath = getSvgPath({
44+
width: 200,
45+
height: 200,
46+
cornerRadius: 24,
47+
cornerSmoothing: 0.8,
48+
// You can also adjust the radius of each corner individually
49+
topLeftCornerRadius: 48,
50+
})
51+
4352
// svgPath can now be used to create SVG elements
4453
function PinkSquircle() {
4554
return (
@@ -65,6 +74,42 @@ function ProfilePicture() {
6574
}
6675
```
6776

77+
## Squircle Params
78+
79+
### cornerSmoothing
80+
81+
> `number`
82+
83+
Goes from 0 to 1, controls how smooth the corners should be.
84+
85+
### cornerRadius
86+
87+
> `number` | defaults to `0`
88+
89+
### topLeftCornerRadius
90+
91+
> `number`
92+
93+
### topRightCornerRadius
94+
95+
> `number`
96+
97+
### bottomRightCornerRadius
98+
99+
> `number`
100+
101+
### bottomLeftCornerRadius
102+
103+
> `number`
104+
105+
### width
106+
107+
> `number`
108+
109+
### height
110+
111+
> `number`
112+
68113
## Thanks
69114

70115
- Figma team for publishing [this article](https://www.figma.com/blog/desperately-seeking-squircles/) and [MartinRGB](https://github.com/MartinRGB) for [figuring out all the math](https://github.com/MartinRGB/Figma_Squircles_Approximation) behind it.

src/index.ts

Lines changed: 216 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,219 @@
11
export interface FigmaSquircleParams {
2-
cornerRadius: number
2+
cornerRadius?: number
3+
topLeftCornerRadius?: number
4+
topRightCornerRadius?: number
5+
bottomRightCornerRadius?: number
6+
bottomLeftCornerRadius?: number
37
cornerSmoothing: number
48
width: number
59
height: number
610
}
711

812
export function getSvgPath({
9-
cornerRadius,
13+
cornerRadius = 0,
14+
topLeftCornerRadius,
15+
topRightCornerRadius,
16+
bottomRightCornerRadius,
17+
bottomLeftCornerRadius,
1018
cornerSmoothing,
1119
width,
1220
height,
1321
}: FigmaSquircleParams) {
22+
const defaultPathParams = getPathParamsForCorner({
23+
width,
24+
height,
25+
cornerRadius,
26+
cornerSmoothing,
27+
})
28+
29+
// Most of the time, all corners will have the same radius
30+
// Instead of calculating path params for all 4 corners,
31+
// we want to use the default path params whenever possible
32+
const topLeftPathPathParams =
33+
topLeftCornerRadius !== undefined
34+
? getPathParamsForCorner({
35+
width,
36+
height,
37+
cornerRadius: topLeftCornerRadius,
38+
cornerSmoothing,
39+
})
40+
: defaultPathParams
41+
42+
const topRightPathPathParams =
43+
topRightCornerRadius !== undefined
44+
? getPathParamsForCorner({
45+
width,
46+
height,
47+
cornerRadius: topRightCornerRadius,
48+
cornerSmoothing,
49+
})
50+
: defaultPathParams
51+
52+
const bottomRightPathPathParams =
53+
bottomRightCornerRadius !== undefined
54+
? getPathParamsForCorner({
55+
width,
56+
height,
57+
cornerRadius: bottomRightCornerRadius,
58+
cornerSmoothing,
59+
})
60+
: defaultPathParams
61+
62+
const bottomLeftPathPathParams =
63+
bottomLeftCornerRadius !== undefined
64+
? getPathParamsForCorner({
65+
width,
66+
height,
67+
cornerRadius: bottomLeftCornerRadius,
68+
cornerSmoothing,
69+
})
70+
: defaultPathParams
71+
72+
return `
73+
${drawTopRightPath(topRightPathPathParams)}
74+
${drawBottomRightPath(bottomRightPathPathParams)}
75+
${drawBottomLeftPath(bottomLeftPathPathParams)}
76+
${drawTopLeftPath(topLeftPathPathParams)}
77+
`
78+
.replace(/[\t\s\n]+/g, ' ')
79+
.trim()
80+
}
81+
82+
function drawTopRightPath({
83+
cornerRadius,
84+
width,
85+
height,
86+
a,
87+
b,
88+
c,
89+
d,
90+
p,
91+
circularSectionLength,
92+
}: CornerPathParams) {
93+
if (cornerRadius) {
94+
return `
95+
M ${Math.max(width / 2, width - p)} 0
96+
C ${width - (p - a)} 0 ${width - (p - a - b)} 0 ${width -
97+
(p - a - b - c)} ${d}
98+
a ${cornerRadius} ${cornerRadius} 0 0 1 ${circularSectionLength} ${circularSectionLength}
99+
C ${width} ${p - a - b}
100+
${width} ${p - a}
101+
${width} ${Math.min(height / 2, p)}`
102+
} else {
103+
return `M ${width / 2} 0
104+
L ${width} ${0}
105+
L ${width} ${height / 2}`
106+
}
107+
}
108+
109+
function drawBottomRightPath({
110+
cornerRadius,
111+
width,
112+
height,
113+
a,
114+
b,
115+
c,
116+
d,
117+
p,
118+
circularSectionLength,
119+
}: CornerPathParams) {
120+
if (cornerRadius) {
121+
return `
122+
L ${width} ${Math.max(height / 2, height - p)}
123+
C ${width} ${height - (p - a)}
124+
${width} ${height - (p - a - b)}
125+
${width - d} ${height - (p - a - b - c)}
126+
a ${cornerRadius} ${cornerRadius} 0 0 1 -${circularSectionLength} ${circularSectionLength}
127+
C ${width - (p - a - b)} ${height}
128+
${width - (p - a)} ${height}
129+
${Math.max(width / 2, width - p)} ${height}`
130+
} else {
131+
return `L ${width} ${height}
132+
L ${width / 2} ${height}`
133+
}
134+
}
135+
136+
function drawBottomLeftPath({
137+
cornerRadius,
138+
width,
139+
height,
140+
a,
141+
b,
142+
c,
143+
d,
144+
p,
145+
circularSectionLength,
146+
}: CornerPathParams) {
147+
if (cornerRadius) {
148+
return `
149+
L ${Math.min(width / 2, p)} ${height}
150+
C ${p - a} ${height}
151+
${p - a - b} ${height}
152+
${p - a - b - c} ${height - d}
153+
a ${cornerRadius} ${cornerRadius} 0 0 1 -${circularSectionLength} -${circularSectionLength}
154+
C 0 ${height - (p - a - b)}
155+
0 ${height - (p - a)}
156+
0 ${Math.max(height / 2, height - p)}`
157+
} else {
158+
return `
159+
L ${0} ${height}
160+
L ${0} ${height / 2}`
161+
}
162+
}
163+
164+
function drawTopLeftPath({
165+
cornerRadius,
166+
width,
167+
height,
168+
a,
169+
b,
170+
c,
171+
d,
172+
p,
173+
circularSectionLength,
174+
}: CornerPathParams) {
175+
if (cornerRadius) {
176+
return `
177+
L 0 ${Math.min(height / 2, p)}
178+
C 0 ${p - a}
179+
0 ${p - a - b}
180+
${d} ${p - a - b - c}
181+
a ${cornerRadius} ${cornerRadius} 0 0 1 ${circularSectionLength} -${circularSectionLength}
182+
C ${p - a - b} 0
183+
${p - a} 0
184+
${+Math.min(width / 2, p)} 0
185+
Z`
186+
} else {
187+
return `L ${0} ${0}
188+
Z`
189+
}
190+
}
191+
192+
interface CornerParams {
193+
cornerRadius: number
194+
cornerSmoothing: number
195+
width: number
196+
height: number
197+
}
198+
199+
interface CornerPathParams {
200+
a: number
201+
b: number
202+
c: number
203+
d: number
204+
p: number
205+
cornerRadius: number
206+
circularSectionLength: number
207+
width: number
208+
height: number
209+
}
210+
211+
function getPathParamsForCorner({
212+
cornerRadius,
213+
cornerSmoothing,
214+
width,
215+
height,
216+
}: CornerParams): CornerPathParams {
14217
const maxRadius = Math.min(width, height) / 2
15218
cornerRadius = Math.min(cornerRadius, maxRadius)
16219

@@ -56,42 +259,17 @@ export function getSvgPath({
56259
const b = (p - circularSectionLength - c - d) / 3
57260
const a = 2 * b
58261

59-
return `
60-
M ${Math.max(width / 2, width - p)} 0
61-
C ${width - (p - a)} 0 ${width - (p - a - b)} 0 ${width -
62-
(p - a - b - c)} ${d}
63-
a ${cornerRadius} ${cornerRadius} 0 0 1 ${circularSectionLength} ${circularSectionLength}
64-
C ${width} ${p - a - b}
65-
${width} ${p - a}
66-
${width} ${Math.min(height / 2, p)}
67-
L ${width} ${Math.max(height / 2, height - p)}
68-
C ${width} ${height - (p - a)}
69-
${width} ${height - (p - a - b)}
70-
${width - d} ${height - (p - a - b - c)}
71-
a ${cornerRadius} ${cornerRadius} 0 0 1 -${circularSectionLength} ${circularSectionLength}
72-
C ${width - (p - a - b)} ${height}
73-
${width - (p - a)} ${height}
74-
${Math.max(width / 2, width - p)} ${height}
75-
L ${Math.min(width / 2, p)} ${height}
76-
C ${p - a} ${height}
77-
${p - a - b} ${height}
78-
${p - a - b - c} ${height - d}
79-
a ${cornerRadius} ${cornerRadius} 0 0 1 -${circularSectionLength} -${circularSectionLength}
80-
C 0 ${height - (p - a - b)}
81-
0 ${height - (p - a)}
82-
0 ${Math.max(height / 2, height - p)}
83-
L 0 ${Math.min(height / 2, p)}
84-
C 0 ${p - a}
85-
0 ${p - a - b}
86-
${d} ${p - a - b - c}
87-
a ${cornerRadius} ${cornerRadius} 0 0 1 ${circularSectionLength} -${circularSectionLength}
88-
C ${p - a - b} 0
89-
${p - a} 0
90-
${+Math.min(width / 2, p)} 0
91-
Z
92-
`
93-
.replace(/[\t\s\n]+/g, ' ')
94-
.trim()
262+
return {
263+
a,
264+
b,
265+
c,
266+
d,
267+
p,
268+
width,
269+
height,
270+
circularSectionLength,
271+
cornerRadius,
272+
}
95273
}
96274

97275
function toRadians(degrees: number) {

0 commit comments

Comments
 (0)