Skip to content

Commit f4d2130

Browse files
authored
feat: improve smooth corner painter for rectangles with larger aspect ratios with smooth-corners-v2 (#152)
* feat: 🎸 add a more general painter add painterV2 that better supports rectangle * chore: 🤖 add visualization of v2, also include a comparison
1 parent 124231f commit f4d2130

File tree

2 files changed

+143
-1
lines changed

2 files changed

+143
-1
lines changed

index.html

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
}
2929

3030
.box,
31+
.rect,
3132
p {
3233
display: grid;
3334
place-items: center;
@@ -39,6 +40,12 @@
3940
color: #fff;
4041
}
4142

43+
.rect {
44+
width: 10rem;
45+
height: 30rem;
46+
color: #fff;
47+
}
48+
4249
.avatar {
4350
width: 10rem;
4451
height: 10rem;
@@ -50,7 +57,8 @@
5057

5158
.box,
5259
.avatar,
53-
.poster {
60+
.poster,
61+
.rect {
5462
flex-shrink: 0;
5563
object-fit: cover;
5664
object-position: 50% 100%;
@@ -62,6 +70,42 @@
6270
-webkit-mask-image: paint(smooth-corners);
6371
}
6472

73+
.mask-v2 {
74+
background: linear-gradient(#f34072, #d01257);
75+
mask-image: paint(smooth-corners-v2);
76+
-webkit-mask-image: paint(smooth-corners-v2);
77+
}
78+
79+
.v2-5-1 {
80+
--smooth-corners: 5;
81+
--smooth-corners-radius: 1rem;
82+
--smooth-corners-steps: 360;
83+
}
84+
85+
.v2-5-1d5 {
86+
--smooth-corners: 5;
87+
--smooth-corners-radius: 1.5rem;
88+
--smooth-corners-steps: 360;
89+
}
90+
91+
.v2-5-0d5 {
92+
--smooth-corners: 5;
93+
--smooth-corners-radius: 0.5rem;
94+
--smooth-corners-steps: 360;
95+
}
96+
97+
.v2-4-1 {
98+
--smooth-corners: 4;
99+
--smooth-corners-radius: 1rem;
100+
--smooth-corners-steps: 360;
101+
}
102+
103+
.v2-2-1 {
104+
--smooth-corners: 2;
105+
--smooth-corners-radius: 1rem;
106+
--smooth-corners-steps: 360;
107+
}
108+
65109
.square {
66110
--smooth-corners: 5;
67111
}
@@ -323,6 +367,28 @@
323367
</div>
324368
</section>
325369
<hr />
370+
<section>
371+
<p style="width:100%;">Update: add a smooth corner painter that better supports rectangle.</p>
372+
</section>
373+
<section>
374+
<div>
375+
<p>Original</p>
376+
<img
377+
src='https://media.kitsu.io/anime/poster_images/42722/large.jpg?1580402011'
378+
class='rect mask v2-5-1'
379+
/>
380+
</div>
381+
<div>
382+
<p>V2</p>
383+
<img
384+
src='https://media.kitsu.io/anime/poster_images/42722/large.jpg?1580402011'
385+
class='rect mask-v2 v2-5-1'
386+
/>
387+
</div>
388+
<div class="rect mask-v2 v2-5-1">V2 5 R1</div>
389+
<div class="rect mask-v2 v2-4-1">V2 4</div>
390+
<div class="rect mask-v2 v2-2-1">V2 5</div>
391+
</section>
326392
<!-- Gradient -->
327393
<section>
328394
<div class='box special-16-2 mask'>16, 2</div>

paint.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,83 @@ class SmoothCornersPainter {
5555
ctx.closePath()
5656
ctx.fill()
5757
}
58+
}
59+
60+
class SmoothCornerPainterV2 {
61+
static get inputProperties() {
62+
return ['--smooth-corners', '--smooth-corners-radius', '--smooth-corners-steps']
63+
}
64+
65+
66+
superellipse(a, b, nX = 4, nY, steps = 360) {
67+
if (Number.isNaN(nX)) nX = 4
68+
if (typeof nY === 'undefined' || Number.isNaN(nY)) nY = nX
69+
if (nX > 100) nX = 100
70+
if (nY > 100) nY = 100
71+
if (nX < 0.00000000001) nX = 0.00000000001
72+
if (nY < 0.00000000001) nY = 0.00000000001
73+
74+
const nX2 = 2 / nX
75+
const nY2 = nY ? 2 / nY : nX2
76+
const step = (2 * Math.PI) / steps
77+
const points = t => {
78+
const cosT = Math.cos(t)
79+
const sinT = Math.sin(t)
80+
return {
81+
x: Math.abs(cosT) ** nX2 * a * Math.sign(cosT),
82+
y: Math.abs(sinT) ** nY2 * b * Math.sign(sinT)
83+
}
84+
}
85+
return Array.from({ length: steps }, (_, i) => points(i * step))
86+
}
87+
88+
paint(ctx, size, props) {
89+
90+
const [nX, nY] = props
91+
.get('--smooth-corners')
92+
.toString()
93+
.replace(/ /g, '')
94+
.split(',')
95+
.map(parseFloat)
96+
97+
const halfWidth = size.width / 2
98+
const halfHeight = size.height / 2
99+
const cornerRadius = Math.min(parseFloat(props.get('--smooth-corner-radius')) || size.width / 2, size.height / 2, size.width / 2)
100+
const steps = parseFloat(props.get('--smooth-corner-steps')) || 360
101+
const smoothCorners = this.superellipse(
102+
cornerRadius,
103+
cornerRadius,
104+
nX,
105+
nY,
106+
steps
107+
)
108+
109+
const xOffset = halfWidth - cornerRadius
110+
const yOffset = halfHeight - cornerRadius
111+
112+
const leftBottomCorners = smoothCorners.slice(0, steps / 4).map(({ x, y }) => ({ x: x + xOffset, y: y + yOffset }))
113+
const rightBottomCorners = smoothCorners.slice(steps / 4, steps / 2).map(({ x, y }) => ({ x: x - xOffset, y: y + yOffset }))
114+
const rightTopCorners = smoothCorners.slice(steps / 2, steps * 3 / 4).map(({ x, y }) => ({ x: x - xOffset, y: y - yOffset }))
115+
const leftTopCorners = smoothCorners.slice(steps * 3 / 4, steps).map(({ x, y }) => ({ x: x + xOffset, y: y - yOffset }))
116+
const points = [...leftBottomCorners, ...rightBottomCorners, ...rightTopCorners, ...leftTopCorners]
117+
118+
ctx.fillStyle = '#000'
119+
ctx.setTransform(1, 0, 0, 1, halfWidth, halfHeight)
120+
ctx.beginPath()
121+
points.forEach(({ x, y }, i) => {
122+
if (i === 0) ctx.moveTo(x, y)
123+
else ctx.lineTo(x, y)
124+
})
125+
ctx.closePath()
126+
ctx.fill()
127+
128+
}
129+
130+
131+
132+
58133
}
59134

60135
// eslint-disable-next-line no-undef
61136
registerPaint('smooth-corners', SmoothCornersPainter)
137+
registerPaint('smooth-corners-v2', SmoothCornerPainterV2)

0 commit comments

Comments
 (0)