最近,在寻找关于编写什么代码的一些想法时,因为我的艺术感为零,所以我唯一能做的就是找到其他人想出的漂亮东西,并用干净紧凑的代码重新制作它们……我遇到了这些糖果鬼按钮!
它们似乎是我可以快速编码的一个很酷的小东西的完美选择。不到十五分钟后,这是我的 Chromium 结果:
纯 CSS 糖果幽灵按钮。
我认为这项技术值得分享,因此在本文中,我们将介绍我最初是如何做到的以及我们还有哪些其他选择。
起点
一个按钮是用...创建的,你准备好了吗?一个button
元素!这个button
元素有一个data-ico
属性,我们可以在其中放置一个表情符号。它还具有--slist
在style
属性中设置的停止列表自定义属性 。
<button data-ico="👻" style="--slist: #ffda5f, #f9376b">boo!</button>
写完这篇文章后,我了解到 Safari 有很多剪切到 的问题text
,即它对button
元素不起作用,或者对具有display: flex(也许grid
也是?)的元素不起作用,更不用说元素子元素的文本了。遗憾的是,这意味着这里介绍的所有技术在 Safari 中都失败了。唯一的解决方法是将button
此处的所有样式应用到span
嵌套在 中的元素上button
,覆盖其父元素的border-box
. 而且,如果这可以帮助其他像我一样使用 Linux 而无法物理访问 Apple 设备的人(除非你把 iPhone 5 算在四楼的人——你不想为这样的东西烦恼的人超过反正一个月两次——最近买的),我也学会了使用在未来顿悟。感谢布赖恩的建议!
对于 CSS 部分,我们将图标添加到::after
伪元素中,并在 上使用grid
布局button
以使文本和图标对齐。在 上button
,我们还设置了 a border
、 a padding
、 a border-radius
,使用停止列表--slist
, 为对角线渐变并美化了font
。
button {
display: grid;
grid-auto-flow: column;
grid-gap: .5em;
border: solid .25em transparent;
padding: 1em 1.5em;
border-radius: 9em;
background:
linear-gradient(to right bottom, var(--slist))
border-box;
font: 700 1.5em/ 1.25 ubuntu, sans-serif;
text-transform: uppercase;
&::after { content: attr(data-ico) }
}
关于上面的代码,有一点需要澄清。在突出显示的行上,我们将 thebackground-origin
和 the都设置background-clip
为border-box
。background-origin
两者都将0 0
点放在background-position
它设置的框的左上角,并为我们提供了与其尺寸background-size
相对的框。
也就是说,如果background-origin
设置为padding-box
,则0 0
for的点background-position
位于 的左上角padding-box
。如果background-origin
设置为border-box
,则 的0 0
点background-position
位于 的左上角border-box
。如果background-origin
被设置为padding-box
,一个background-size
的50% 25%
装置50%
的的padding-box
宽度和25%
所述的padding-box
高度。如果background-origin
被设置为border-box
,同样background-size
的50% 25%
手段50%
的的border-box
宽度和25%
所述的border-box
高度。
为默认值background-origin
是padding-box
,这意味着默认大小100% 100%
渐变将覆盖padding-box
再重演下面的border
(在这里我们看不到它,如果border
是完全不透明)。然而,在我们的例子中,border
是完全的transparent
,我们希望我们的梯度延伸到整个border-box
. 这意味着我们需要将background-origin
值更改为border-box
。
应用基本样式后的结果(现场演示)。
简单但可悲的是非标准 Chromium 解决方案
这涉及使用mask
三层并将它们合成。如果您需要复习mask
合成,可以查看此速成课程。
请注意,在 CSSmask
图层的情况下,只有 alpha 通道很重要,因为遮罩元素的每个像素都获得相应mask
像素的 alpha ,而 RGB 通道不会以任何方式影响结果,因此它们可能是任何有效的价值。下面,你可以看到的效果purple
,以transparent
渐变叠加与使用完全相同的梯度作为的效果mask
。
渐变叠加与相同的渐变蒙版(现场演示)。
我们将从底部的两层开始。第一个是完全不透明的层,完全覆盖整个border-box
,这意味着它的 alpha1
绝对无处不在。另一个也是完全不透明的,但限制(通过使用mask-clip
)到padding-box
,这意味着,虽然该层在整个 中的 alpha 为 1 padding-box
,但它完全transparent
在该border
区域中,在0
那里有一个 alpha 。
如果你很难想象这一点,一个很好的技巧是将元素的布局框视为嵌套矩形,如下图所示。
布局框(现场演示)。
在我们的例子中,底层1
在整个橙色框( )上是完全不透明的(alpha 值为border-box
)。我们放置在第一层之上的第二层是完全不透明的(alpha 值为1
),整个红色框(the padding-box
)和完全transparent
(alpha 为0
)在padding
限制和border
限制之间的区域中。
关于这些框的限制,一件非常酷的事情是圆角由 决定border-radius
(并且,在 的情况下padding-box
,也由 决定border-width
)。下面的交互式演示对此进行了说明,我们可以在其中看到 的圆角是如何border-box
由border-radius
值给出的,而 的圆角padding-box
计算为border-radius
减去 the border-width
(在0
差值为负值的情况下限制为)。
现在让我们回到我们的mask
图层,一个在整个 中完全不透明border-box
,而它上面的一个在整个padding-box
区域完全不透明并且在该border
区域(在padding
限制和border
限制之间)完全透明。这两个层使用exclude
操作(xor
在非标准 WebKit 版本中调用)合成。
两个基础层(现场演示)。
在两层的 alpha 为0
或的情况下,此操作的名称非常具有启发性1
,就像在我们的例子中一样——第一层1
的 alpha 无处不在,而第二层的 alpha(我们放在第一)的顶部是1
内部的padding-box
并0
在border
所述区域之间padding
限制和border
限制。
在这种情况下,应用布尔逻辑的规则非常直观——对两个相同的值0
进行异或运算得到,而对两个不同的值进行异或运算得到1
。
在整个 中padding-box
,第一层和第二层的 alpha 均为1
,因此使用此操作将它们合成为我们0
在该区域中生成的层的 alpha为 。但是,在该border
区域(在padding
限制之外,但在border
限制内)中,第一层的 alpha 为1
,而第二层的 alpha 为0
,因此我们1
在该区域中得到的结果层的 alpha为 。
下面的交互式演示对此进行了说明,您可以在其中切换查看mask
以 3D 方式分离的两个图层,以及使用此操作查看堆叠和合成的图层。
把东西变成代码,我们有:
button {
/* same base styles */
--full: linear-gradient(red 0 0);
-webkit-mask: var(--full) padding-box, var(--full);
-webkit-mask-composite: xor;
mask: var(--full) padding-box exclude, var(--full);
}
在我们进一步讨论之前,让我们讨论一些关于上述 CSS 的微调细节。
首先,由于完全不透明的层可以是任何东西(alpha 通道是固定的,总是1
和 RGB 通道不重要),我通常制作它们red
——只有三个字符!同样,使用圆锥渐变而不是线性渐变也可以为我们节省一个字符,但我很少这样做,因为我们仍然有支持 masking但不支持圆锥渐变的移动浏览器。使用线性可确保我们获得全面支持。好吧,除了 IE 和 Chromium Edge 之前的版本,但话又说回来,无论如何,没有多少很酷的闪亮的东西在这些东西中起作用。
其次,我们对两个层都使用渐变。我们没有background-color
为底部使用一个普通的,因为我们不能background-clip
为它background-color
本身设置一个单独的。如果我们要将background-image
图层裁剪到padding-box
,那么这个background-clip
值也将应用于background-color
下面——它也会被裁剪到padding-box
,我们无法让它覆盖整个border-box
。
第三,我们没有mask-clip
为底层明确设置一个值,因为这个属性的默认值正是我们在这种情况下想要的值,border-box
。
第四,我们可以mask-composite
在mask
速记中包含标准(Firefox 支持),但不能包含非标准(WebKit 浏览器支持)。
最后,我们始终将标准版本设置为最后,以便它覆盖可能也受支持的任何非标准版本。
到目前为止,我们的代码的结果(此时仍然是跨浏览器的)如下所示。我们还在background-image
根上添加了一个,这样很明显我们在整个padding-box
区域都有真正的透明度。
屏蔽整个
padding-box
(现场演示)后的结果。
这不是我们想要的。虽然我们有一个很好的渐变border
(顺便说一下,这是我获得渐变的首选方法,border
因为我们在整个过程中都有真正的透明度padding-box
,而不仅仅是封面),但我们现在缺少文本。
因此,下一步是使用mask
前一层之上的另一层重新添加文本,这一次仅限于text
(同时也使实际文本完全,transparent
以便我们可以background
通过它看到渐变)和 XOR 这第三mask
层与前两个异或的结果(结果可以在上面的屏幕截图中看到)。
下面的交互式演示允许查看mask
在 3D 中分离以及堆叠和合成的三个图层。
请注意,text
for的值mask-clip
是非标准的,因此,遗憾的是,这仅适用于 Chrome。在 Firefox 中,我们只是不再对按钮进行任何屏蔽,并且制作了 text transparent
,我们甚至不会优雅地降级。
button {
/* same base styles */
-webkit-text-fill-color: transparent;
--full: linear-gradient(red 0 0);
-webkit-mask: var(--full) text, var(--full) padding-box, var(--full);
-webkit-mask-composite: xor;
/* sadly, still same result as before :( */
mask: var(--full) padding-box exclude, var(--full);
}
如果我们不想以button
这种方式使我们无法使用,我们应该将应用mask
和 制作文本的代码transparent
放在一个@supports
块中。
button {
/* same base styles */
@supports (-webkit-mask-clip: text) {
-webkit-text-fill-color: transparent;
--full: linear-gradient(red 0 0);
-webkit-mask: var(--full) text, var(--full) padding-box, var(--full);
-webkit-mask-composite: xor;
}
}
使用仅屏蔽方法的最终结果(现场演示)。
我真的很喜欢这种方法,它是我们目前拥有的最简单的方法,我真的希望它text
是一个标准值,mask-clip
并且所有浏览器都正确支持它。
然而,我们还有其他一些实现糖果幽灵按钮效果的方法,尽管它们比我们刚刚讨论的非标准 Chromium-only 更复杂或更有限,但它们也得到了更好的支持。让我们来看看这些。
额外的伪元素解决方案
这涉及设置与以前相同的初始样式,但是,我们不使用 ,而是将mask
剪切background
到该text
区域。
button {
/* same base styles */
background:
linear-gradient(to right bottom, var(--slist))
border-box;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent
}
就像以前一样,我们还需要制作实际的 text transparent
,这样我们就可以看到它background
后面的柔和渐变,现在被剪裁到了它的形状。
淘汰赛按钮文本(现场演示)。
好的,我们有渐变文本,但现在我们缺少渐变border
。因此,我们将使用绝对定位的::before
伪元素添加它,该伪元素覆盖 的整个border-box
区域button
并继承border
,border-radius
并background
从其父元素(除了background-clip
,它被重置为border-box
)。
$b: .25em;
button {
/* same as before */
position: relative;
border: solid $b transparent;
&::before {
position: absolute;
z-index: -1;
inset: -$b;
border: inherit;
border-radius: inherit;
background: inherit;
background-clip: border-box;
content: '';
}
}
inset: -$b
是以下的简写:
top: -$b;
right: -$b;
bottom: -$b;
left: -$b
请注意,我们在此处使用带减号的border-width
值 ( $b
)。该0
值将使margin-box
假的(等同于border-box
在这种情况下,因为我们没有margin
对::before
)仅覆盖padding-box
其button
母公司,我们希望它覆盖整个border-box
。此外,正方向是向内,但我们需要向外走 aborder-width
才能从padding
极限到border
极限,因此是负号 - 我们向负方向前进。
我们还在z-index
这个绝对定位的元素上设置了一个否定,因为我们不希望它位于button
文本的顶部并阻止我们选择它。在这一点上,文本选择是我们区分文本和 的唯一方法background
,但我们很快就会解决这个问题!
添加渐变伪后的结果(现场演示)。
请注意,由于伪元素内容不可选择,因此选择仅包括按钮的实际文本内容,而不包括::after
伪元素中的表情符号。
下一步是添加一个两层,它们之间mask
有一个exclude
合成操作,以便只留下border
这个伪元素的区域可见。
button {
/* same as before */
&::before {
/* same as before */
--full: linear-gradient(red 0 0);
-webkit-mask: var(--full) padding-box, var(--full);
-webkit-mask-composite: xor;
mask: var(--full) padding-box exclude, var(--full);
}
}
这几乎是我们button
在前一种方法的一个中间阶段中为实际所做的。
使用额外的伪方法(现场演示)的最终结果。
我发现在大多数情况下,这是最好的方法,当我们想要跨浏览器的东西并且不包括 IE 或 Chromium Edge 之前,它们都不支持屏蔽。
该border-image
解决方案
在这一点上,你们中的一些人可能会在屏幕上尖叫,::before
当我们可以使用渐变border-image
来创建这种幽灵按钮时,没有必要使用伪元素——这是一种已经使用了四分之三以上的策略十年!
但是,border-image
用于药丸形状的按钮有一个很大的问题:此属性与 不兼容border-radius
,如下面的交互式演示中所示。一旦我们border-image
在一个元素上设置了 a border-radius
,我们就会失去 的角舍入border
,即使通过,有趣的是,background
仍然会尊重这个舍入。
尽管如此,在不需要圆角或所需圆角最多为border-width
.
在没有圆角的情况下,除了删除现在毫无意义的border-radius
,我们不需要太多改变初始样式:
button {
/* same base styles */
--img: linear-gradient(to right bottom, var(--slist));
border: solid .25em;
border-image: var(--img) 1;
background: var(--img) border-box;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
结果可以在下面看到,跨浏览器(即使在 Chromium Edge 之前也应该支持)。
使用
border-image
方法(现场演示)的无圆角结果。
所需圆角小于 的技巧border-width
取决于border-radius
工作方式。当我们设置这个属性时,我们设置的半径代表border-box
. 如果该差为正,则padding-box
(即 的内部圆角border
)的角的圆角为border-radius
负数,border-width
否则为0
(无圆角)。这意味着border
如果border-radius
小于或等于 ,我们没有内部舍入border-width
。
在这种情况下,我们可以将该inset()
函数用作clip-path
值,因为它还提供了圆角剪切矩形角的可能性。如果您需要复习此功能的基础知识,可以查看下图:
如何
inset()
函数工作。
inset()
剪掉由元素边缘的距离定义的裁剪矩形之外的所有内容border-box
,指定方式与我们指定的相同margin
,border
或者padding
(使用一个、两个、三个或四个值)以及此矩形的圆角,指定相同我们会指定方式border-radius
(任何有效的border-radius价值也是有效的在这里)。
在我们的例子中,到 边缘的距离border-box
是全部0
(我们不想从 的任何边缘切掉任何东西button
),但是我们有一个舍入,最多必须与 一样大,border-width
这样就没有任何内部border
舍入都是有道理的。
$b: .25em;
button {
/* same as before */
border: solid $b transparent;
clip-path: inset(0 round $b)
}
请注意,clip-path
也将删除我们可能在button
元素上添加的任何外部阴影,无论它们是通过box-shadow
还是filter: drop-shadow()
.
使用
border-image
方法(现场演示)的小角圆角结果。
虽然这种技术不能达到药丸形状的外观,但它确实具有当今强大的支持优势,并且在某些情况下可能是我们所需要的。
到目前为止讨论的三个解决方案可以在下面的演示中看到编译,该演示还附带一个 YouTube 链接,如果您更喜欢通过观看在视频上构建的东西而不是阅读它们来学习,您可以看到我从头开始编写每个解决方案.
所有这些方法都padding-box
在文本外部创造了真正的透明度,因此它们适用于background
我们在button
. 但是,我们还有一些其他方法可能值得一提,尽管它们在该部门受到限制。
封面解决方案
就像border-image
方法一样,这是一种非常有限的策略。除非我们background
在button
.
它涉及使用不同background-clip
值对背景进行分层,就像渐变边框的覆盖技术一样。唯一的区别是,在这里我们在模拟元素background
后面的图层之上再添加一个渐变图层,button
并将此顶层剪辑为text
。
$c: #393939;
html { background: $c; }
button {
/* same as before */
--grad: linear-gradient(to right bottom, var(--slist));
border: solid .25em transparent;
border-radius: 9em;
background: var(--grad) border-box,
linear-gradient($c 0 0) /* emulate bg behind button */,
var(--grad) border-box;
-webkit-background-clip: text, padding-box, border-box;
-webkit-text-fill-color: transparent;
}
遗憾的是,这种方法在 Firefox 中由于一个旧错误而失败——只是没有应用任何内容background-clip
,同时使文本transparent
产生一个没有可见文本的药丸状按钮。
全
background-clip
覆盖解决方案(现场演示)。
我们仍然可以通过border
在::before
伪和background-clip: text
实际上使用渐变的覆盖方法使其跨浏览器button
,这基本上只是我们讨论的第二个解决方案的更有限版本 - 我们仍然需要使用伪,但是,因为我们使用盖子,而不是mask
,它只有background
在button
.
$b: .25em;
$c: #393939;
html { background: $c; }
button {
/* same base styles */
--grad: linear-gradient(to right bottom, var(--slist));
border: solid $b transparent;
background: var(--grad) border-box;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
&::before {
position: absolute;
z-index: -1;
inset: -$b;
border: inherit;
border-radius: inherit;
background: linear-gradient($c 0 0) padding-box,
var(--grad) border-box;
content: '';
}
}
从好的方面来说,这个更受限制的版本也应该适用于 Chromium Edge 之前的版本。
background
按钮后面的实体的伪封面解决方案(现场演示)。
下面,还有固定background
版本。
$f: url(balls.jpg) 50%/ cover fixed;
html { background: $f; }
button {
/* same as before */
&::before {
/* same as before */
background: $f padding-box,
var(--grad) border-box
}
}
固定
background
按钮后面的伪封面解决方案(现场演示)。
总的来说,我不认为这是最好的策略,除非我们都符合background
限制并且我们需要在不支持遮罩但支持将 剪切background
到 的浏览器中重现效果text
,例如前 Chromium Edge。
混合溶液
这种方法是另一种有限的一个,因为它不会工作,除非每个那就是每一个可见的像素梯度,其通道具有要么全部被大或比所有比的对应的像素较小的值background
下面的button
。然而,这并不是最糟糕的限制,因为它可能会导致我们的页面具有更好的对比度。
在这里,我们首先制作我们想要渐变的部分(即文本、图标和border
)white
或black
,这取决于我们分别拥有带有浅渐变的深色主题还是带有深色渐变的浅色主题。的其余部分button
(文本和图标周围的区域,但在 内部border
)与之前选择的相反color
(white
如果我们将color
值设置为black
和black
否则)。
在我们的例子中,我们有一个漂亮的渐变光button
在一个黑暗的background
,所以我们先从white
文本,图标和边界,并black
为background
。我们的两个梯度停止点的十六进制通道值是ff
( R
), da
( G
), 5f
( B
) 和f9
( R
), 37
( G
), 6b
( B
),所以我们background
对通道值至多与min(ff, f9) = f9
红色一样大的任何像素都是安全的,min(da, 37) = 37
因为绿色和min(5f, 6b) = 5f
蓝色。
这意味着background-color
在我们的后面有一个button
小于或等于f9
,37
和 的通道值,或者单独5f
作为一个实体background
,或者在background-image
我们使用multiply
混合模式混合的层下面(这总是产生至少与两层中较深的一层)。我们将它设置background
在一个伪元素上,因为与实际混合body
或html
在 Chrome 中不起作用。
$b: .25em;
body::before {
position: fixed;
inset: 0;
background: url(fog.jpg) 50%/ cover #f9375f;
background-blend-mode: multiply;
content: '';
}
button {
/* same base styles */
position: relative; /* so it shows on top of body::before */
border: solid $b;
background: #000;
color: #fff;
&::after {
filter: brightness(0) invert(1);
content: attr(data-ico);
}
}
需要注意的是使图标完全white
指使其先black
用brightness(0)
,然后反转这个black
用invert(1)
。
在
black
和white
按钮(现场演示)。
然后我们添加一个渐变::before
伪元素,就像我们为第一个跨浏览器方法所做的那样。
button {
/* same styles as before */
position: relative;
&::before {
position: absolute;
z-index: 2;
inset: -$b;
border-radius: inherit;
background: linear-gradient(to right bottom, var(--slist);
pointer-events: none;
content: '';
}
}
唯一的区别是,这里不是给它一个负数z-index
,而是给它一个正数z-index
。这样它不仅在实际的button
,而且在::after
伪上,我们设置pointer-events
为none
以允许鼠标与button
下面的实际内容进行交互。
在
black
和white
按钮顶部添加渐变伪后的结果(现场演示)。
现在下一步是保留black
我们的部分button
,但用渐变替换white
部分(即文本、图标和border
)。我们可以使用darken
混合模式来做到这一点,其中两层是带有::after
图标的黑白按钮和其上的渐变伪。
对于每个 RGB 通道,此混合模式采用两个图层的值,并使用较暗(较小)的一个作为结果。由于一切都比 暗white
,因此生成的图层使用该区域中的渐变像素值。由于black
是暗于一切,所得层是black
无处不在button
的black
。
button {
/* same styles as before */
&::before {
/* same styles as before */
mix-blend-mode: darken;
}
}
“几乎在那里”的结果(现场演示)。
好的,但如果background
后面button
是纯black
. 否则,在 abackground
的每个像素都比我们的 上的渐变的相应像素更暗的情况下button
,我们可以应用第二个混合模式,这次是lighten
在实际上button
(以前,我们darken
在::before
伪上)。
对于每个 RGB 通道,此混合模式采用两个图层的值,并使用较亮(较大)的图层作为结果。因为任何事情比更轻black
,所得层使用background
后面button
到处button
是black
。并且由于要求是 的每个渐变像素button
都比其background
后面的相应像素更亮,因此生成的图层使用该区域的渐变像素值。
button {
/* same styles as before */
mix-blend-mode: lighten;
}
深色背景上的浅色幽灵按钮(现场演示)。
对于button
light 上的暗渐变background
,我们需要切换混合模式。也就是说,使用lighten
的::before
假,并darken
在button
自身。首先,我们需要确保background
后面button
足够轻。
假设我们的梯度介于#602749
和之间#b14623
。我们梯度站的通道值是60
( R
), 27
( G
), 49
( B
) 和b1
( R
), 46
( G
), 23
( R
),所以background
后面的button
通道值需要至少max(60, b1) = b1
为红色、max(27, 46) = 46
绿色和max(49, 23) = 49
蓝色。
这意味着background-color
在我们button
的通道值上有一个大于或等于b1
, 46
and 49
,无论是作为一个实体background
,还是在一个background-image
层下面,使用screen
混合模式(它总是产生至少与两层)。
我们还需要制作button
边框、文本和图标black
,同时将其设置background
为white
:
$b: .25em;
section {
background: url(fog.jpg) 50%/ cover #b14649;
background-blend-mode: screen;
}
button {
/* same as before */
border: solid $b;
background: #fff;
color: #000;
mix-blend-mode: darken;
&::before {
/* same as before */
mix-blend-mode: lighten
}
&::after {
filter: brightness(0);
content: attr(data-ico);
}
}
::after
伪元素中的图标是black
通过filter: brightness(0)
在其上设置来制作的。
浅色背景上的黑色幽灵按钮(现场演示)。
我们还可以选择将所有button
图层混合为其 的一部分background
,无论是浅色还是深色主题,但是,如前所述,Firefox 只会忽略任何background-clip
声明,其中text
是值列表的一部分,而不是单个值。
嗯,就是这样!我希望你有(或有)一个可怕的万圣节。我发现的所有错误绝对让我感到可怕……或重新发现,以及同时它们尚未修复的现实。