在CSS中重现《赛博朋克2077》的按钮突变效果

249 阅读9分钟

如果你对视频游戏有丝毫兴趣,你无疑知道赛博朋克2077。它是2020年最令人期待的游戏之一。它所描绘的世界具有某种风格。该游戏的网站在描绘这种美学方面做得很出色。它的设计在传达外观和感觉方面做得很好。正如你可以想象的那样,这意味着它有一些看起来相当光滑的用户界面组件。

有人首先联系我,问我如何创建网站上使用的图像效果。如果你在图片库中悬停图片,它们会有这种整洁的 "噪音 "效果。

我接受了这个挑战。我挖空心思看了一下网站的来源。经过一番调查,我发现它是用着色器和WebGL实现的。我对编写着色器和WebGL完全陌生。这确实刺激了我去尝试一下。但是,现在,我已经把学习WebGL和着色器代码放在后面了。

当我在直播中环顾网站时,吸引我们眼球的是那些整齐的突变效果按钮。我对用CSS创造突变的效果并不陌生。我们决定让我尝试重新创建它们。

这里是你如何做到的!

效果按钮

让我们从一些标记开始:

<button class="cybr-btn">
  Beginning_
</button>

我们首先需要解决的问题是尺寸、颜色和字体。把这些弄好的最好方法是什么?潜入源代码,看看它是怎么做的。从第一次检查中,我们看到正在使用一种自定义字体。(你可以在下面的代码块中看到它的直接链接)。

让我们创建一个自定义的@font-face规则:

@font-face {
  font-family: Cyber;
  src: url("https://assets.codepen.io/605876/Blender-Pro-Bold.otf");
  font-display: swap;
}

一旦我们有了这个规则,我们就可以把基本的样式设计到位了。在颜色和字体大小等方面使用CSS变量,为我们以后提供了机会。这也是使用HSL颜色空间的原因。我们将在后面说明原因:

--primary: hsl(var(--primary-hue), 85%, calc(var(--primary-lightness, 50) * 1%));
--shadow-primary: hsl(var(--shadow-primary-hue), 90%, 50%);
--primary-hue: 0;
--primary-lightness: 50;
--color: hsl(0, 0%, 100%);
--font-size: 26px;
--shadow-primary-hue: 180;

把这些放在一起,我们就有了这个起点。注意到我们是如何使用内嵌盒式阴影而不是为那条蓝线设置边框的吗?这是因为边框会使我们的文字偏离中心。嵌入盒状阴影不会影响文字的对齐。

剪掉的角

这个按钮的一个明显特征是它的剪角。我的第一个想法是使用剪切路径。但是,令我惊讶的是,网站上的按钮的形状是用背景图片实现的。

我们可以用clip-path 属性来剪掉这个角:

clip-path: polygon(-10% -10%, 110% -10%, 110% 110%, 10% 110%, -10% 40%);

请注意,我们并没有对按钮的边缘进行剪裁。我们给了按钮10%的呼吸空间。这是因为我们需要考虑到 "R25 "标签,以及突变效果会流向按钮之外的事实。这是用clip-path 的一个巧妙技巧。我们可以把它作为一个受控的overflow: hidden 。我们在说,"是的,你可以溢出一点。但只有这么多"。

把它加到我们的按钮上,就可以得到我们想要的剪裁效果。

创建R25标签

接下来,让我们来创建 "R25 "标签。我们可以在这里接触到一个伪元素并使用content 属性。事实上,网站上就是这样做的。但是这种方法需要注意的是--屏幕阅读器可能会读出它。实际的按钮文本也是如此。网站上的每个按钮都有以下划线接续的文字。我们想让屏幕阅读器读出这些文字吗?如果是,那么我们可以保持原样。让我们假设它们是用于装饰的。我们可以更新我们的标记并使用aria-hidden ,这样屏幕阅读器就只能读出按钮的文字:

<button class="cybr-btn">
  Clipped<span aria-hidden>_</span>
  <span aria-hidden class="cybr-btn__tag">R25</span>
</button>

为了使标签具有风格,我们可以给它absolute 定位。这需要我们在按钮上设置relative 定位。和按钮本身一样,该标签使用了一个inset box-shadow:

.cybr-btn {
  --label-size: 9px;
  --shadow-secondary-hue: 60;
  --shadow-secondary: hsl(var(--shadow-secondary-hue), 90%, 60%);
  position: relative;
}
.cybr-btn__tag {
  position: absolute;
  padding: 1px 4px;
  letter-spacing: 1px;
  line-height: 1;
  bottom: -5%;
  right: 5%;
  background: var(--shadow-secondary);
  color: hsl(0, 0%, 0%);
  font-size: var(--label-size);
  box-shadow: 2px 0 inset var(--shadow-primary);
}

我们在这里又引入了一些CSS变量。尽管它们被标签使用,但我们把它们放在了按钮选择器下面。这样做是有原因的。我们以后可能会决定利用范围变量的力量。如果我们这样做,我们只需要在按钮选择器上设置这些变量。如果我们把变量留在标签规则下,那么在按钮上设置的变量就不会有超过下层范围的力量。我们为标签设置了一个background-color 。但很快就会发现,网站上并没有这样做。

随着我们的标签到位,按钮现在已经成形了。

添加突变效果

现在是制作突变效果的时候了。根据经验,我的假设是这个按钮是被复制的。复制的按钮会有某种形式的剪切动画。我们在这里的第一个任务是创建突变体。还记得我们之前发现的背景图片的使用吗?很快我们就明白了为什么要使用这个背景:为标签提供一个切口。这意味着按钮后面的background-color ,而标签也是如此。角部的切口也是用图像创建的。

注意到蓝色边框是如何顺着墙角,绕过 "R25 "的?像我们这样使用剪辑路径,就可以把这个角切掉,而且不会勾勒出 "R25 "的轮廓。该网站的实现使用了一个drop-shadow

使用一个背景图片可以让我们重新创造这种效果。不过,如果我们想让我们的按钮灵活和可重复使用,它也有一些妥协。

例如,如果我们想改变按钮的颜色怎么办?我们必须为每个按钮的颜色变化创建许多图片吗?如果我们改变按钮的长宽比呢?图像就不适合了。

突变的动画是很快的。它足够快,以至于不太可能注意到剪切的角落。为了得到一套更灵活和可重复使用的样式,这种折衷是值得的。

让我们继续使用这个解决方案。我们可以为突发事件添加一个新元素。这需要和我们的按钮一样的文本,也需要用aria-hidden 来隐藏屏幕阅读器:

<button class="cybr-btn">
  Glitch<span aria-hidden>_</span>
  <span aria-hidden class="cybr-btn__glitch">Glitch_</span>
  <span aria-hidden class="cybr-btn__tag">R25</span>
</button>

我们需要在这里复制文本,我们有选择。该网站使用一个伪元素来复制文本。但如果我们这样做,就意味着要同时对两个元素进行动画处理以达到效果。通过将文本移到glitch元素中,我们只需要给一个元素做动画:

.cybr-btn__glitch {
  position: absolute;
  height: 100%;
  width: 100%;
  top: 0;
  left: 0;
  box-shadow: 0 0 0 4px var(--shadow-primary);
  text-shadow: 2px 2px var(--shadow-primary), -2px -2px var(--shadow-secondary);
}

应用一些样式,如text-shadow 和一个box-shadow ,我们就可以达到这个效果。

但我们对这个角的剪裁并不满意。另外,我们如何使用clip-path ,给人以呼吸的空间,感觉很脆。我们可以用一个小技巧把它找回来。如果我们使用伪元素来给按钮上色,我们就不必对整个按钮进行剪裁了我们可以使用绝对定位,然后只剪辑伪元素。我们也不需要提供呼吸空间。这里的好处是,我们已经有了变量中的按钮颜色:

.cybr-btn {
  --clip: polygon(0 0, 100% 0, 100% 100%, 8% 100%, 0 70%);
}
.cybr-btn:before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--primary);
  clip-path: var(--clip);
  z-index: -1;
}

我们可以从按钮上删除clip-path ,然后把这个夹子放到一个变量中,我们就可以重新使用。我们需要将z-index: -1 到伪元素上,这样文字仍然可以显示出来:

.cybr-btn {
  --border: 4px;
}

.cybr-btn__glitch {
  position: absolute;
  top: calc(var(--border) * -1);
  left: calc(var(--border) * -1);
  right: calc(var(--border) * -1);
  bottom: calc(var(--border) * -1);
  background: var(--shadow-primary);
  text-shadow: 2px 2px var(--shadow-primary), -2px -2px var(--shadow-secondary);
  clip-path: var(--clip);
}

.cybr-btn__glitch:before {
  content: '';
  position: absolute;
  top: calc(var(--border) * 1);
  right: calc(var(--border) * 1);
  bottom: calc(var(--border) * 1);
  left: calc(var(--border) * 1);
  clip-path: var(--clip);
  background: var(--primary);
  z-index: -1;
}

然后我们就可以为突变元素的伪元素重新使用这个片段。让突发事件元素正确的诀窍是将其绝对定位,就像它是边界一样。然后将伪元素覆盖在它上面。在这两个元素上应用相同的剪辑,就可以得到整齐的蓝色边框,紧跟角的位置。

这多好啊?我们甚至可以调整剪辑路径来获得 "R25 "周围的切口。调整clip-path ,并像这样删除标签样式。

.cybr-btn {
  --clip: polygon(0 0, 100% 0, 100% 100%, 95% 100%, 95% 90%, 85% 90%, 85% 100%, 8% 100%, 0 70%);
}

.cybr-btn__tag {
  position: absolute;
  padding: 1px 4px;
  letter-spacing: 1px;
  line-height: 1;
  bottom: -5%;
  right: 5%;
  color: hsl(0, 0%, 0%);
  font-size: var(--label-size);
}

而这时我们就有机会做一些其他很酷的事情了。当我调查了这个按钮并发现了背景图片后,我把它拉下来。而我发现的是,通过堆叠两张图片并翻译底部的图片,就可以实现边框。现在我们使用clip-path ,我们也可以这样做。

如果我们用:before 伪元素来表示我们按钮的蓝色,用:after 来表示红色,然后用边框的大小来翻译:before 伪元素,它就会给我们提供边框。它给了我们边框,而没有应用border:

.cybr-btn:after,
.cybr-btn:before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  clip-path: var(--clip);
  z-index: -1;
}

.cybr-btn:before {
  background: var(--shadow-primary);
  transform: translate(var(--border), 0);
}

.cybr-btn:after {
  background: var(--primary);
}

现在我们有了标签和按钮的阴影。而标签将使用它后面的背景色。试着把background-color 改为body ,你就会明白了!