很久之前就发现了一个非常心动的效果,一路从QQ追到了Telegrame,再追到了X,最后到了CodePen,感觉一定很复杂,现在看来也没什么难度。 原链接
按钮
原作者的代码非常详尽,还用了很多高级技巧,但是我只在乎动画本身,简化后,按钮也不过是clip-path和transform位移的巧妙结合,手动实现一下吧,首先是基本的按钮
准备工作
为了方便,用了tailwindcss(是的,我觉得tailwindcss的可读性很高,比跳转去看实现方便的多),可以直接用CDN引入,然后定义全局变量
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style>
body {
display: grid;
place-items: center;
min-height: 100vh;
background: #000;
color: white;
}
:root {
--accent: hsl(183 38% 57%);
--shadow: hsl(10 100% 60%);
--corner: 12px;
--border: 2px;
--clip: polygon(
0 0,
100% 0,
100% calc(100% - var(--corner)),
calc(100% - var(--corner)) 100%,
0% 100%
);
}
</style>
按钮的结构和基础样式
简单的说,按钮有两层,正常显示看到的那层,Hover时另外一层glitch从隐藏变为显示
<style>
.cyber-btn {
/* 使用变量应用切角 */
clip-path: var(--clip);
}
.cyber-btn:hover {
background-color: var(--accent);
color: #1a1a1a;
}
.cyber-btn:hover > kbd {
visibility: hidden;
}
.cyber-btn:is(:hover, :focus-visible) .glitch {
display: flex; /* 悬停时显示 */
animation: glitch 1.5s infinite; /* 并播放动画 */
}
</style>
<button
class="cyber-btn mx-auto mt-20 relative w-[200px] p-2 border border-[var(--accent)] text-[var(--accent)] cursor-pointer flex items-center gap-2 uppercase z-10"
popovertarget="upgrade"
id="showModalBtn"
>
<kbd
class="w-[30px] aspect-square rounded-full bg-[var(--accent)] text-[#1a1a1a] inline-grid place-items-center font-bold"
>U</kbd
>
<span>Upgrade</span>
<div
class="glitch hidden absolute inset-0 pointer-events-none bg-black text-aqua items-center gap-2 p-2"
>
<span class="text-shadow-[0_1px_var(--shadow)]">Upgrade</span>
</div>
</button>
Glitch 动画
这部分是整个效果的灵魂。glitch 动画通过在极短的时间内快速切换不同的 clip-path 和 transform 位移,模拟出数字信号干扰的视觉效果。它将 glitch 层切割成不规则的碎片并使其抖动,从而产生炫酷的故障感。
<style>
:root {
--shimmy-distance: 5px; /* 抖动距离 */
--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(var(--shimmy-distance), 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(var(--shimmy-distance), 0);
}
13% {
clip-path: var(--clip-three);
transform: translate(0, 0);
}
14%,
21% {
clip-path: var(--clip-four);
transform: translate(var(--shimmy-distance), 0);
}
25% {
clip-path: var(--clip-five);
transform: translate(var(--shimmy-distance), 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(var(--shimmy-distance));
}
50% {
clip-path: var(--clip-six);
transform: translate(0, 0);
}
55% {
clip-path: var(--clip-seven);
transform: translate(var(--shimmy-distance), 0);
}
60% {
clip-path: var(--clip-seven);
transform: translate(0, 0);
}
31%,
61%,
100% {
clip-path: var(--clip-four);
}
}
</style>
不得不佩服原作者的耐心,能把动画的每一帧都调校得如此精细,我暂且不想管这是怎么弄出来的。到这里,我们的赛博朋克按钮就完成了!
模态框
接下来,我们为这个按钮创建一个与之匹配的模态框。模态框的动画运用了类似的 CSS 属性,但更侧重于通过 transition 和不同的 transition-delay 来营造一种元素逐个登场的序列感。
HTML和JS
<!-- 模态框 HTML -->
<div
class="modal fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[clamp(340px,55vw,480px)] bg-transparent invisible opacity-0 z-[100] p-5"
id="upgradeModal"
>
<div class="relative">
<!-- 模态框背景,会有一个展开动画 -->
<div class="expanding-div absolute inset-0 bg-black z-[-1] border-2 border-[var(--accent)]"></div>
<div class="content">
<h2 class="text-xl uppercase text-center mb-4">
<span>Craft of UI – Pro upgrade</span>
</h2>
<div class="my-5 text-center leading-relaxed">
<p>You are one step away. Do you want to proceed?</p>
</div>
<div class="flex justify-around mt-5">
<button class="cyber-btn closeModalBtn flex-1 mx-2.5">Cancel</button>
<button class="cyber-btn closeModalBtn flex-1 mx-2.5">Proceed</button>
</div>
</div>
</div>
</div>
<script>
// 将按钮的 popovertarget 替换为 id,用于 JS 控制
document.querySelector('.cyber-btn').id = 'showModalBtn';
const showBtn = document.getElementById("showModalBtn");
const modal = document.getElementById("upgradeModal");
const closeBtns = document.querySelectorAll(".closeModalBtn");
showBtn.addEventListener("click", () => {
modal.classList.add("is-open");
});
closeBtns.forEach((btn) => {
btn.addEventListener("click", () => {
modal.classList.remove("is-open");
});
});
</script>
动画效果 CSS
1.模态框容器:淡入淡出。 2.侧边装饰条:利用 ::before 和 ::after 伪元素实现,从左侧滑入。 3.标题下划线:从左到右展开。 4.背景:同样从左到右展开。
<style>
.modal {
transition: visibility 0.5s, opacity 0.5s;
}
.modal.is-open {
visibility: visible;
opacity: 1;
}
/* 侧边装饰条 */
.modal::after,
.modal::before {
content: "";
position: absolute;
top: 1px;
bottom: 1px;
right: 100%;
width: 1rem;
border: var(--border) solid var(--accent);
background: var(--accent);
opacity: 0;
transform: translateX(-25%);
transition: 0.2s ease-out;
transition-property: opacity, transform;
}
.modal.is-open::before,
.modal.is-open::after {
transition-delay: 0.2s; /* 延迟出现 */
opacity: 1;
transform: translateX(var(--border));
}
/* 标题下划线 */
.modal h2 {
position: relative;
}
.modal h2::after {
content: "";
height: var(--border);
position: absolute;
top: 100%;
left: 0;
right: 0;
background: var(--accent);
transform-origin: 0 50%;
transform: scaleX(0);
transition: transform 0.26s ease-in;
transition-delay: 0.325s; /* 延迟更久 */
}
.modal.is-open h2::after {
transform: scaleX(1);
}
/* 背景展开效果 */
.expanding-div {
transform-origin: left;
transform: scaleX(0);
transition: transform 0.5s;
transition-delay: 0.325s;
}
.modal.is-open .expanding-div {
transform: scaleX(1);
}
</style>
总结
这个例子给我最大的启示就是,看似复杂的动画,只是由一些基础的CSS属性的组合以及精密控制实现的,
评论区
评论加载中...