Vue 3动画角色登录页:从创意到优化
Vue 3 动画角色登录页面:从创意实现到性能优化
本文基于 GitHub 开源项目进行学习和优化实践,原项目地址:https://github.com/marker964/animated-characters-login-page
项目简介
今天给大家分享一个有趣的 Vue 3 登录页面项目 —— CareerCompass。这个项目的亮点在于左侧有四个可爱的动画角色,它们会根据用户的输入行为做出不同的反应:
- 🎯 眼睛跟随鼠标 - 角色的眼睛会追踪鼠标移动
- 📧 输入邮箱时 - 角色会变高,仿佛在关注你的输入
- 🔐 输入密码时 - 角色会用手遮挡眼睛,保护隐私
- 👀 显示密码时 - 角色会偷偷偷看,增添趣味性

技术栈
- Vue 3 - 使用 Composition API
- Vite - 极速开发构建工具
- Tailwind CSS - 现代化 CSS 框架
- tailwindcss-animate - 动画增强插件
核心实现思路
1. 眼睛追踪原理
眼睛追踪的核心是计算鼠标相对于眼睛中心的位置,然后根据角度和距离计算瞳孔的偏移:
const calculatePupilPosition = (eyeRef, mouseX, mouseY, maxDistance) => {
const rect = eyeRef.getBoundingClientRect()
const centerX = rect.left + rect.width / 2
const centerY = rect.top + rect.height / 2
const deltaX = mouseX - centerX
const deltaY = mouseY - centerY
// 限制瞳孔移动距离,防止超出眼球范围
const distance = Math.min(Math.sqrt(deltaX ** 2 + deltaY ** 2), maxDistance)
const angle = Math.atan2(deltaY, deltaX)
return {
x: Math.cos(angle) * distance,
y: Math.sin(angle) * distance
}
}
2. 角色状态响应
通过 Vue 的响应式系统,我们可以轻松实现角色对不同状态的响应:
<script setup>
const props = defineProps({
isTyping: { type: Boolean, default: false },
showPassword: { type: Boolean, default: false },
passwordLength: { type: Number, default: 0 }
})
// 判断是否处于遮挡密码状态
const isHidingPassword = computed(() =>
props.passwordLength > 0 && !props.showPassword
)
// 角色变高的条件
const shouldGrow = computed(() =>
props.isTyping || isHidingPassword.value
)
</script>
3. 角色身体变形
使用 CSS transform: skewX() 实现角色身体的倾斜效果,让角色看起来更生动:
<template>
<div
class="character"
:style="{
transform: shouldGrow
? `skewX(${bodySkew - 12}deg) translateX(40px)`
: `skewX(${bodySkew}deg)`,
transformOrigin: 'bottom center'
}"
/>
</template>
性能优化历程
在项目初版中,我发现了几个性能问题,经过优化后效果显著提升。
问题一:重复的鼠标事件监听
初版代码中,三个组件各自监听了 mousemove 事件:
// EyeBall.vue
onMounted(() => window.addEventListener('mousemove', onMouseMove))
// Pupil.vue
onMounted(() => window.addEventListener('mousemove', onMouseMove))
// AnimatedCharacters.vue
onMounted(() => window.addEventListener('mousemove', onMouseMove))
这意味着每次鼠标移动,会触发 3 次 事件处理!
优化方案:Composable + 单例模式
创建 useMousePosition composable,使用单例模式共享鼠标位置:
// src/composables/useMousePosition.js
import { ref } from 'vue'
// 单例:全局共享
let mouseX = ref(0)
let mouseY = ref(0)
let isListening = false
export function useMousePosition() {
if (!isListening) {
window.addEventListener('mousemove', onMouseMove, { passive: true })
isListening = true
}
return { mouseX, mouseY }
}
问题二:高频事件无节流
mousemove 是高频事件,每秒可能触发数十次,导致 computed 计算过于频繁。
优化方案:requestAnimationFrame 节流
let rafId = null
let pendingX = 0
let pendingY = 0
const onMouseMove = (e) => {
pendingX = e.clientX
pendingY = e.clientY
// 使用 rAF 节流,每帧最多更新一次
if (!rafId) {
rafId = requestAnimationFrame(() => {
mouseX.value = pendingX
mouseY.value = pendingY
rafId = null
})
}
}
问题三:冗长的 inline CSS class
初版 App.vue 中,input 元素的 class 超过 200 字符:
<!-- 优化前 -->
<input
class="login-input flex w-full rounded-full border px-4 py-2 text-base
ring-offset-background file:border-0 file:bg-transparent
file:text-sm file:font-medium file:text-foreground
placeholder:text-muted-foreground focus-visible:outline-none
focus-visible:ring-2 focus-visible:ring-ring
focus-visible:ring-offset-2 disabled:cursor-not-allowed
disabled:opacity-50 md:text-sm h-12 bg-background
border-border/60 focus:border-primary"
/>
优化方案:提取 CSS 类
/* src/style.css */
.login-input {
@apply flex w-full rounded-full border px-4 py-2 text-base ring-offset-background;
@apply placeholder:text-muted-foreground;
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring;
@apply h-12 bg-background border-border/60;
}
.login-input:focus {
@apply border-primary;
}
<!-- 优化后 -->
<input class="login-input" />
可访问性改进
添加键盘支持
密码显示切换按钮初版只支持鼠标点击,优化后添加了键盘支持:
<button
@click="showPassword = !showPassword"
@keydown.enter="showPassword = !showPassword"
@keydown.space.prevent="showPassword = !showPassword"
class="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<!-- 眼睛图标 -->
</button>
本地化图片资源
初版使用外部 URL 加载 Logo 图片,存在加载失败风险:
<!-- 优化前 -->
<img src="https://i-postimg.cc/nLrDYrHW/icon.png" />
优化后将图片存入本地 src/assets/:
<!-- 优化后 -->
<img src="./assets/logo.svg" />
项目结构
src/
├── assets/
│ └── logo.svg # 本地化 Logo
├── components/
│ ├── AnimatedCharacters.vue # 主角色组件
│ ├── EyeBall.vue # 眼球组件(带眨眼)
│ ├── Pupil.vue # 瞳孔组件
│ └── InteractiveHoverButton.vue # 悬浮按钮
├── composables/
│ ├── useMousePosition.js # 鼠标位置追踪
│ └── useEyeTracking.js # 眼睛追踪计算
├── App.vue # 主应用
├── main.js # 入口文件
└── style.css # 全局样式
运行项目
# 安装依赖
npm install
# 启动开发服务器
npm run dev
# 构建生产版本
npm run build
总结
本文基于 GitHub 开源项目进行学习和二次优化,展示了如何:
- 利用 Vue 3 Composition API 构建响应式动画组件
- 通过 Composables 实现逻辑复用和性能优化
- 使用 requestAnimationFrame 节流高频事件
- 提取 CSS 类 提高代码可维护性
- 注重可访问性 添加键盘交互支持
GitHub 原项目地址:https://github.com/marker964/animated-characters-login-page
Gitee 优化版地址:https://gitee.com/gomes/animated-characters-login-page
如果觉得有帮助,欢迎点赞收藏!有什么问题可以在评论区讨论。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)