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 开源项目进行学习和二次优化,展示了如何:

  1. 利用 Vue 3 Composition API 构建响应式动画组件
  2. 通过 Composables 实现逻辑复用和性能优化
  3. 使用 requestAnimationFrame 节流高频事件
  4. 提取 CSS 类 提高代码可维护性
  5. 注重可访问性 添加键盘交互支持

GitHub 原项目地址https://github.com/marker964/animated-characters-login-page

Gitee 优化版地址https://gitee.com/gomes/animated-characters-login-page

如果觉得有帮助,欢迎点赞收藏!有什么问题可以在评论区讨论。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐