50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Form Wave(表单label波动效果)

📅 我们继续 50 个小项目挑战!------ FormWave组件

仓库地址:https://github.com/SunACong/50-vue-projects

项目预览地址:https://50-vue-projects.vercel.app/


🎯 组件目标

构建一个美观、动态的登录表单,重点在于实现带有浮动标签(floating label)的输入框体验,提升交互感知和视觉效果,适合作为任何登录注册模块的基础模板。

🛠️ 技术实现点

  • 使用 Vue3 + <script setup> 编写响应式逻辑。
  • 使用 TailwindCSS 完全控制样式,特别是浮动文字的动画。
  • 实现 focus/blur 状态下标签文字的动画浮动效果。
  • 使用 v-model 实现双向绑定,结合 focus 状态精准控制浮动逻辑。

🧱 组件实现

html 复制代码
<!-- 🌈 模板部分 Template -->
<template>
    <div class="flex h-screen items-center justify-center bg-gray-800 text-gray-300">
        <div class="rounded-2xl bg-gray-500/60 p-12 text-center">
            <h1 class="text-4xl font-bold text-gray-300">Please Login</h1>
            <form>
                <!-- Email 输入框 -->
                <div class="form-control relative mt-10 border-b-2 border-b-white">
                    <input
                        class="peer relative z-10 w-full bg-transparent py-3 text-white focus:border-sky-300 focus:outline-none"
                        type="text"
                        required
                        v-model="emailValue"
                        @focus="activeInput = 'email'"
                        @blur="handleBlur('email')" />
                    <label class="pointer-events-none absolute top-4 left-0">
                        <!-- ✨ 字符级浮动动画 -->
                        <span
                            v-for="(letter, idx) in 'Email'.split('')"
                            :key="idx"
                            :class="[
                                'inline-block min-w-[5px] text-lg transition-all duration-300',
                                'transform-gpu',
                                {
                                    '-translate-y-8 text-sky-300':
                                        activeInput === 'email' || emailValue,
                                },
                            ]"
                            :style="{
                                transitionDelay: `${idx * 50}ms`,
                                transitionTimingFunction: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
                            }">
                            {{ letter }}
                        </span>
                    </label>
                </div>

                <!-- Password 输入框 -->
                <div class="form-control relative mt-10 border-b-2 border-b-white">
                    <input
                        class="peer relative z-10 w-full bg-transparent py-3 text-white focus:border-sky-300 focus:outline-none"
                        type="password"
                        required
                        v-model="passwordValue"
                        @focus="activeInput = 'password'"
                        @blur="handleBlur('password')" />
                    <label class="pointer-events-none absolute top-4 left-0">
                        <!-- ✨ 字符级浮动动画 -->
                        <span
                            v-for="(letter, idx) in 'Password'.split('')"
                            :key="idx"
                            :class="[
                                'inline-block min-w-[5px] text-lg transition-all duration-300',
                                'transform-gpu',
                                {
                                    '-translate-y-8 text-sky-300':
                                        activeInput === 'password' || passwordValue,
                                },
                            ]"
                            :style="{
                                transitionDelay: `${idx * 50}ms`,
                                transitionTimingFunction: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
                            }">
                            {{ letter }}
                        </span>
                    </label>
                </div>

                <!-- 登录按钮 -->
                <button
                    class="mt-10 w-full rounded bg-blue-500 px-4 py-2 font-bold hover:bg-blue-600 focus:ring-2 focus:ring-blue-400 focus:outline-none"
                    type="submit">
                    Login
                </button>

                <p class="mt-10">
                    Don't have an account?
                    <a href="#" class="text-blue-400 hover:underline">Register</a>
                </p>
            </form>
        </div>
    </div>
</template>

🧩 重点效果实现

js 复制代码
<!-- ⚙️ 脚本部分 Script -->
<script setup>
import { ref } from 'vue'

// 输入值响应式变量
const emailValue = ref('')
const passwordValue = ref('')

// 当前聚焦的输入项
const activeInput = ref(null)

// 失焦逻辑:如果输入框为空,则取消浮动状态
const handleBlur = (inputName) => {
    if (
        (inputName === 'email' && !emailValue.value) ||
        (inputName === 'password' && !passwordValue.value)
    ) {
        activeInput.value = null
    }
}
</script>
  • 标签浮动是通过 translate-y 配合 activeInput 或绑定值来实现的。
  • 使用 transition-delay 实现了字符级别的延迟动画,让文字一个个浮动。
  • 利用 cubic-bezier 定义自定义缓动函数,提升动画的弹性和自然感。

🎨 TailwindCSS 样式重点讲解

类名 作用
peer / relative z-10 确保 input 在 label 之上,供 label 状态判断使用
-translate-y-8 控制文字上浮距离
transition-delay 实现文字一个个浮动的动画延迟
transform-gpu 使用 GPU 加速动画,提高性能和流畅度
focus:outline-none / focus:ring-2 聚焦时视觉反馈
min-w-[5px] 保证字符宽度一致,不会断行

🧾 常量定义 + 组件路由建议

constants/index.js 添加组件预览常量:

js 复制代码
{
        id: 8,
        title: 'Form Wave',
        image: 'https://50projects50days.com/img/projects-img/8-form-wave.png',
        link: 'FormWave',
    },

router/index.js 中添加路由选项:

js 复制代码
{
        path: '/FormWave',
        name: 'FormWave',
        component: () => import('@/projects/FormWave.vue'),
    },

路由守卫可后续扩展身份验证逻辑,跳转到注册或首页。


🧠 小结

完成了通用场景下的表单样式界面,可以为你以后的表单设计以及登录页面提供一些灵感进行参考!!!🚀


👉 下一篇,我们将完成声音组件 Sound Board组件,可以实现点击发出对应的声音!🚀

相关推荐
LaughingZhu41 分钟前
Product Hunt 每日热榜 | 2026-05-21
前端·人工智能·经验分享·chatgpt·html
怕浪猫1 小时前
Electron 开发实战(一):从零入门核心基础与环境搭建
前端·electron·ai编程
小鹏linux2 小时前
Ubuntu 22.04 部署开源免费具有精美现代web页面的Casdoor账号管理系统
linux·前端·ubuntu·开源·堡垒机
前端若水2 小时前
会话管理:创建、切换、删除对话历史
前端·人工智能·python·react.js
Bigger3 小时前
mini-cc:一个轻量级 AI 编程助手的诞生
前端·ai编程·claude
涵涵(互关)3 小时前
Naive-ui树型选择器只显示根节点
前端·ui·vue
BY组态3 小时前
Ricon组态系统最佳实践:从零开始构建物联网监控平台
前端·物联网·iot·web组态·组态
BY组态3 小时前
Ricon组态系统vs传统组态软件:为什么选择新一代Web组态平台
前端·物联网·iot·web组态·组态
SoaringHeart3 小时前
Flutter进阶:OverlayEntry 插入图层管理器 NOverlayZIndexManager
前端·flutter
放下华子我只抽RuiKe53 小时前
React 从入门到生产(四):自定义 Hook
前端·javascript·人工智能·深度学习·react.js·自然语言处理·前端框架