一、这不是插图,是代码
打开 https://lx.yfdxs.com/login,页面底部站着四个彩色的「小怪兽」——紫色、黑色、橙色、黄色。它们不是静态插画,而是由纯 CSS 和 Vue 响应式驱动的活角色。核心代码在 LoginAnimatedCharacters.vue 中(约 300 行),通过四个 div + 精致的 computed 计算属性,赋予了它们生命。
二、四个角色,四种性格
| 角色 | 颜色 | 宽 | 高 | 特点 |
|---|---|---|---|---|
| 🟣 紫色怪 | #6C3FF5 | 180px | 400~440px | 最高,会眨眼,会伸长脖子偷看 |
| ⚫ 黑色怪 | #2D2D2D | 120px | 310px | 高冷,也会眨眼,但动作克制 |
| 🟠 橙色怪 | #FF9B6B | 240px | 200px | 矮胖半圆,只有瞳孔没有眼眶 |
| 🟡 黄色怪 | #E8D754 | 140px | 230px | 有小嘴巴,情绪写在脸上 |
紫色和黑色拥有完整的「眼眶 + 瞳孔」结构(LoginAnimatedEyeBall),橙色和黄色则是简约的「纯瞳孔」风格(LoginAnimatedPupil)。
三、鼠标追踪:每一像素都跟随
四个怪兽的身体位置和眼球方向会根据鼠标位置实时计算。核心算法:
const centerX = rect.left + rect.width / 2
const centerY = rect.top + rect.height / 3
const deltaX = mouseX.value - centerX
const deltaY = mouseY.value - centerY
const faceX = Math.max(-15, Math.min(15, deltaX / 20)) // 眼球 X 偏移
const faceY = Math.max(-10, Math.min(10, deltaY / 30)) // 眼球 Y 偏移
const bodySkew = Math.max(-6, Math.min(6, -deltaX / 120)) // 身体倾斜- 眼球跟随:
deltaX / 20和deltaY / 30将屏幕像素差值缩放到 15px/10px 范围内,避免眼球飞出眼眶 - 身体倾斜:
-deltaX / 120取反——鼠标在右侧时身体向左倾,模拟「歪头看」的自然姿态 - 限制范围:所有值通过
Math.max/min钳制,确保动画永远在合理区间
四、输入时的小剧场
聚焦输入框时(isTyping = true):
紫色怪的身体加高 40px(从 400→440),并且两个角色互相对视:
watch(() => props.isTyping, (val) => {
if (val) {
isLookingAtEachOther.value = true
// 800ms 后恢复
lookTimeout = setTimeout(() => { isLookingAtEachOther.value = false }, 800)
}
})紫黑两怪的眼球会强制转向对方方向,仿佛在说「看,有人在登录!」
输入密码并切换可见时(showPassword = true):
所有四个怪兽的眼睛齐刷刷转向密码区域:
const purpleForceLookX = computed(() => {
if (isPasswordVisible.value) return isPurplePeeking.value ? 4 : -4
})更有趣的是紫色怪会周期性偷看——每隔 2~5 秒(随机),脖子伸长,眼球猛转,然后缩回:
const interval = Math.random() * 3000 + 2000
peekTimeout = setTimeout(() => {
isPurplePeeking.value = true
setTimeout(() => { isPurplePeeking.value = false }, 800)
}, interval)五、眨眼动画:不机械的随机节奏
紫黑两怪会随机眨眼——间隔在 3~7 秒之间随机,眨眼持续 150ms:
function schedulePurpleBlink() {
const interval = Math.random() * 4000 + 3000 // 3~7秒
purpleBlinkTimeout = setTimeout(() => {
isPurpleBlinking.value = true
setTimeout(() => {
isPurpleBlinking.value = false
schedulePurpleBlink() // 递归,永远循环
}, 150)
}, interval)
}这种随机性让角色看起来不像程序——像活物。比起固定 5 秒眨一次,随机的 3~7 秒让每次眨眼都不可预测。
六、镜像布局支持
代码通过 isMirrored 和 resolveDirection 系列函数支持水平翻转——如果登录组件使用 horizontal="left" 布局,所有位置和偏移会自动镜像。左右布局切换只需改一个 prop,无需重写任何动画逻辑。
七、性能考量
effectScope隔离:所有watch用effectScope包裹,组件卸载时scope.stop()一次性清理transition: duration-700 ease-in-out:身体移动用 CSS transition 而非 JS 动画,利用 GPU 合成层setTimeout精准清理:onUnmounted中清除所有 4 个 timeout,零泄漏
八、技术总结
| 维度 | 实现 |
|---|---|
| 眼球跟随 | deltaX/20, deltaY/30 缩放 + Math.min/max 钳制 |
| 身体倾斜 | CSS skewX + 负反馈偏移 |
| 眨眼 | 递归 setTimeout,3~7 秒随机间隔 |
| 输入反应 | watch(isTyping) → 对视 800ms |
| 密码偷看 | watch(passwordLength, showPassword) → 随机 peek |
| 镜像支持 | isMirrored → 所有绝对值自动取反 |
| 性能 | effectScope + CSS transition + 清理 |
九、结语
登录页通常是用户最无聊的触点——输入账号密码,点登录,结束。但这四个小怪兽把登录变成了一次微交互体验。用户会心一笑,网站的人情味就出来了。技术上并不复杂——300 行 Vue + CSS——但创意的价值远超代码量。
这种细节是区别「能用的网站」和「让人记住的网站」的关键——也是项目技术实力与产品品味兼具的最好证明。



