最稳妥方案是用 clip-path 的 path() 实现两侧对称凹槽,通过矢量裁剪精准切出左右半圆缺口;兼容性不足时降级为 radial-gradient 或 base64 svg 背景,需注意 safari、x5 内核及移动端视口单位适配问题。

用 clip-path 实现两侧对称凹槽最稳妥
纯 CSS 做打孔效果,clip-path 是目前兼容性好、控制精准的首选。伪元素或 border-radius 拼接容易在缩放或高 DPI 屏幕上露白边,而 clip-path 是矢量裁剪,边缘锐利。
关键不是“画孔”,而是“裁掉多余部分”——把优惠券容器的左右边缘各切出一个半圆缺口:
.coupon {
clip-path: path('M0,0 H100% V100% H0 Z M20,50 A10,10 0 0,1 30,40 L30,60 A10,10 0 0,1 20,50 Z Mcalc(100%-30),50 A10,10 0 0,1 calc(100%-20),40 Lcalc(100%-20),60 A10,10 0 0,1 calc(100%-30),50 Z');
}
上面的 path() 描述了主体矩形 + 左右两个半圆凹槽(半径 10px,中心 y=50px)。注意:clip-path 的 path() 在 Safari 15.4+ 和 Chrome 102+ 支持良好,但旧版 Safari 需降级为 polygon() 近似模拟。
用 radial-gradient 打孔更轻量但有精度限制
如果只要视觉近似、不追求像素级精确,radial-gradient 叠加背景是更轻量的方案,尤其适合动态尺寸优惠券(比如宽度随内容变化):
.coupon {
background:
radial-gradient(circle at 20px 50%, transparent 9px, #fff 10px),
radial-gradient(circle at calc(100% - 20px) 50%, transparent 9px, #fff 10px),
#ff6b35;
}
这个写法本质是用两个圆形渐变“挖空”左右位置,露出底层白色背景。缺点是:凹槽半径固定(这里 10px),当优惠券高度变化时,y 轴位置不会自适应;另外多层渐变叠加可能在低端安卓 WebView 中出现渲染闪烁。
常见错误是把 transparent 写成 rgba(0,0,0,0) ——某些旧版 Android 浏览器会把后者当成黑色,导致“孔”没挖通。
为什么不用 border-image 或 SVG <use></use>?
border-image 理论上能复用打孔图案,但实际中极易因缩放失真,且无法响应容器宽高变化;<svg></svg> 内联虽精准,但每个优惠券都塞一段 SVG 代码会显著增加 HTML 体积,SSR 渲染时还可能触发重复 ID 报错(如果用了 <use href="#hole"></use>)。
更现实的做法是:把凹槽 SVG 导出为 base64 数据 URI,作为 background-image 单独使用:
.coupon::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Ccircle cx='20' cy='50' r='10' fill='white'/%3E%3Ccircle cx='80' cy='50' r='10' fill='white'/%3C/svg%3E");
pointer-events: none;
}
这样复用率高,也避免内联 SVG 的 ID 冲突问题。但要注意:base64 编码后的 SVG 必须 URL encode 特殊字符(如 , <code>>, '),否则解析失败。
移动端适配时最容易忽略的三个点
凹槽效果在 iPhone 上突然变扁、Android 上偏移、或者横屏后消失,往往不是 CSS 写错了,而是被这些细节绊倒:
-
clip-path的百分比坐标基于元素自身宽高,但若容器设置了transform: scale()(比如某些 UI 库的 zoom 逻辑),会导致裁剪区域错位 - 使用
vh/vmax定义凹槽尺寸时,在 iOS Safari 横屏切换瞬间,vh值会滞后更新,造成短暂错位 - 微信内置浏览器(X5 内核)对
clip-path: path()支持极差,必须检测并 fallback 到radial-gradient方案,可用@supports (clip-path: path(''))做条件加载
真正难的不是画出凹槽,而是让这个小细节在各种机型、各种 WebView、各种缩放状态下都稳住位置。建议把凹槽尺寸统一用 px 或 rem,避开视口单位;同时保留一层无裁剪的备用样式兜底。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











