1. 背景画像でシンプルに
最も軽量なのは、linear-gradient を組み合わせた背景画像で左右のギザギザを表現する方法です。
<div class="coupon">
<div class="coupon__body">¥500 OFF</div>
<div class="coupon__side">GET</div>
</div>
.coupon {
display: flex;
height: 96px;
border-top: 1px solid #e63946;
border-bottom: 1px solid #e63946;
position: relative;
overflow: hidden;
}
.coupon::before,
.coupon::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
width: 12px;
background:
radial-gradient(circle at 0 50%, transparent 6px, #e63946 6px, #e63946 7px, transparent 7px) 0 0 / 12px 12px;
}
.coupon::before {
left: 0;
}
.coupon::after {
right: 0;
transform: scaleX(-1);
}
.coupon__body {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #e63946;
padding: 0 20px;
}
.coupon__side {
width: 72px;
background: #e63946;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
2. 擬似要素で動的に生成
円形の擬似要素を多数並べることで、より滑らかなギザギザを実現します。
<div class="ticket">
<div class="ticket__left">
<strong>20%割引</strong>
<small>期間限定</small>
</div>
<div class="ticket__right">
<span>USE</span>
</div>
</div>
.ticket {
--radius: 8px;
--gap: 12px;
--color: #d62828;
display: flex;
width: 320px;
height: 100px;
position: relative;
filter: drop-shadow(0 2px 4px rgba(0,0,0,.1));
}
.ticket__left,
.ticket__right {
position: relative;
background: #fff;
}
.ticket__left {
flex: 1;
border: 1px solid var(--color);
border-right: none;
border-radius: var(--radius) 0 0 var(--radius);
display: flex;
flex-direction: column;
justify-content: center;
padding-left: 16px;
}
.ticket__right {
width: 80px;
background: var(--color);
border-radius: 0 var(--radius) var(--radius) 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: bold;
}
.ticket__left::after,
.ticket__right::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
width: var(--gap);
background-image:
radial-gradient(circle at 50% 0, transparent var(--radius), var(--color) calc(var(--radius) + 1px), transparent calc(var(--radius) + 2px)),
radial-gradient(circle at 50% var(--gap), transparent var(--radius), var(--color) calc(var(--radius) + 1px), transparent calc(var(--radius) + 2px)),
radial-gradient(circle at 50% calc(var(--gap) * 2), transparent var(--radius), var(--color) calc(var(--radius) + 1px), transparent calc(var(--radius) + 2px)),
radial-gradient(circle at 50% calc(var(--gap) * 3), transparent var(--radius), var(--color) calc(var(--radius) + 1px), transparent calc(var(--radius) + 2px)),
radial-gradient(circle at 50% calc(var(--gap) * 4), transparent var(--radius), var(--color) calc(var(--radius) + 1px), transparent calc(var(--radius) + 2px)),
radial-gradient(circle at 50% calc(var(--gap) * 5), transparent var(--radius), var(--color) calc(var(--radius) + 1px), transparent calc(var(--radius) + 2px)),
radial-gradient(circle at 50% calc(var(--gap) * 6), transparent var(--radius), var(--color) calc(var(--radius) + 1px), transparent calc(var(--radius) + 2px));
background-size: 100% calc(var(--gap) * 7);
background-repeat: repeat-y;
}
.ticket__left::after {
right: calc(var(--gap) * -0.5);
}
.ticket__right::before {
left: calc(var(--gap) * -0.5);
transform: rotate(180deg);
}
3. SVGマスクを使った高解像度対応
Retina や印刷時も綺麗に表示したい場合は、SVGマスクが最適です。
<svg width="0" height="0">
<defs>
<mask id="gizaMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle cx="0" cy="12" r="6" fill="black"/>
<circle cx="0" cy="36" r="6" fill="black"/>
<circle cx="0" cy="60" r="6" fill="black"/>
<circle cx="0" cy="84" r="6" fill="black"/>
</mask>
</defs>
</svg>
<div class="clip-coupon">
<div class="clip-coupon__main">¥1,000 OFF</div>
<div class="clip-coupon__action">TAP</div>
</div>
.clip-coupon {
display: flex;
width: 280px;
height: 88px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0,0,0,.15);
}
.clip-coupon__main {
flex: 1;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
color: #e63946;
mask: url(#gizaMask) 100% 0 / 12px 100% no-repeat;
}
.clip-coupon__action {
width: 72px;
background: linear-gradient(135deg, #e63946, #f77f00);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
mask: url(#gizaMask) 0 0 / 12px 100% no-repeat;
}
4. CSS Paint API で完全に動的に
Chrome 系ブラウザ限定ですが、paint() を使えば JavaScript なしで動的にギザギザを描画できます。
/* paint-worklet.js */
registerPaint('giza-border', class {
static get inputProperties() {
return ['--giza-radius', '--giza-gap'];
}
paint(ctx, geom, props) {
const r = props.get('--giza-radius').value;
const g = props.get('--giza-gap').value;
const h = geom.height;
for (let y = r; y < h; y += g) {
ctx.beginPath();
ctx.arc(0, y, r, 0, 2 * Math.PI);
ctx.fillStyle = '#fff';
ctx.fill();
}
}
});
/* メインCSS */
.coupon-paint {
--giza-radius: 6;
--giza-gap: 12;
background: paint(giza-border) right / 12px 100% no-repeat, #fff;
border: 1px solid #e63946;
border-right: none;
border-radius: 8px 0 0 8px;
height: 96px;
width: 240px;
}
上記のように、用途に応じて背景画像・擬似要素・SVGマスク・Paint API の 4 パターンを使い分けることで、様々な環境でチケット風クーポンを実装できます。