CSS如何实现内凹角效果
编辑推荐:诚征广告商金主入驻此广告位置,如有感兴趣的金主,欢迎邮件至:airenliao@gmail.com咨询相关合作事宜!!!(^_^)
特别声明:此篇文章内容来源于@ANA TUDOR的《Scooped Corners in 2018》一文。
记得@Lea Verou的《CSS Secrets》一书和前几天@Chris Coyier刚发的帖子都介绍了CSS怎么实现元素斜切口的效果。我也尝试着借助Vue的能力,把这种效果构建成一个Vue组件。我把这种效果定义为外切口。而今天将要聊的是与其刚好相反的一个效果:CSS如何实现内凹角的效果。

上图展示的效果就是接下来所要聊的内凹角的效果。也就是说,通过下文的介绍,我们可以知道这种效果是如何做的,而且如何在多个元素上实现这样的内凹角效果。在实现这样的效果当中,将会遇到些什么棘手的问题,又是怎么绕过这些问题的。
最初的想法:box-shadow
对于box-shadow的属性,想必大家已经非常了解了,如果你从未接触过box-shadow属性,那么强烈建议您花一点时间去了解一下box-shadow相关的知识。这样能帮助你更好的理解后续的内容。
我先假设你对box-shadow有了一定的了解。就算你不了解,也没有关系。你也可以继续后面的内容。假设我们有一个div的元素。给这个元素添加了一个.box的类名:
<div class="box"></div>
我们可以显式的给这个.box元素设置大小或者通过其自己的内容来决定大小,不管是哪种方式,都并不很重要。这里为了简单起见,给其设置了max-width和min-height(也是用来设置其大小的)。另外为了能在浏览器中看到效果,其添加了一个outline的效果,让其看起来有边框的样子。或许你会问,为什么不直接使用border呢?这个问题留给大家去思考吧,因为不是这篇文章要探讨的内容。
.box {
outline: solid 2px;
max-width: 15em;
min-height: 10em;
}
接下来,通过伪元素::before来创建一个正方形,其边长等于圆角的直径(或者半径--r的两倍),而且对这个伪元素使用绝对定位。另外为了能在浏览器中看到效果,给这个伪元素添加了一个box-shadow和background属性。这只是用来辅助大家理解的,后续会删除的。
:root {
--r: 2em;
}
.box {
position: relative;
&::before {
content: '';
position: absolute;
padding: var(--r);
box-shadow: 0 0 7px #b53;
background: #95a;
}
}
特别声明:本文的实例代码都来自于@ANA TUDOR的《Scooped Corners in 2018》一文。不同的是我把文章中的Sass变量换成了CSS自定义变量。后续内容如无特别说明,都将类似的做了修改。
这个时候看到的效果如下:
效果如你所期望的一样。接下来对伪元素::before的border-radius值设置为50%,让它成为一个圆形,并且给它设置一个margin的负值,值等于它的半径--r。伪元素的中心点和它的父容器.box的左上角(0,0)重合。为了让溢出的.box的伪元素能隐藏起来,需要在.box中添加一个overflow:hidden。
:root {
--r: 2em;
}
.box {
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
padding: var(--r);
box-shadow: 0 0 7px #b53;
background: #95a;
margin: calc(var(--r) * (-1));
border-radius: 50%;
}
}
现在的结果是这样的:
但这样的效果仍然不是我们想要的。为了达到我们想要的效果,我们需要使用box-shadow的第四个参数值:阴影扩展半径。如果你想了解box-shadow添加第四个参数值的效果,可以看下面这个Demo:
你可能已经猜到我们下一步要做什么了。把background和box-shadow前三个值(x和y轴的偏移值以及模糊半径)设置为0,并给box-shadow的扩展半径设置为一个较大的值。
box-shadow: 0 0 0 300px;
下面的这个示例演示了box-shadow的扩展半径如何让阴影效果覆盖容器更多的面积。
这里用到的一个技巧是让box-shadow有足够大的扩展半径,这样让伪元素的阴影能覆盖其容器更多的面积。这是非常有意思的一点,给.box设置box-shadow以及给其伪元素添加一个半透明的阴影效果。
.box {
overflow: hidden;
position: relative;
margin: .25em auto;
min-width: 15em;
max-width: 15em;
min-height: 10em;
border-radius: 1em;
&:before {
position: absolute;
margin: calc(var(--r) * -1);
padding: var(--r);
border-radius: 50%;
box-shadow: 0 0 0 300px rgba(#95a, .75);
content: ''
}
}
其实这是很关键的一步,如果你不仔细看,你或许会认为,那个凹角的效果是box-shadow实现的。或许你和我一样会纳闷,box-shadow是如何实现透明凹角的效果。事实并非如此,透明凹角部分是伪元素::before的background-color为transparent,而整个紫色部分是由::before的box-shadow实现的(就是阴影扩散半径有足够大的值,能铺满.box的容器大小)。我录一个视频给大家看看,或许能比文字更好的说明一切原理:

是不是一图胜过千言万语呀。
上面看到的效果,不难发现,凹角的大小是固定的。好在我们这里使用了CSS的自定义属性。因为使用CSS自定义属性之后,可以很容易的通过JavaScript来修改这个属性。这样一来,就可以很好的控制凹角的大小。比如:
:root { --r: 50px }
.box {
padding: var(--r);
&:before {
margin: calc(-1*var(--r));
padding: inherit;
}
}
这是实现凹角效果的关键样式。具体的不多说了,能只要仔细阅读上面的内容,你就能明白为什么。
值得一提的是,我们前面看到的效果都是.box中没有任何内容。也就是说.box里有内容的时候,我们是需要在样式上做一定的调整的。为什么这么说呢?先来看一个效果:

要解决这个问题,很简单,咱们只需要在.box的伪元素::before上添加z-index属性,并且给其设置值为-1。
另外通过.setProperty()来修改--r的值。这需要一些JavaScript代码来支持:
// 获取id为r的input元素 和 output元素
const _R = document.getElementById('r'),
_O = _R.nextElementSibling.querySelector('output');
// 设置一个变量v
let v;
// 创建一个update函数,更新--r的值
function update() {
if(v !== +_R.value) {
document.body.style.setProperty(`--r`, `${_O.value = v = +_R.value}px`)
}
};
update();
_R.addEventListener('change', update, false);
_R.addEventListener('input', update, false);
最终效果如下:
现在离我们想要的效果越来越近了。我们已经知道如何通过box-shadow给单个.box设置单个凹角的效果。那么如果我们想要给一个元素添加四个凹角效果,怎么实现呢?想想,如果你想得出来,可以立马动手试试,就算你想不出来,也并不要紧,后面我们会介绍怎么给.box盒子的每个角添加凹角的效果。
那么到这一步,咱们先暂停一下。上面我们看到的是CSS的自定义属性和JavaScript来实现想要的凹角效果。那么咱们先暂停一步,来看看怎么通过Vue来实现上面示例的效果。
有关于Vue的代码这里就不展示了。详细的可以查看上面Demo的代码,其实你还可以添加其他的参数,比如除了给scooped-corners组件传凹角半径值之外,还可以传border-radius和background-color之类。感兴趣的可以尝试一下,并且欢迎在下面的评论中分享您的成果。
就用这种技术
接下来,咱们再深入一点,看看怎么运用这种技术来实现文章开头展示的效果。这里有一点不一样,伪元素的中心点与盒子不致,但他们都有一个共同点,伪元素的中心点,在每个盒子的顶点处。
使用的HTML结构非常简单,这里使用了4个<article>元素(相当于前面所讲的.box元素),在<article>元素中包含了一些文本内容:
<article>
<h3>Yogi Bear</h3>
<section>
<p>Smaaaarter than the average bear!</p><a href="#">go</a>
</section>
</article>
...
<body>包含了四个<article>元素,还有一个<header>元素,整个布局效果采用的是Flexbox。从文章开头的效果上来看,<header>宽度非常宽,然后每行有一个个或两个<article>元素。具体每行展示一个还是两个,这取决于浏览器视窗的宽度。

如果我们每一行只有一个<article>时,那么元素上就不会有凹角的效果,这个时候需要把凹角的半径设置为0。否则我们就要设置一个非零的半径,也就是说--r的值不为0。
:root {
--minW: 15rem; /* 每个article元素的最小宽度 */
--m: 1rem; /* 每个article元素的margin值 */
--r: 0px; /* 每个article元素上凹角的半径 */
}
article {
margin: var(--m);
min-width: var(--minW);
width: 21em;
}
@media (min-width: 2*($min-w + 2*$m)) {
html {
--r: 4rem;
}
article {
width: 40%;
}
}
特别注意,CSS自定义属性不能用于媒体查询的条件中,但可以用于媒体查询的区块内。
现在我们考虑一下,每行有两个<article>元素(当然,每个元素都有一个内凹角,因为这个才是我们感兴趣的东东)。
第一个元素中,凹角的圆形在它的父元素的最右边边,也就是left:100%。为了将凹角圆中心点x坐标移到其父元素的右边缘,我们就需要减去圆的半径--r,那么left的值就变成了calc(100% - var(--r))。但我们不想让它出现在右边,而是希望它在<article>元素向右移--m。这样我们就可以算出我们最终想要的一个值:
left: calc(100% - var(--r) + var(--m));
同样的沿着y轴,我们给凹角设置top: 100%,可以把整个凹角圆移到盒子的底部边缘,同样让圆心能和边缘重合,需要把凹角上移凹角的半径--r。如此一来,top的值就变成了calc(100% - var(--r))。最后,类似于left一样,为了让凹角中心点在其父元素底线边缘下,需要加上一定的偏移量--m。
top: calc(100% - var(--r) + var(--m));
对于第二个<article>元素(同一行的第二部分),其垂直方向的偏移量具有相同的值:
然而,对于水平方向,凹角是从左边界开始,也就是left处0%开始,如此要把凹角放到元素左边缘上,需要向左移动一个--r,如此得到left:calc(0% - var(--r))。然而,最后的位置,咱们同样还需要向左偏移--m。最终left的值:
left: calc(0% - var(--r) - var(--m));
对于第三个<article>元素,在x轴上的偏移量与第一个<article>相同:
第三个元素的垂直方向,凹角的顶部沿其容器顶部边缘开始,也就是top: 0%。如果把凹角圆中心点放在父容器的顶部缘,同样要向上移动一个半径--r,如此得到top: calc(0% - var(--r))。但要把凹角的圆心高于父容器的顶部边缘,那么还需要向上移动--m。这样可以得到top的值:
top: calc(0% - var(--r) - var(--m));
对于最后一个,其水平方向的偏移量和第二个具有相同的值,垂直方向的偏移量和第三个具有相同的值。
所以四个元素对应的left和top的偏移量如下:
article:nth-of-type(1) { /* 1st */
left: calc(100% - var(--r) + var(--m));
top: calc(100% - var(--r) + var(--m));
}
article:nth-of-type(2) { /* 2nd */
left: calc( 0% - var(--r) - var(--m));
top: calc(100% - var(--r) + var(--m));
}
article:nth-of-type(3) { /* 3rd */
left: calc(100% - var(--r) + var(--m));
top: calc( 0% - var(--r) - var(--m));
}
article:nth-of-type(4) { /* 4th */
left: calc( 0% - var(--r) - var(--m));
top: calc( 0% - var(--r) - var(--m));
}
这意味着凹角圆的中心位置取决于<article>元素之间的间距(这个间距是我们设置的margin: var(--m)的两倍),凹角的半径是--r。在一对水平和垂直的乘数因子分别是--i和--j,而且他们的最初的值都是-1。
对于第一行的两个<article>元素(第一行是一个2 x 2的网格),我们需要改变垂直方向的乘数因子--j为1,这样就可以让凹角的圆心在y轴上低于父容器底部边缘;而对于奇数的<article>(第一列),需要改变水平方向的乘数因子--i为1,这样就可以让凹角的圆心在x轴上位于父容器的右侧边缘。
/* multipliers initially set to -1 */
html {
--i: -1;
--j: -1
}
h3, section {
&:before {
/* set generic offsets */
top: calc((1 + var(--j)) * 50% - var(--r) + var(--j) * var(--m));
left: calc((1 + var(--i)) * 50% - var(--r) + var(--i) * var(--m));
}
}
@media (min-width: 2*($min-w + 2*$m)) {
article {
/* change vertical multiplier for first two (on 1st row of 2x2 grid) */
&:nth-of-type(-n + 2) {
--j: 1
}
/* change horizontal multiplier for odd ones (on 1st column) */
&:nth-of-type(odd) {
--i: 1
}
}
}
注意,第一行的的两个凹角位置位于<article>中的<section>元素上,所以这两个元素的凹角使用的是<section>的伪元素::before;另第二行的两个凹角位置位于<article>中的<h3>元素上,因此这两个凹角用的是<h3>的伪元素::before。前两个元素的<h3>元素的::before的半径--r设置为0,后两个元素中<section>的::before的--r设置为0。
@media (min-width: 2*($min-w + 2*$m)) {
article {
&:nth-of-type(-n + 2) h3,
&:nth-of-type(n + 3) section {
&:before {
--r: 0 ;
}
}
}
}
以类似的方式,我们为<article>元素的子元素添加不同的样式:
h3, section {
--p: .5rem;
padding: $p;
}
@media (min-width: 2*($min-w + 2*$m)) {
article {
&:nth-of-type(-n + 2) section,
&:nth-of-type(n + 3) h3 {
padding-right: calc(.5*(1 + var(--i))*(var(--r) - var(--m)) + var(--p));
padding-left: calc(.5*(1 - var(--i))*(var(--r) - var(--m)) + var(--p));
}
}
}
最终效果如下:
特别声明:今天使用CSS自定义属性,在媒体查询的条件中使用自定义属性踩了一个坑。那是因为我想在代码中统一使用CSS自定义属性来替代Sass这样处理器的变量。一直以为在CSS的媒体查询的条件中使用CSS自定义属性是OK的,结果实测代码的时候才发现不支持。最后查找了一下原因:
The
var()function can be used in place of any part of a value in any property on an element. Thevar()function can not be used as property names, selectors, or anything else besides property values. (Doing so usually produces invalid syntax, or else a value whose meaning has no connection to the variable.) —— From the spec
值得庆達的是,你可以使用PostCSS插件postcss-media-variables来做处理。感兴趣的可以自己试试。说实话,再一次感叹PostCSS的神奇之处和无所不能。
潜在的问题
上面的示例看上去完美,方法简单而又能跪浏览器兼容。或许你已经发现了,上例是在一个特定情况下想的结果,但很多时候我们总不是这么的幸运。哪一天需求一变,是不是还能如此轻易而又完美的实现呢?
首先,我们需要用一个伪元素来做这个凹角,当你只需要一个(比如上面看到的示例)或者两个的时候,都不是问题,但有的时候元素的四个角都需要这样的凹角时,那么我们就需要引入一个额外的元素。另外当你的伪元素被其他功能(比如Icon)占用时,你也不得不为此效果添加一个额外的标签元素。蛋疼了吧!
其次上面示例中的background是一个纯色,但我们不可能总是在使用纯色背景的场景中。如果我们想要一个半透明的或者渐变的背景,或者在一张背景图片之下,那么凹角将会成为我们的一个痛点,甚至会说,这个没法实现。
因此,我们需要探索其他更可靠的方案,并且也能让它得到众多浏览器的支持。
灵活性和良好的浏览器支持?是SVG?
想到SVG并不奇怪,但是如果我们想要灵活一点,浏览器兼容性全面一点,SVG可以说是一个最好的解决方案。在.box容器中包含了一个<svg>元素,而且放置在内容的前面。SVG中包含了一个<circle>元素,在这个元素上设置了r属性。
<div class='box'>
<svg>
<circle r='50'/>
</svg>
TEXT CONTENT OF BOX GOES HERE
</div>
让svg相对于.box元素做相对定位,将将其大小设置为能完全覆盖父容器:
.box {
position: relative;
}
svg {
position: absolute;
width: 100%;
height: 100%;
}
到目前为止,没有什么有趣的东西,所以给<circle>添加一个id属性,并且使用SVG的<use>元素来复制多个id相同的<circle>:
<circle id='c' r='50'/>
<use xlink:href='#c' x='100%'/>
<use xlink:href='#c' y='100%'/>
<use xlink:href='#c' x='100%' y='100%'/>
看到这里,是不是会觉得比使用::before伪元素要来得简便,而且也非常方便,就算你要移去一个或多个凹角(示例效果的紫色部分),你只需要少使用几个<use>去克隆就行了。
从上例效果中可以看到,.box的四个角落都圆圈在那了,但这并不是我们想要的凹角,对吧!不要纳闷了,我们的做法是对的。请接着往下看。接下来要做的就是把这些圆圈放到一个<mask>中,给这个<mask>设置一个white的填充色(fill属性来搞定)。同时在其里面使用<rect>元素设置一个和SVG元素一样大小的矩形,主要用来覆盖整个SVG。然后我们在另一个再次使用<use>来调用这个已创建好的<mask>:
<mask id='m' fill='#fff'>
<rect id='r' width='100%' height='100%'/>
<circle id='c' r='50' fill='#000'/>
<use xlink:href='#c' x='100%'/>
<use xlink:href='#c' y='100%'/>
<use xlink:href='#c' x='100%' y='100%'/>
</mask>
<use xlink:href='#r' fill='#f90' mask='url(#m)'/>
最终效果如下:
同样的,我录制了一个动图来演示效果中的每一个元素:

特别注意:如果
.box中有内容,建议放置在svg元素之后。当然也可以放置在其前面,如果放置在前面,svg在做定位时,需要显式的设置top、right、bottom和left之类的值。至于为什么,这里不做过多的阐述,感兴趣的同学可以自己去深究其中的为什么。
如果.box有文本,需要把.box的padding的值和圆角的半径设置相同,同样的,如果使用了CSS自定义属性,可以使用JavaScript来控制它。最好把圆的半径和.box的padding使用同一个CSS自定义属性--r。这样会更好的控制一点:
当然,我们的背景不再局限于使用一个纯色了。它可以是半透的(就比如上面演示的一样),或者可以使用SVG的渐变和图案填充来实现它。后者也允许我们使用一个或多个背景图像。
我喜欢的就是CSS
或许你会说,我不懂SVG,我就是想使用CSS来实现。其实很高兴你能这样的深究与思考。事实上我们的确可以使用CSS来实现这样的效果。
遗憾的是,使用CSS的方案目前为止并不是所有浏览器都能支持,但使用CSS让我们把事情变得更简化,而且在不远的将来,它们肯定是能得到众多浏览器支持的。
在HTML元素上使用CSS的mask
这里我们移除SVG所有的东西,然后使用CSS,可以在.box元素上设置一个background(可以是一个纯色、半透明、渐变、图像或者多背景,甚至是你任何你想要的CSS)和mask属性。
.box {
/* any kind of background we wish */
mask: url(#m);
}
注意,在HTML元素上使用一个
svg元素,这个元素里有我们前面使用的mask元素。不幸的是,到目前为止只在Firefox浏览器可以看到效果。
使用CSS设置圆半径
这意味着,需要把<circle>中的r属性删除,然后在CSS中给其设置半径的大小,这里设置的半径大小与.box容器的padding值一样:
.box {
padding: var(--r);
}
[id='c'] {
r: var(--r);
}
这样一来,如果我们改变半么--r的值,凹角的大小和.box的padding也会随着更新。
注意,在CSS中给SVG元素设置几何属性只在Blink浏览器中有效!
结合前两种方法
虽然这很酷,但遗憾的是目前在任何浏览器中都看不到效果。但值得庆幸的是我们可以做得更好!
使用渐变来做朦层
Note that CSS masking on HTML elements doesn't work at all in Edge at this point, though it's listed as "In Development" and a flag for it (that doesn't do anything for now) has already shown up in
about:flags.
由于我们需要完全抛弃SVG,所以我们需要使用CSS的渐变为mask做些事情。这里将使用CSS径向渐变来画圆,下面就是CSS绘制的一个半径为--r的圆,并且这个圆位于.box的左上角。
.box {
background: radial-gradient(circle at 0 0, #000 var(--r, 50px), transparent 0);
}
如果您对CSS的渐变不太了解,建议您花点时间阅读这几篇文章:《再说CSS3渐变:线性渐变》、《再说CSS3渐变:径向渐变》、《为什么要使用
repeating-linear-gradient》、《你真的理解CSS的linear-gradient?》。
这个时候你可以看到像上面这样的一个效果:
接下来在mask使用相同的渐变:
.box {
/* same as before */
/* any CSS background we wish */
mask: radial-gradient(circle at 0 0, #000 var(--r, 50px), transparent 0);
}
注意,Webkit浏览器仍然需要给
mask属性添加-webkit-前缀。如果你不知道mask怎么使用,建议你花点时间阅读《如何在CSS中使用遮罩》一文。因为后面很多内容都会涉及到这个属性,这样能帮助更好的理解后续的内容。
给.box每个角落都添加渐变绘制的圆:
$grad-list: radial-gradient(circle at 0 0 , #000 var(--r, 50px), transparent 0),
radial-gradient(circle at 100% 0 , #000 var(--r, 50px), transparent 0),
radial-gradient(circle at 0 100%, #000 var(--r, 50px), transparent 0),
radial-gradient(circle at 100% 100%, #000 var(--r, 50px), transparent 0);
.box {
/* same as before */
/* any CSS background we wish */
mask: $grad-list
}
看到上面的代码是不是感觉要崩溃了,有太多重复的代码要写,其实我们使用一个CSS自定义属性--stop-list可以让我们把事情简化不少:
$grad-list: radial-gradient(circle at 0 0 , var(--stop-list)),
radial-gradient(circle at 100% 0 , var(--stop-list)),
radial-gradient(circle at 0 100%, var(--stop-list)),
radial-gradient(circle at 100% 100%, var(--stop-list));
.box {
/* same as before */
/* any CSS background we wish */
--stop-list: #000 var(--r, 50px), transparent 0;
mask: $grad-list;
}
上面这样做还不是很好,可以借助CSS处理器的循环特性来做,会更好一些:
$grad-list: ();
@for $i from 0 to 4 {
$grad-list: $grad-list,
radial-gradient(circle at ($i%2)*100% floor($i/2)*100%, var(--stop-list));
}
.box {
/* same as before */
/* any CSS background we wish */
--stop-list: #000 var(--r, 50px), transparent 0;
mask: $grad-list;
}
就代码而言,这样写已经很完美了。因为我们不需要多次编写,并且以后在任何地方使用都不需要做任何更改。但到目前为止的结果并不是我们想要的:
从上面的示例中可以看出,我们除了凹角部分之外的东西都剪切掉了,这正好和我们想要的东西相反。要得到我们想要的效果,咱们只需要做一件事情,把渐变反过来。让凹角的圆变成透明,剩余的部分全部是黑色。
--stop-list: transparent var(--r, 50px), #000 0;
需要注意的是,如果我们只使用一个渐变的时候,那么上面的代码就帮我们解决了问题:
但是,当我们把所有的四个圆圈(甚至两个)都堆起来的时候,就会得到一个黑色的矩形,这个矩形的大小相当于我们的mask的大小,这意味着没有任何东西会被掩盖掉。
因此,我们需要把每个渐变的大小限制在盒子的四分之处(width的50%和height的50%),从而得到25%的面积:
这个意思就是,我们需要设置mask-size的值为50% 50%,同时mask-repeat的值为no-repeat以及每个mask-image自身的位置。
$grad-list: ();
@for $i from 0 to 4 {
$x: ($i%2)*100%;
$y: floor($i/2)*100%;
$grad-list: $grad-list
radial-gradient(circle at $x $y, var(--stop-list)) /* mask image */
$x $y; /* mask position */
}
.box {
/* same as before */
/* any CSS background we wish */
--stop-list: transparent var(--r, 50px), #000 0;
mask: $grad-list;
mask-size: 50% 50%;
mask-repeat: no-repeat;
}
但这里有一个大问题,一般情况下,我们的四分之一计算的每个部分会经过四舍五入,那么这四个部分重新组合在一起的时候,width和height都有可能产生间距。如下图所示:

好吧,我们不能用linear-gradient()来做这个线条或者说把mask-size的尺寸增加到51%。比如下面的这个示例,增加了mask-size的尺寸来处理四个渐变区载之间的间距。
但是,难道没有更优雅的方式来处理这个间距?不是的,可以使用mask-composite属性来帮我们处理。当我们返回全部渐变的全尺寸时,可以把mask-composite的值设置为intersect。
$grad-list: ();
@for $i from 0 to 4 {
$grad-list: $grad-list,
radial-gradient(circle at ($i%2)*100% floor($i/2)*100%, var(--stop-list));
}
.box {
/* same as before */
/* any CSS background we wish */
--stop-list: transparent var(--r, 50px), #000 0;
mask: $grad-list;
mask-composite: exclude;
}
这非常酷,因为它是纯CSS的解决方案,没有使用任何SVG代码,但不幸的是,目前得能看到效果的也仅限于Firefox53+。
corner-shape
大约在五年前,@Lea Verou提出了一个想法,甚至还为它创建了一个预览页面。遗憾的是,它不仅没有被任何浏览器实现,而且在此期规范还没有得到很大的提高。对于未来,它仍然是值得期待的,因为它提供了很多灵活性,而且代码非常少。比如说,实现我们前面所说的效果,只需要以下几行代码:
padding: var(--r);
corner-shape: scoop;
border-radius: var(--r);
就是一个非常简单的CSS。是不是值得期待,但最终还是要看浏览器什么时候会对其支持。
CSS Houdini
CSS Houdini慢慢的开始进入大家的世界当中,试问一下,我们使用CSS Houdini是不是可以更方便的实现这个内凹角的效果呢?比如像下面这样的一个效果,它就是使用CSS Houdini实现的:
咱们不仿尝试一下使用Paint Worklet或者CSS Paint API来实现呢?请开动你的大脑,动手撸一撸。希望您能把你的成果在下面的评论中与大家一起分享?如果你感兴趣,也可以在下面的评论中留言,我们后续可以专门花一点时间来看看CSS Houdini可以实现内凹角的效果,甚至是前面所讲的斜外切口的效果。
构建一个内凹角的Vue组件
记得在《使用Vue制作切口盒子组件》一文中,咱们就尝试使用Vue构建了一个斜外切口的Vue组件c-noth:
那么我们来看看怎么使用Vue来构建一个内凹角的组件,具体代码如下:
特别声明,如果您的浏览器没有看到任何效果,请使用Firefox 53+浏览器查阅。具体原因,前面文章已经介绍过来了。
为了照顾其他同学查看最终的效果,我录了一个屏:

这就是最终的效果。由于我自己是Vue的初学者,现在有一个病,看到什么东西都想用Vue来写,而且想封装成一个组件。如果写得不好,或者有更好的方案,欢迎大家指点,并且希望能看到您的分享的成果。如果你和我一样,也是Vue的一个初学者,可以和我一起来学习Vue。整理了一些有关于Vue的学习笔记,希望大家能喜欢,更希望能帮助到初学者,同时也希望不会误人子弟。
总结
文章开头抛出了怎么实现内凹角的一个效果。首先从CSS的box-shadow着手,使用CSS的box-shadow可以轻易的实现内凹角的效果,但这个方案有一定的局限性,比如要多个内凹角时,需要通过增加元素标签来实现,特别是在面对渐变,或者有背景图像和半透明的情景之下,这个方案基本上无法来满足我们的需求。
接着探索了SVG的方案,通过SVG的mask和use之类的一些独有的特性,可能灵活的帮助我们实现想要的效果,而且能做到box-shadow无法做到的事情。特别是通过CSS自定义属性来修改SVG的属性,让事情变得更具灵活性,只不过部分浏览器还不支持CSS来修改SVG的属性,这算是其中的一个坑吧。不过我们还是可以规避掉的。
虽然SVG能实现想要的效果,但对于一位CSS执着者而言,总是希望不借助其他的外力,通过纯CSS来实现这个效果,事实上也是可以的,使用CSS的径向渐变和mask相关的知识,可以实现我们想要的效果。遗憾的是,目前众多浏览器对mask还是有所保留,未能全面支持。比如文中提到的,很多mask相关的特性,仅能在Firefox 53+上看到。包括咱们写的示例,有些仅能在Firefox上看到。
随着CSS Houdini技术越来越成熟,我在试想,是否可以通过CSS Houdini来实现。正如@Lea Verou五年前提出的corner-shape属性。我想是可以的,后面可以尝试动手写写。当然,CSS Houdini虽然还没有得到所有浏览器支持,但这并不防碍我们去尝试着写各种效果。有兴趣的一起动手写写,看看这个想法是否能成真。
最后为了能练习Vue相关的知识,尝试使用Vue封装了一个简单的凹角组件。写得比较拙逼,希望能得到大神的指点。
最后的最后,需要特别感谢@ANA TUDOR写了这么优秀的教程。我在原作者的基础上做过一些调整,如果你觉得这里整理和不好,可以查阅原文。
如需转载,烦请注明出处:https://www.w3cplus.com/css/scooped-corners.html
如需转载,烦请注明出处:https://www.w3cplus.com/css/scooped-corners.html
如果文章中有不对之处,烦请各位大神拍正。如果你觉得这篇文章对你有所帮助,打个赏,让我有更大的动力去创作。(^_^)。看完了?还不过瘾?点击向作者提问!






