TV动画《我心里危险的东西》官网开屏动画复刻
TV 动画《我心里危险的东西》(Boku no Kokoro no Yabai Yatsu / 简称 Bokuyaba)官方网站(bokuyaba-anime.com)在页面首次载入时,展现了一套精致优雅的开屏动画。它通过将 Logo 标题分割为多个独立的 SVG/PNG 图层,在经过短暂的圆环 Loader 旋转预载后,各个分图层从不同的倾斜、缩放与偏移位置按特定时间差平滑拼合归位,营造出极具仪式感的开场视觉冲力。
本文将详细剖析这套动画的核心实现原理,并附上完整的复刻源代码。
动画效果演示
以下为复刻实现的开屏动画效果实录:

核心实现技术拆解
整个开屏动画由三个关键阶段构成:独立圆环旋转加载(0.6s) 多图层延时分层组装 淡出解封主界面。
1. 0.6s 独立旋转 Loader 动画
在开屏动画尚未播放之前,界面中央会优先展示一个轻量的旋转加载圈。它不需要复杂的图片资源,仅通过 CSS 伪元素与 keyframes 即可实现:
.l-wrap__loader::before { content: ""; width: 100%; height: 100%; display: block; position: absolute; top: 0; left: 0; border-radius: 50%; border: 0.3rem solid #ffffff; border-top: 0.3rem solid #fb32ca; /* 突出粉色描边 */ animation: rotate 0.6s linear infinite; box-sizing: border-box;}
@keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); }}当 DOM 加载完毕并经过 600ms 后,通过给 body 容器添加 .is-op 类,加载圈的 opacity 会在 0.5s 内平滑过渡为 0 并隐藏。
2. Logo 9 图层拆分与初始形变设置
官方将 Logo 拆分成了 9 个独立图层(logo_1.png ~ logo_9.png 与 logo_11.png)。为了让各个拆分块在拼合时产生错落有致的入场效果,初始状态(未添加 .is-op 类)下为不同的图层单独设定了缩放比例、位移偏移量以及旋转中心点(transform-origin):
.p-op__logo-item { position: relative; opacity: 0; filter: brightness(120%) blur(8px); transform: scale(1.05);}
/* 各分图层的个性化偏移与旋转中心点 (transform-origin) */.p-op__logo-item:nth-of-type(1) { transform: translate3d(0, 0, 0) scale(1.1);}.p-op__logo-item:nth-of-type(2) { transform: translate3d(0, -15px, 0) scale(1.05);}.p-op__logo-item:nth-of-type(3) { transform: translate3d(-10px, 0, 0) scale(1.05); transform-origin: 65.3% 24.4%;}.p-op__logo-item:nth-of-type(5) { transform: translate3d(0, 0, 0) scale(1.35); transform-origin: 17.9% 74.8%;}.p-op__logo-item:nth-of-type(9) { transform: translate3d(6px, 0, 0) scale(0.8); transform-origin: 91.8% 58.1%;}针对带有旋转和缩放的某些文字图层,精确定位 transform-origin 是确保其在缩放归位过程中轨迹自然的秘诀。
3. 三阶贝塞尔缓动与序次延时 (Transition Delay)
当触发开屏状态(类名变为 .is-op)时,所有图层的 transform 均归位为 translate3d(0, 0, 0) scale(1),opacity 恢复为 1,blur 变为 0。
为了实现依次弹入的效果,各图层配置了不同的 transition-delay 步频:
.is-op .p-op__logo-item { opacity: 1; filter: brightness(100%) blur(0); transform: translate3d(0, 0, 0) scale(1); transition: transform cubic-bezier(0.01, 0.53, 0.37, 0.99) 1s, opacity cubic-bezier(0.5, 1, 0.89, 1) 1s, filter cubic-bezier(0.5, 1, 0.89, 1) 1s;}
/* 阶梯式动画触发延迟 */.is-op .p-op__logo-item:nth-of-type(1) { transition-delay: 0.9s; }.is-op .p-op__logo-item:nth-of-type(2) { transition-delay: 1.22s; }.is-op .p-op__logo-item:nth-of-type(3) { transition-delay: 1.54s; }.is-op .p-op__logo-item:nth-of-type(4) { transition-delay: 1.86s; }.is-op .p-op__logo-item:nth-of-type(5) { transition-delay: 2.41s; }.is-op .p-op__logo-item:nth-of-type(6) { transition-delay: 2.65s; }.is-op .p-op__logo-item:nth-of-type(7) { transition-delay: 2.89s; }.is-op .p-op__logo-item:nth-of-type(8) { transition-delay: 3.13s; }.is-op .p-op__logo-item:nth-of-type(9) { transition-delay: 3.37s; }使用的三次贝塞尔曲线 cubic-bezier(0.01, 0.53, 0.37, 0.99) 前段急速冲刺、后段吸附减速,使整个组装动画非常具有吸附感。
4. 状态机控制与强制重绘 (Reflow)
通过 JavaScript 来切换控制类 is-op,并在重新播放动画时利用访问 offsetWidth 触发浏览器强制重绘(Reflow),以重置动画状态:
function playAnimation() { document.body.classList.remove('is-op', 'is-load');
// 强制触发浏览器重绘 (Reflow),重置 CSS 过渡与 keyframes 动画 void document.body.offsetWidth;
const delay = parseInt(document.getElementById('loaderDuration').value, 10) || 600;
// 定时器进入开屏 Logo 组装阶段 setTimeout(() => { document.body.classList.add('is-op'); }, delay);}完整源代码
点击下方展开框即可查阅并复制包含 HTML、CSS 与 JS 控制面板的完整复刻源码:
点击展开查看完整 op_animation.html 源代码
<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TV动画《我心里危险的东西》- 开屏动画还原</title> <style> :root { --winH: 100vh; --color-black: #333; --color-white: #fff; --color-pink: #fb32ca; }
html { width: 100%; font-size: 62.5%; overflow-x: hidden; overflow-y: auto; } @media screen and (max-width: 960px) and (min-width: 561px) { html { font-size: 68.75%; } } @media screen and (max-width: 960px) and (min-width: 751px) { html { font-size: 78.125%; } }
body { width: 100%; height: 100vh; margin: 0; padding: 0; font-size: 1.6rem; line-height: 1.3; letter-spacing: 0.15rem; font-weight: 500; color: var(--color-black); background: #ffffff; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; overflow: hidden; user-select: none; }
img { max-width: 100%; display: block; vertical-align: middle; }
/* 容器与加载圈 */ .l-wrap { display: block; position: relative; width: 100%; height: 100vh; z-index: 0; }
.l-wrap__loader { height: 6rem; width: 6rem; display: block; position: fixed; top: 50%; left: 50%; z-index: 15; transform: translate(-50%, -50%); pointer-events: none; opacity: 1; transition: opacity 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); } @media screen and (max-width: 960px) { .l-wrap__loader { height: 3rem; width: 3rem; } }
.is-op .l-wrap__loader, .is-load .l-wrap__loader { opacity: 0; }
.l-wrap__loader::before { content: ""; width: 100%; height: 100%; display: block; position: absolute; top: 0; left: 0; border-radius: 50%; border: 0.3rem solid var(--color-white); border-top: 0.3rem solid var(--color-pink); animation: rotate 0.6s linear infinite; z-index: 20; pointer-events: none; box-sizing: border-box; }
@keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
/* 开屏动画层 */ .p-op { display: flex; align-items: center; justify-content: center; position: fixed; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; z-index: 100; pointer-events: none; } .is-op .p-op { pointer-events: auto; } .is-load .p-op { opacity: 0; transition: opacity 1.4s cubic-bezier(0.65, 0, 0.35, 1) 0.5s; pointer-events: none; }
.p-op__logo { position: relative; width: 82%; max-width: 550px; transform: translateY(5%) scale(1.15); } .is-op .p-op__logo { transform: translateY(5%) scale(1); transition: transform 12s cubic-bezier(0.5, 1, 0.89, 1), filter 0.55s; }
.p-op__logo-item { position: relative; opacity: 0; filter: brightness(120%) blur(8px); transform: scale(1.05); } .p-op__logo-item:not(:first-of-type) { position: absolute; top: 0; left: 0; width: 100%; }
/* 初始拆分偏移及缩放设置 */ .p-op__logo-item:nth-of-type(1) { transform: translate3d(0, 0, 0) scale(1.1); } .p-op__logo-item:nth-of-type(2) { transform: translate3d(0, -15px, 0) scale(1.05); } .p-op__logo-item:nth-of-type(3) { transform: translate3d(-10px, 0, 0) scale(1.05); transform-origin: 65.3% 24.4%; } .p-op__logo-item:nth-of-type(4) { transform: translate3d(-2px, -2px, 0) scale(0.9); transform-origin: 86.2% 21.4%; } .p-op__logo-item:nth-of-type(5) { transform: translate3d(0, 0, 0) scale(1.35); transform-origin: 17.9% 74.8%; } .p-op__logo-item:nth-of-type(6) { transform: translate3d(15px, 0, 0) scale(1.05); } .p-op__logo-item:nth-of-type(7) { transform: translate3d(5px, 0, 0) scale(1.01); } .p-op__logo-item:nth-of-type(8) { transform: translate3d(5px, 0, 0) scale(1.01); } .p-op__logo-item:nth-of-type(9) { transform: translate3d(6px, 0, 0) scale(0.8); transform-origin: 91.8% 58.1%; }
/* is-op 状态下的动画归位 */ .is-op .p-op__logo-item { opacity: 1; filter: brightness(100%) blur(0); transform: translate3d(0, 0, 0) scale(1); transition: transform cubic-bezier(0.01, 0.53, 0.37, 0.99) 1s, opacity cubic-bezier(0.5, 1, 0.89, 1) 1s, filter cubic-bezier(0.5, 1, 0.89, 1) 1s; } .is-op .p-op__logo-item:nth-of-type(1) { transition-delay: 0.9s; } .is-op .p-op__logo-item:nth-of-type(2) { transition-delay: 1.22s; } .is-op .p-op__logo-item:nth-of-type(3) { transition-delay: 1.54s; } .is-op .p-op__logo-item:nth-of-type(4) { transition-delay: 1.86s; } .is-op .p-op__logo-item:nth-of-type(5) { transition-delay: 2.41s; } .is-op .p-op__logo-item:nth-of-type(6) { transition-delay: 2.65s; } .is-op .p-op__logo-item:nth-of-type(7) { transition-delay: 2.89s; } .is-op .p-op__logo-item:nth-of-type(8) { transition-delay: 3.13s; } .is-op .p-op__logo-item:nth-of-type(9) { transition-delay: 3.37s; } .is-op .p-op__logo-item:nth-of-type(10) { transition-delay: 3.61s; }
/* 控制面板 */ .controls { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); z-index: 1000; background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(12px); border: 1px solid rgba(251, 50, 202, 0.2); padding: 12px 24px; border-radius: 40px; display: flex; align-items: center; gap: 16px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08); font-size: 14px; } .btn-replay { background: linear-gradient(135deg, #fb32ca, #e012ac); color: #fff; border: none; padding: 8px 20px; border-radius: 20px; font-weight: 600; cursor: pointer; box-shadow: 0 4px 12px rgba(251, 50, 202, 0.3); } </style></head><body>
<div class="l-wrap js-wrap"> <div class="l-wrap__loader"></div> <div class="l-wrap__cover"> <div class="p-op" id="js-op"> <div class="p-op__logo"> <div class="p-op__logo-item"><img src="./assets/img/op/logo_1.png" alt=""></div> <div class="p-op__logo-item"><img src="./assets/img/op/logo_2.png" alt=""></div> <div class="p-op__logo-item"><img src="./assets/img/op/logo_3.png" alt=""></div> <div class="p-op__logo-item"><img src="./assets/img/op/logo_4.png" alt=""></div> <div class="p-op__logo-item"><img src="./assets/img/op/logo_5.png" alt=""></div> <div class="p-op__logo-item"><img src="./assets/img/op/logo_6.png" alt=""></div> <div class="p-op__logo-item"><img src="./assets/img/op/logo_7.png" alt=""></div> <div class="p-op__logo-item"><img src="./assets/img/op/logo_8.png" alt=""></div> <div class="p-op__logo-item"><img src="./assets/img/op/logo_9.png" alt=""></div> <div class="p-op__logo-item"><img src="./assets/img/op/logo_11.png" alt=""></div> </div> </div> </div></div>
<div class="controls"> <button class="btn-replay" onclick="playAnimation()">↻ 重新播放动画</button></div>
<script> function playAnimation() { document.body.classList.remove('is-op', 'is-load'); void document.body.offsetWidth; setTimeout(() => { document.body.classList.add('is-op'); }, 600); }
window.addEventListener('DOMContentLoaded', () => { playAnimation(); });</script></body></html>支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!




















































































