如果你对电子游戏有丝毫兴趣,那么你无疑知道赛博朋克 2077。它是 2020 年最受期待的游戏之一。它描绘的世界具有一定的风格。该游戏的网站在描绘这种美学方面做得非常出色。它的设计在传达外观和感觉方面做得很好。可以想象,这意味着它有一些相当漂亮的 UI 组件。
有人首先联系我询问我将如何创建在网站上使用的图像效果。如果你将图像悬停在图库中,它们会产生这种整洁的“噪音”效果,CSS如何创建赛博朋克 2077按钮故障效果?
我接受了挑战。我深入研究了该网站的来源。经过一番挖掘,我发现它是用着色器和 WebGL 实现的。我对编写着色器和 WebGL 完全陌生。这确实激励我尝试一下。但是,就目前而言,我已将学习 WebGL 和着色器代码置于次要位置。
当我在直播中环顾网站时,吸引我们眼球的是整洁的故障效果按钮。我对使用 CSS 创建故障效果并不陌生。我们决定尝试重新创建它们。
这就是你可以做到的!
CSS赛博朋克 2077按钮故障效果教程:效果按钮
让我们从一些标记开始:
<button class="cybr-btn">
Beginning_
</button>
CSS赛博朋克 2077按钮故障效果示例:我们首先需要排序的是大小、颜色和字体。获得这些权利的最佳方法是什么?深入研究源代码,看看它是如何完成的。从第一次检查中,我们看到正在使用自定义字体。(你可以在下面的代码块中看到它的直接链接。)
让我们创建一个自定义的 @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 标签
CSS如何创建赛博朋克 2077按钮故障效果?接下来,让我们创建“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
为标签设置了一个。但很快就很明显,这并没有在网站上完成。
随着我们的标签就位,按钮现在正在形成。
添加故障效果
CSS赛博朋克 2077按钮故障效果教程:现在是故障效应的时候了。根据经验,我在这里的假设是按钮被复制了。复制的按钮将应用某种形式的剪辑动画。我们的第一个任务是创建故障主体。还记得我们之前发现了背景图像的使用吗?很快就明白为什么要使用它:为标签提供一个切口。这意味着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>
CSS赛博朋克 2077按钮故障效果示例:我们需要复制这里的文本,我们有选择。该站点使用伪元素来复制文本。但如果我们这样做,就意味着同时为两个元素设置动画效果。通过将文本移动到故障元素中,我们只需要为一个元素设置动画:
.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
,你会看到!
按钮动画
CSS如何创建赛博朋克 2077按钮故障效果?差不多好了!等一下。我们有小故障。我们有我们需要的一切。剩下的就是为 上的按钮设置动画:hover
。
这种故障效应是如何发生的?诀窍是只显示故障元素:hover
,默认情况下应用动画。我的假设是在一组关键帧中使用transform
和clip-path
。我是对的!我是怎么发现的?我检查了按钮并使用 Chrome 的“强制状态”将按钮设置为:hover
状态。
然后,检查样式并找到动画。单击文件名,这将带你到源。
CSS赛博朋克 2077按钮故障效果示例:这让我可以看到正在使用的关键帧:
@keyframes glitch-anim-1 {
0% {
opacity: 1;
-webkit-transform: translateZ(0);
transform: translateZ(0);
-webkit-clip-path: polygon(0 2%,100% 2%,100% 5%,0 5%);
clip-path: polygon(0 2%,100% 2%,100% 5%,0 5%)
}
2% {
-webkit-clip-path: polygon(0 78%,100% 78%,100% 100%,0 100%);
clip-path: polygon(0 78%,100% 78%,100% 100%,0 100%);
-webkit-transform: translate(-5px);
transform: translate(-5px)
}
6% {
-webkit-clip-path: polygon(0 78%,100% 78%,100% 100%,0 100%);
clip-path: polygon(0 78%,100% 78%,100% 100%,0 100%);
-webkit-transform: translate(5px);
transform: translate(5px)
}
8% {
-webkit-clip-path: polygon(0 78%,100% 78%,100% 100%,0 100%);
clip-path: polygon(0 78%,100% 78%,100% 100%,0 100%);
-webkit-transform: translate(-5px);
transform: translate(-5px)
}
9% {
-webkit-clip-path: polygon(0 78%,100% 78%,100% 100%,0 100%);
clip-path: polygon(0 78%,100% 78%,100% 100%,0 100%);
-webkit-transform: translate(0);
transform: translate(0)
}
10% {
-webkit-clip-path: polygon(0 54%,100% 54%,100% 44%,0 44%);
clip-path: polygon(0 54%,100% 54%,100% 44%,0 44%);
-webkit-transform: translate3d(5px,0,0);
transform: translate3d(5px,0,0)
}
13% {
-webkit-clip-path: polygon(0 54%,100% 54%,100% 44%,0 44%);
clip-path: polygon(0 54%,100% 54%,100% 44%,0 44%);
-webkit-transform: translateZ(0);
transform: translateZ(0)
}
13.1% {
-webkit-clip-path: polygon(0 0,0 0,0 0,0 0);
clip-path: polygon(0 0,0 0,0 0,0 0);
-webkit-transform: translate3d(5px,0,0);
transform: translate3d(5px,0,0)
}
15% {
-webkit-clip-path: polygon(0 60%,100% 60%,100% 40%,0 40%);
clip-path: polygon(0 60%,100% 60%,100% 40%,0 40%);
-webkit-transform: translate3d(5px,0,0);
transform: translate3d(5px,0,0)
}
20% {
-webkit-clip-path: polygon(0 60%,100% 60%,100% 40%,0 40%);
clip-path: polygon(0 60%,100% 60%,100% 40%,0 40%);
-webkit-transform: translate3d(-5px,0,0);
transform: translate3d(-5px,0,0)
}
20.1% {
-webkit-clip-path: polygon(0 0,0 0,0 0,0 0);
clip-path: polygon(0 0,0 0,0 0,0 0);
-webkit-transform: translate3d(5px,0,0);
transform: translate3d(5px,0,0)
}
25% {
-webkit-clip-path: polygon(0 85%,100% 85%,100% 40%,0 40%);
clip-path: polygon(0 85%,100% 85%,100% 40%,0 40%);
-webkit-transform: translate3d(5px,0,0);
transform: translate3d(5px,0,0)
}
30% {
-webkit-clip-path: polygon(0 85%,100% 85%,100% 40%,0 40%);
clip-path: polygon(0 85%,100% 85%,100% 40%,0 40%);
-webkit-transform: translate3d(-5px,0,0);
transform: translate3d(-5px,0,0)
}
30.1% {
-webkit-clip-path: polygon(0 0,0 0,0 0,0 0);
clip-path: polygon(0 0,0 0,0 0,0 0)
}
35% {
-webkit-clip-path: polygon(0 63%,100% 63%,100% 80%,0 80%);
clip-path: polygon(0 63%,100% 63%,100% 80%,0 80%);
-webkit-transform: translate(-5px);
transform: translate(-5px)
}
40% {
-webkit-clip-path: polygon(0 63%,100% 63%,100% 80%,0 80%);
clip-path: polygon(0 63%,100% 63%,100% 80%,0 80%);
-webkit-transform: translate(5px);
transform: translate(5px)
}
45% {
-webkit-clip-path: polygon(0 63%,100% 63%,100% 80%,0 80%);
clip-path: polygon(0 63%,100% 63%,100% 80%,0 80%);
-webkit-transform: translate(-5px);
transform: translate(-5px)
}
50% {
-webkit-clip-path: polygon(0 63%,100% 63%,100% 80%,0 80%);
clip-path: polygon(0 63%,100% 63%,100% 80%,0 80%);
-webkit-transform: translate(0);
transform: translate(0)
}
55% {
-webkit-clip-path: polygon(0 10%,100% 10%,100% 0,0 0);
clip-path: polygon(0 10%,100% 10%,100% 0,0 0);
-webkit-transform: translate3d(5px,0,0);
transform: translate3d(5px,0,0)
}
60% {
-webkit-clip-path: polygon(0 10%,100% 10%,100% 0,0 0);
clip-path: polygon(0 10%,100% 10%,100% 0,0 0);
-webkit-transform: translateZ(0);
transform: translateZ(0);
opacity: 1
}
60.1% {
-webkit-clip-path: polygon(0 0,0 0,0 0,0 0);
clip-path: polygon(0 0,0 0,0 0,0 0);
opacity: 1
}
to {
-webkit-clip-path: polygon(0 0,0 0,0 0,0 0);
clip-path: polygon(0 0,0 0,0 0,0 0);
opacity: 1
}
}
对于我们的动画,我们可以遵循相同的结构。但在我们的示例中,我们可以应用不同版本的剪辑路径:
.cybr-btn {
--shimmy-distance: 5;
--clip-one: polygon(0 2%, 100% 2%, 100% 95%, 95% 95%, 95% 90%, 85% 90%, 85% 95%, 8% 95%, 0 70%);
--clip-two: polygon(0 78%, 100% 78%, 100% 100%, 95% 100%, 95% 90%, 85% 90%, 85% 100%, 8% 100%, 0 78%);
--clip-three: polygon(0 44%, 100% 44%, 100% 54%, 95% 54%, 95% 54%, 85% 54%, 85% 54%, 8% 54%, 0 54%);
--clip-four: polygon(0 0, 100% 0, 100% 0, 95% 0, 95% 0, 85% 0, 85% 0, 8% 0, 0 0);
--clip-five: polygon(0 0, 100% 0, 100% 0, 95% 0, 95% 0, 85% 0, 85% 0, 8% 0, 0 0);
--clip-six: polygon(0 40%, 100% 40%, 100% 85%, 95% 85%, 95% 85%, 85% 85%, 85% 85%, 8% 85%, 0 70%);
--clip-seven: polygon(0 63%, 100% 63%, 100% 80%, 95% 80%, 95% 80%, 85% 80%, 85% 80%, 8% 80%, 0 70%);
}
@keyframes glitch {
0% {
clip-path: var(--clip-one);
}
2%, 8% {
clip-path: var(--clip-two);
transform: translate(calc(var(--shimmy-distance) * -1%), 0);
}
6% {
clip-path: var(--clip-two);
transform: translate(calc(var(--shimmy-distance) * 1%), 0);
}
9% {
clip-path: var(--clip-two);
transform: translate(0, 0);
}
10% {
clip-path: var(--clip-three);
transform: translate(calc(var(--shimmy-distance) * 1%), 0);
}
13% {
clip-path: var(--clip-three);
transform: translate(0, 0);
}
14%, 21% {
clip-path: var(--clip-four);
transform: translate(calc(var(--shimmy-distance) * 1%), 0);
}
25% {
clip-path: var(--clip-five);
transform: translate(calc(var(--shimmy-distance) * 1%), 0);
}
30% {
clip-path: var(--clip-five);
transform: translate(calc(var(--shimmy-distance) * -1%), 0);
}
35%, 45% {
clip-path: var(--clip-six);
transform: translate(calc(var(--shimmy-distance) * -1%));
}
40% {
clip-path: var(--clip-six);
transform: translate(calc(var(--shimmy-distance) * 1%));
}
50% {
clip-path: var(--clip-six);
transform: translate(0, 0);
}
55% {
clip-path: var(--clip-seven);
transform: translate(calc(var(--shimmy-distance) * 1%), 0);
}
60% {
clip-path: var(--clip-seven);
transform: translate(0, 0);
}
31%, 61%, 100% {
clip-path: var(--clip-four);
}
}
这是最难理解的部分。这里究竟发生了什么?我们的关键帧为故障元素上的剪辑路径设置动画。同时,我们左右摆动元件。我们可以放慢动画速度,看看发生了什么。
这是一个慢速演示,用于展示动画是如何工作的:
我还制作了一个演示,显示剪辑的不同状态:
这将使我们更容易维护和调整不同的动画状态。
通过悬停连接
CSS如何创建赛博朋克 2077按钮故障效果?剩下要做的就是将它与:hover
选择器联系起来。默认情况下,我们隐藏故障元素。然后,在悬停时,我们将其显示为动画:
.cybr-btn__glitch {
display: none;
}
.cybr-btn:hover .cybr-btn__glitch {
display: block;
}
这给了我们我们正在寻找的结果:
CSS赛博朋克 2077按钮故障效果教程总结
这就是你仅使用 CSS 重新创建赛博朋克 2077 按钮的方法!
还记得我们如何使用颜色变量吗?这是有原因的。将 HSL 与变量相结合,我们不仅可以轻松添加颜色变体,还可以添加:active
颜色变化。