CSS糖果按钮的代码示例

190 阅读15分钟

最近,当我在寻找一些编码的想法时,因为我没有艺术感,所以我唯一能做的就是找到别人想出来的漂亮东西,然后用简洁的代码重新制作它们......我看到了这些糖果鬼魂按钮!它们似乎是我可以快速编码的最佳选择。

他们似乎是一个完美的选择,我可以快速地编码一个很酷的小东西。不到15分钟后,这就是我的Chromium成果。

Chrome screenshot. Shows a four row, five column grid of candy ghost buttons with text and an icon following it. These buttons have an elongated pill-like shape, a transparent background and a continuous sweet pastel gradient for the border and the text and icon inside.

纯CSS的糖果鬼魂按钮。

我认为这个技术值得分享,所以在这篇文章中,我们将讨论我是如何做到这一点的,以及我们还有哪些其他选择。

起始点

一个按钮是用......你准备好了么?一个button 元素!这个button 元素有一个data-ico 属性,我们在其中放置一个表情符号。它也有一个停止列表的自定义属性,--slist ,设置在style 属性中。

<button data-ico="👻" style="--slist: #ffda5f, #f9376b">boo!</button>

在写完这篇文章后,我了解到Safari在剪切到text 方面有很多问题,即它对button 元素或带有display: flex(也许还有grid ?)的元素不起作用,更不用说一个元素的子元素的文本。可悲的是,这意味着这里介绍的所有技术在Safari中都会失败。唯一的解决办法是将这里的所有button 样式应用于嵌套在button 内的span 元素上,覆盖其父级的border-box 。而且,如果这能帮助其他像我一样使用Linux而没有实际接触到苹果设备的人(除非你算上四楼某人的iPhone 5--反正你也不想为这样的东西打扰他一个月两次以上--最近买的),我也学会了在将来使用Epiphany。感谢布赖恩的建议!

对于CSS部分,我们在一个::after 伪元素中添加图标,并在button 上使用grid 布局,以便为文本和图标提供良好的对齐方式。在button ,我们还设置了一个border ,一个padding ,一个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) }
}

关于上面的代码,有一件事需要澄清。在突出显示的一行中,我们将background-originbackground-clip 设置为border-boxbackground-origin 既将background-position0 0 点放在它所设置的盒子的左上角,又给我们提供了background-size 所相对的盒子的尺寸。

0 0 0 0 50% 25% 50% padding-box 也就是说,如果background-origin 被设置为padding-boxbackground-position 的点就在padding-box 的左上角。如果background-origin 被设置为border-boxbackground-position 的点就在border-box 的左上角。如果background-origin 被设置为padding-boxbackground-size 的意思是padding-box 的宽度和25% 的高度。border-box border-box 如果background-origin 被设置为border-box ,同样的background-size50% 25% 表示50% 的宽度和25% 的高度。

background-origin 的默认值是padding-box ,这意味着一个默认大小的100% 100% 梯度将覆盖padding-box ,然后在border 下面重复它(如果border 是完全不透明的,我们就看不到它)。然而,在我们的案例中,border 是完全的transparent ,我们希望我们的梯度能延伸到整个border-box 。这意味着我们需要将background-origin 的值改为border-box

Screenshot collage. Chrome on the left, Firefox on the right, showing differences between ghost emojis. The button has a pastel gradient background going along the main diagonal, the text 'Boo!' in black and a ghost emoji, which is going to look different depending on the OS and browser.

简单,但不幸的是,非标准的Chromium解决方案

这涉及到使用三个mask 图层并将它们合成。如果你需要复习一下mask 的合成,你可以看看这个速成课程

请注意,在CSSmask 图层的情况下,只有alpha通道是重要的,因为被遮盖元素的每个像素都会得到相应的mask 像素的alpha,而RGB通道不会以任何方式影响结果,所以它们可能是任何有效的值。下面,你可以看到purpletransparent 梯度叠加的效果与使用完全相同的梯度作为mask 的效果。

Screenshot. Shows two Halloween-themed cat pictures (the cat is protectively climbed on top of a Halloween pumpkin) side by side. The first one has a purple to transparent linear gradient overlay on top. The second one uses the exact same linear gradient as a mask. By default, CSS masks are alpha masks, meaning that every pixel of the masked element gets the alpha of the corresponding mask pixel.

我们要从最下面的两个层开始。第一个是完全不透明的层,完全覆盖整个border-box ,这意味着它的alpha值绝对是10 另一个也是完全不透明的,但是(通过使用mask-clip )限制在padding-box ,这意味着,虽然这个层在整个padding-box ,它的alpha是1,但是在border ,它的alpha是transparent

如果你很难想象这一点,一个好的技巧是把一个元素的布局框想象成嵌套的矩形,就像下面说明的那样。

Illustration showing the layout boxes. The outermost box is the border-box. Inside it, a border-width away from the border limit, we have the padding-box. And finally, inside the padding-box, a padding away from the padding limit, we have the content-box.

布局框(现场演示)。

在我们的例子中,底层是完全不透明的(alpha值为1 ),横跨整个橙色盒子(border-box )。第二层,我们放在第一层的上面,在整个红框(padding-box )中是完全不透明的(alpha值是1 ),在padding 限度和border 限度之间的区域是完全transparent (alpha值是0 )。

关于这些盒子的限制,一个非常酷的事情是,角落的圆角是由border-radius (在padding-box 的情况下,也由border-width )决定。下面的交互式演示说明了这一点,我们可以看到border-box 的角舍入是由border-radius 的值决定的,而padding-box 的角舍入是由border-radius 减去border-width 来计算的(在0 的限制下,差值是一个负值)。

CodePen嵌入回退

现在让我们回到我们的mask 图层,其中一个在整个border-box 上是完全不透明的,而它上面的一个在padding-box 区域是完全不透明的,在border 区域是完全透明的(在padding 限制和border 限制之间)。这两个图层使用exclude 操作进行合成(在非标准的 WebKit 版本中称为xor )。

Illustration. Shows the bottom two background layers in 3D. The first one from the bottom has an alpha of 1 all across the entire border-box. The second one, layered on top of it, has an alpha of 1 across the padding box, within the padding limit; it also has an alpha of 0 in the border area, outside the padding limit, but inside the border limit.

两个基础层(现场演示)。

1 在两个图层的字母是01 的情况下,这个操作的名字很有暗示性,就像在我们的例子中一样--第一个图层的字母在任何地方都是1 ,而第二个图层(我们放在第一个图层的上面)的字母在padding-box0 ,在paddingborder 之间的border 区域。

在这种情况下,非常直观的是布尔逻辑规则的应用--将两个相同的值进行XOR得到0 ,而将两个不同的值进行XOR得到1

在整个padding-box ,第一层和第二层的alpha值都是1 ,所以用这个操作将它们合成,在这个区域得到的层的alpha值是0 。然而,在border 区域(在padding 限制之外,但在border 限制之内),第一层的 alpha 值为1 ,而第二层的 alpha 值为0 ,因此我们在这个区域得到的结果层的 alpha 值为1

下面的交互式演示说明了这一点,你可以在查看两个mask ,在3D中分离的层和使用此操作查看它们的堆叠和合成之间切换。

CodePen嵌入回退

把东西放到代码中,我们有:

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 - 只有三个字符同样,使用圆锥渐变而不是线性渐变也可以节省一个字符,但我很少这样做,因为我们的移动浏览器仍然支持屏蔽,但不支持圆锥渐变。使用线性梯度可以确保我们有全面的支持。好吧,除了IE和Chromium之前的Edge,但是,话又说回来,反正没有多少酷炫的东西能在这些浏览器上工作。

第二,我们在两个图层中都使用了梯度。我们没有在底层使用一个普通的background-color ,因为我们不能为background-color 本身设置一个单独的background-clip 。如果我们把background-image 层剪切到padding-box 上,那么这个background-clip 值也会适用于下面的background-color - 它也会被剪切到padding-box 上,我们就没有办法让它覆盖整个border-box

第三,我们没有明确地为底层设置一个mask-clip ,因为这个属性的默认值正是我们在这种情况下想要的值,border-box

第四,我们可以将标准的mask-composite (由Firefox支持)包含在mask 简称中,但不包括非标准的(由WebKit浏览器支持)。

最后,我们总是将标准版本放在最后,这样它就会覆盖任何可能也被支持的非标准版本。

到目前为止,我们的代码结果(在这一点上仍然是跨浏览器的)看起来如下。我们还在根部添加了一个background-image ,这样就可以明显看出我们在整个padding-box ,有真正的透明度。

Screenshot. The pastel gradient button is just a shadow of its former self. Well, just a border, that's all we can see of it. The entire area inside the padding limit has been masked out and we can now see through to the image background behind the button.

遮住整个padding-box (现场演示)后的结果。

这不是我们想要的结果。虽然我们有一个漂亮的渐变border (顺便说一下,这是我喜欢的获得渐变border 的方法,因为我们有真正的透明的整个padding-box ,而不仅仅是一个封面),但我们现在缺少文本。

因此,下一步是在之前的图层上再加一个mask ,这次是限制在text (同时也使实际的文本完全transparent ,这样我们就可以通过它看到梯度background ),并将这第三个mask 图层与前两个图层XOR的结果(结果可以在上面的截图中看到)。

下面的互动演示允许查看三个mask ,既可以在3D中分离,也可以堆叠和合成。

CodePen嵌入回退

请注意,text 的值 [mask-clip](https://css-tricks.com/almanac/properties/m/mask-clip/)的值是非标准的,所以,很遗憾,这只在Chrome中有效。在Firefox中,我们只是没有在按钮上得到任何遮蔽,而且在使文本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;
  }
}

Screenshot collage. Chrome (left) vs. Firefox (right). In Chrome, we have a real pill-shaped pastel gradient ghost button. It has a transparent background that lets us see through to the image background behind our button and a continuous sweet pastel gradient for the border and the text and icon inside. In Firefox, we have the same pill-shaped, pastel background, black text and normal emoji button we had after setting the base styles. The ghost emoji is going to look different depending on the OS and browser - here it can be seen it has different looks in Chrome and Firefox.

我真的很喜欢这种方法,这是我们目前最简单的方法,我真的希望textmask-clip 的标准值,所有的浏览器都能正确地支持它。

然而,我们还有其他一些实现糖果鬼魂按钮效果的方法,虽然它们比我们刚才讨论的只有Chromium的非标准方法更复杂或更有限,但它们也得到了更好的支持。所以我们来看看这些。

额外的伪元素解决方案

这涉及到设置与之前相同的初始样式,但是,我们没有使用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
}

就像以前一样,我们还需要使实际的文本transparent ,这样我们就可以通过它看到后面的粉色渐变background ,现在这个渐变被剪切到了它的形状。

Screenshot collage. Chrome (left) vs. Firefox (right), highlighting the differences in emoji shapes when they're part of knockout text. This is entirely normal and fine, as emojis look different depending on OS and browser.

好了,我们有了梯度文本,但现在我们缺少梯度border 。所以我们要用一个绝对定位的::before 伪元素来添加它,这个伪元素覆盖了button 的整个border-box 区域,并从它的父级继承了borderborder-radiusbackground (除了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 相同,因为我们在::before 上没有margin )只覆盖其button 父的padding-box ,我们希望它覆盖整个border-box 。另外,正方向是向内的,但我们需要向外走一个border-width ,以便从padding 的极限到border 的极限,因此有减号--我们要向负方向走。

我们还在这个绝对定位的元素上设置了一个负的z-index ,因为我们不希望它在button 文字的上面,妨碍我们选择它。在这一点上,文本选择是我们区分文本和background 的唯一方法,但我们很快就会解决这个问题

Screenshot. Shows how text selection is the only way of still distinguishing the transparent text and gradient background clipped to text area button from its gradient background ::before pseudo that covers it fully.

注意,由于伪元素的内容是不可选择的,选择只包括按钮的实际文本内容,而不包括::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

Screenshot collage. Chrome (left) vs. Firefox (right). Both display a pill-shaped pastel gradient ghost button. The only difference is in the shape of the emoji. This is entirely normal and fine, as emojis look different depending on OS and browser.

我发现在大多数情况下,当我们想要一些跨浏览器的东西时,这是最好的方法,这不包括IE或前Chromium Edge,它们都不支持屏蔽。

border-image 的解决方案

在这一点上,你们中的一些人可能会对着屏幕大叫,说没有必要使用::before 伪元素,因为我们可以使用梯度border-image 来创建这种幽灵按钮--这是一种已经工作了超过四分之三年的战术

然而,使用border-image 来制作药丸状的按钮有一个非常大的问题:这个属性与border-radius 不合拍,在下面的互动演示中可以看到。只要我们在一个带有border-radius 的元素上设置一个border-image ,我们就会失去border 的圆角,即使如此,有趣的是,background 仍然会尊重这个圆角。

CodePen嵌入回退

尽管如此,这可能是一个简单的解决方案,在不需要圆角的情况下,或者所需的圆角最多是 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-radius 小于或等于border-width ,我们就没有对border 的内部舍入。

在这种情况下,我们可以使用inset() 函数作为clip-path 值,因为它也提供了对剪裁矩形的角进行舍入的可能性。如果你需要复习一下这个函数的基本知识,你可以看看下面的插图。

Illustration of how inset(d round r) works. Shows the clipping rectangle inside the element's border-box, its edges all a distance d away from the border limit. The corners of this clipping rectangle all have a rounding r along both axes.

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() 添加的。

Screenshot collage. Chrome (left) vs. Firefox (right). Both display a pastel gradient ghost button with small rounded corners, the rounding radius being the same size as the border-width. The only difference is in the shape of the emoji. This is entirely normal and fine, as emojis look different depending on OS and browser.

使用border-image 方法的小角圆角结果(现场演示)。

虽然这种技术不能实现药丸形状的外观,但它的优点是现在有很大的支持,在某些情况下,它可能是我们所需要的全部。

到目前为止讨论的三种解决方案可以在下面的演示中看到,其中也有一个YouTube链接,如果你喜欢通过观看视频构建的东西而不是阅读它们来学习,你可以看到我从头开始编码。

CodePen嵌入回退

button所有这些方法都在文本之外的padding-box ,创建真正的透明度,因此它们适用于我们可能在background 。然而,我们还有其他一些方法,可能值得一提,尽管它们在这个部门有限制。

覆盖解决方案

就像border-image ,这也是一个相当有限的战术。除非我们在button 后面有一个实体或固定的background ,否则它就不起作用。

它涉及到用不同的background-clip 值对背景进行分层,就像梯度边框的覆盖技术一样。唯一不同的是,在这里我们在我们的button 元素后面模拟background 的层上再添加一个渐变层,并将这个顶层夹在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 ,产生一个没有可见文本的药丸状按钮。

Screenshot collage. Chrome (left) vs. Firefox (right). Chrome displays a pill-shaped pastel gradient ghost button. Firefox sadly only displays a pill-shaped button with no visible text.

所有background-clip 盖的解决方案(现场演示)。

我们仍然可以通过在::before 伪基站和background-clip: text 实际button 上使用渐变border 的覆盖方法来实现跨浏览器,这基本上只是我们讨论的第二个解决方案的一个更有限的版本--我们仍然需要使用一个伪基站,但是,由于我们使用一个覆盖,而不是一个mask ,它只在我们有一个固体或固定backgroundbutton

$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中工作。

Screenshot collage. Chrome (left) vs. Firefox (right). Both display a pill-shaped pastel gradient ghost button that has a solid background behind. The only difference is in the shape of the emoji. This is entirely normal and fine, as emojis look different depending on OS and browser.

在按钮后面有一个实心的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
  }
}

Screenshot collage. Chrome (left) vs. Firefox (right). Both display a pill-shaped pastel gradient ghost button that has a fixed image background behind. The only difference is in the shape of the emoji. This is entirely normal and fine, as emojis look different depending on OS and browser.

在按钮后面有一个固定的background (现场演示)的伪装方案。

总的来说,我不认为这是最好的策略,除非我们都符合background 的限制,而且我们需要在不支持遮盖,但支持将background 剪切到text 的浏览器中重现效果,例如Chromium Edge之前的浏览器。

融合解决方案

button这个方法是另一个有限的方法,因为它不会工作,除非对于每一个可见的渐变像素,其通道的值都比background 下方的相应像素大或小。然而,这并不是最糟糕的限制,因为它可能会使我们的页面有更好的对比。

在这里,我们先把我们想要有渐变的部分(即文字、图标和border )做成whiteblack ,这取决于我们是用浅色渐变的暗主题还是用深色渐变的亮主题。button 的其余部分(文本和图标周围的区域,但在border 内)是之前选择的color 的逆值(如果我们将color 的值设置为black ,则white ,否则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 一样大。

这意味着在我们的button 后面有一个通道值小于或等于f9,375fbackground-color ,或者作为一个实心的background ,或者在我们使用multiply 混合模式混合的background-image 图层下面(这总是产生一个至少与两层中较暗的一层一样暗的结果)。我们在一个伪元素上设置这个background ,因为与实际的bodyhtml 混合在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);
  }
}

black 请注意,使图标完全white ,意味着首先用brightness(0) ,然后用invert(1) ,将这个black

Screenshot collage. Chrome (left) vs. Firefox (right). Both show a pill-shaped black and white (white border, white text, white emoji and black everything in between) button on top of a dark image background. The only difference is in the shape of the emoji. This is entirely normal and fine, as emojis look different depending on OS and browser.

blackwhite 按钮(现场演示)。

然后我们添加一个渐变的::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 内容互动。

Screenshot. Shows a pill-shaped gradient button with no visible text on top of a dark image background.

blackwhite 按钮上添加渐变伪数后的结果(现场演示)。

button现在,下一步是保留我们的black 部分,但用梯度替换white 部分(即文字、图标和border )。我们可以用darken 混合模式来做到这一点,其中的两个图层是带有::after 图标的黑白按钮和它上面的渐变假象。

对于RGB通道中的每一个,这个混合模式采取两个层的值,并使用较暗(较小)的那一个作为结果。因为所有东西都比white ,所以结果层使用该区域的梯度像素值。由于black 比所有东西都要暗,所以结果层是black ,到处都是buttonblack

button {
  /* same styles as before */
  
  &::before {
    /* same styles as before */
    mix-blend-mode: darken;
  }
}

Screenshot collage.  Chrome (left) vs. Firefox (right). Both show a pill-shaped black and pastel gradient (pastel gradient border, text, emoji and black everything in between) button on top of a dark image background. The only difference is in the shape of the emoji. This is entirely normal and fine, as emojis look different depending on OS and browser.

好吧,但我们只有在button 后面的background 是纯粹的black ,才会在这一点上完成。button 否则,如果一个background ,其每个像素都比我们的button 上的梯度的相应像素更暗,我们可以应用第二个混合模式,这次是在实际的lighten (之前,我们在::before 伪装上有darken )。

对于RGB通道中的每一个,这个混合模式采用两个图层的值,并使用较轻(较大)的那个作为结果。因为任何东西都比black 轻,所以结果层在button 后面使用background ,而在button 后面使用black 。而且由于一个要求是button 的每一个渐变像素都比它后面的background 的相应像素要浅,所以结果层使用该区域的渐变像素值。

button {
  /* same styles as before */
  mix-blend-mode: lighten;
}

Screenshot collage. Chrome (left) vs. Firefox (right). Both show a pill-shaped pastel gradient ghost with a 'BOO!' text and a ghost emoji button on top of a dark image background. The only difference is in the shape of the emoji. This is entirely normal and fine, as emojis look different depending on OS and browser.

对于在浅色background 上的深色渐变button ,我们需要切换混合模式。也就是说,在::before 伪装上使用lighten ,在button 本身上使用darken 。首先,我们需要确保button 后面的background 是足够亮的。

假设我们的梯度在#602749#b14623 之间。background 我们的梯度站的通道值是60 (R),27 (G),49 (B) 和b1 (R),46 (G),23 (R), 所以button 后面的通道值需要至少是max(60, b1) = b1 的红色,max(27, 46) = 46 的绿色和max(49, 23) = 49 的蓝色。

这意味着在我们的button 上有一个通道值大于或等于b1,4649background-color ,或者作为一个实心的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 伪元素中的图标通过对其设置filter: brightness(0) ,使其成为black

Screenshot collage. Chrome (left) vs. Firefox (right). Both show a pill-shaped dark gradient ghost with a 'BOO!' text and a ghost emoji button on top of a light image background. The only difference is in the shape of the emoji. This is entirely normal and fine, as emojis look different depending on OS and browser.

我们还可以选择将所有的button 层作为其background 的一部分进行混合,无论是浅色还是深色主题,但是,如前所述,Firefox只是忽略了任何background-clip 声明,其中text 是一个值列表的一部分,而不是单一的值。

好了,就这样吧!我希望你有(或有)一个可怕的万圣节。我的万圣节肯定是被我发现的所有bug......或重新发现的bug,以及它们还没有被修复的现实所吓倒的。