📅 我们继续 50 个小项目挑战!—— NetflixMobileNavigation组件

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

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

在这里插入图片描述


欢迎来到本篇教程!今天我们将使用 Vue 3<script setup> 语法结合 Tailwind CSS 来打造一个极具视觉冲击力的移动端侧边导航菜单。这个菜单的亮点在于其独特的三层嵌套滑动动画,当用户点击菜单按钮时,三个不同颜色的面板会依次从屏幕左侧滑入,营造出一种“层层展开”的动感效果,非常适合用于移动应用或响应式网站。

让我们开始吧!🚀


📝 应用目标

  • 创建一个响应式的移动端侧边导航菜单
  • 实现一个带有延迟的、分层的滑入/滑出动画效果
  • 使用 Vue 3 的响应式系统控制菜单的显示状态
  • 利用 Tailwind CSS 快速构建现代化的 UI 和基础动画
  • 通过 transformtransition 类实现复杂的动画序列

🔧 技术实现点

技术点 描述
Vue 3 <script setup> 使用 ref 定义响应式变量 isNavVisible
v-model / ref 状态管理 isNavVisible 控制菜单的打开和关闭
@click 事件监听 监听打开和关闭按钮的点击事件
:class 动态绑定 根据 isNavVisible 的值动态切换 CSS 类,控制面板的 transform 状态
transition-transform duration-300 ease-in-out 定义平滑的位移动画
delay-100 / delay-200 为内层面板添加动画延迟,实现错开的滑入效果
transform (CSS) 使用 translate-x 实现面板的位移(从 -100%0
fixed 定位 将菜单面板固定在视口,实现覆盖层效果
z-50 / z-index 控制打开按钮的层级,确保其始终可见

📚 常量和路由定义

本组件的核心是其分层动画逻辑。理解以下关键配置至关重要:

配置 值/说明
isNavVisible (响应式) 布尔值,true 表示菜单打开,false 表示菜单关闭
外层面板 w-[60%] (宽度占屏幕60%),bg-gray-900 (深灰色背景)
中层面板 w-[95%] (相对于外层面板95%宽),bg-red-600 (红色背景),delay-100 (延迟100ms动画)
内层面板 w-[95%] (相对于中层面板95%宽),bg-white (白色背景),delay-200 (延迟200ms动画)
动画持续时间 duration-300 (300毫秒)
动画缓动函数 ease-in-out (先慢后快再慢)

💡 提示:delay-100delay-200 是 Tailwind CSS 提供的实用类,分别代表 transition-delay: 100ms200ms。这使得内层面板的动画在触发后稍晚开始,从而形成错落有致的视觉效果。


🖌️ 组件实现

🎨 模板结构 <template>

<template>
    <div class="relative flex min-h-screen flex-col items-center justify-center font-sans">
        <!-- 打开按钮 -->
        <button
            class="fixed top-3 left-3 z-50 border-none bg-transparent text-2xl text-white"
            @click="isNavVisible = true">
            <i class="fas fa-bars"></i>
        </button>

        <!-- Logo和标题 -->
        <img
            src="https://images.ctfassets.net/4cd45et68cgf/7LrExJ6PAj6MSIPkDyCO86/542b1dfabbf3959908f69be546879952/Netflix-Brand-Logo.png?w=684&h=456"
            alt="Logo"
            class="h-auto w-40" />
        <p class="mt-4 text-sm text-gray-700 uppercase">Mobile Navigation</p>

        <!-- 导航菜单 (三层嵌套) -->
        <div
            :class="[
                'fixed top-0 left-0 h-full transform transition-transform duration-300 ease-in-out',
                isNavVisible ? 'translate-x-0' : '-translate-x-full',
            ]"
            class="w-[60%] max-w-[480px] min-w-[320px] bg-gray-900">
            <div
                :class="[
                    'h-full transform transition-transform duration-300 ease-in-out',
                    isNavVisible ? 'translate-x-0 delay-100' : '-translate-x-full',
                ]"
                class="w-[95%] bg-red-600">
                <div
                    :class="[
                        'h-full transform transition-transform duration-300 ease-in-out',
                        isNavVisible ? 'translate-x-0 delay-200' : '-translate-x-full',
                    ]"
                    class="relative w-[95%] bg-white p-10">
                    <!-- 关闭按钮 -->
                    <button
                        class="absolute top-10 right-8 border-none bg-transparent text-xl text-gray-400"
                        @click="isNavVisible = false">
                        <i class="fas fa-times"></i>
                    </button>

                    <!-- 导航Logo -->
                    <img
                        src="https://images.ctfassets.net/4cd45et68cgf/7LrExJ6PAj6MSIPkDyCO86/542b1dfabbf3959908f69be546879952/Netflix-Brand-Logo.png?w=684&h=456"
                        alt="Logo"
                        class="mb-8 h-auto w-32" />

                    <!-- 导航菜单 -->
                    <ul class="list-none p-0">
                        <li class="mb-5">
                            <a
                                href="#"
                                class="text-sm text-gray-900 uppercase no-underline transition-colors hover:text-red-600">
                                Teams
                            </a>
                        </li>
                        <li class="mb-5">
                            <a
                                href="#"
                                class="text-sm text-gray-900 uppercase no-underline transition-colors hover:text-red-600">
                                Locations
                            </a>
                        </li>
                        <li class="mb-5">
                            <a
                                href="#"
                                class="text-sm text-gray-900 uppercase no-underline transition-colors hover:text-red-600">
                                Life at Netflix
                            </a>
                        </li>
                        <li>
                            <ul class="mt-2 list-none pl-5">
                                <li class="mb-3">
                                    <a
                                        href="#"
                                        class="text-sm text-gray-900 uppercase no-underline transition-colors hover:text-red-600">
                                        Netflix culture memo
                                    </a>
                                </li>
                                <li class="mb-3">
                                    <a
                                        href="#"
                                        class="text-sm text-gray-900 uppercase no-underline transition-colors hover:text-red-600">
                                        Work life balance
                                    </a>
                                </li>
                                <li class="mb-3">
                                    <a
                                        href="#"
                                        class="text-sm text-gray-900 uppercase no-underline transition-colors hover:text-red-600">
                                        Inclusion & diversity
                                    </a>
                                </li>
                                <li class="mb-3">
                                    <a
                                        href="#"
                                        class="text-sm text-gray-900 uppercase no-underline transition-colors hover:text-red-600">
                                        Blog
                                    </a>
                                </li>
                            </ul>
                        </li>
                    </ul>
                </div>
            </div>
        </div>
    </div>
</template>

模板结构清晰地分为三个主要部分:

  1. 触发按钮

    • 一个固定在屏幕左上角 (fixed top-3 left-3) 的按钮,使用 Font Awesome 的 fa-bars 图标。
    • z-50 确保它在任何滑出的菜单面板之上。
    • 点击时 (@click) 将 isNavVisible 设置为 true,触发菜单展开。
  2. 主页面内容

    • 居中的 Netflix Logo 和一个标题 “Mobile Navigation”,用于展示菜单未展开时的主界面。
  3. 三层嵌套导航菜单

    • 外层 (div):最基础的容器,fixed 定位,w-[60%] 宽度,bg-gray-900 背景。其 :class 绑定根据 isNavVisible 决定是 translate-x-0 (完全显示) 还是 -translate-x-full (完全隐藏在屏幕外)。
    • 中层 (div):嵌套在外层之内,w-[95%] 宽度,bg-red-600 背景。它的动画有 delay-100,意味着当外层开始滑入后100ms,它才开始自己的滑入动画。
    • 内层 (div):最内层的面板,包含实际的导航内容。w-[95%] 宽度,bg-white 背景,p-10 内边距。它的动画有 delay-200,比中层再晚100ms开始。
    • 关闭按钮:位于内层面板右上角的 fa-times 图标,点击后将 isNavVisible 设置为 false,触发动画反向执行,菜单逐层收起。
    • 导航内容:一个包含主菜单项和子菜单项的无序列表 (ul),使用 hover:text-red-600 实现鼠标悬停时的文字颜色变化。

💻 脚本逻辑 <script setup>

<script setup>
    import { ref } from 'vue'

    // 响应式状态管理导航显示/隐藏
    const isNavVisible = ref(false)
</script>

脚本部分极其简洁,体现了 Vue 3 Composition API 的优雅:

  • isNavVisible:一个由 ref 创建的响应式布尔变量,初始值为 false,表示菜单默认是关闭的。
  • 这个变量被模板中的 :class 绑定和 @click 事件直接引用,实现了视图与状态的双向绑定。当状态改变时,Vue 会自动重新计算并应用相应的 CSS 类,从而触发动画。

🎨 Tailwind CSS 样式重点

类名 作用
relative / fixed 定位方式,fixed 使元素脱离文档流并相对于视口定位
top-0 / left-0 / top-3 / right-8 / top-10 精确控制元素位置
h-full 高度100%
w-[60%] / w-[95%] / w-40 / w-32 宽度设置(支持任意值)
max-w-[480px] / min-w-[320px] 最大/最小宽度限制
flex / flex-col Flexbox 布局
items-center / justify-center Flex 项目对齐
min-h-screen 最小高度为视口高度
font-sans 无衬线字体
border-none / bg-transparent 移除边框和背景
text-2xl / text-xl / text-sm / text-white / text-gray-700 / text-gray-900 / text-gray-400 / text-red-600 文字大小和颜色
uppercase 文字大写
z-50 层级最高
transform 启用 CSS 变换
transition-transform / transition-colors / transition-all 指定需要过渡的CSS属性
duration-300 过渡持续时间为300ms
ease-in-out 过渡缓动函数
delay-100 / delay-200 过渡延迟100ms/200ms
translate-x-0 / -translate-x-full X轴位移(0% / -100%)
bg-gray-900 / bg-red-600 / bg-white 背景颜色
p-10 / pl-5 / mb-5 / mb-3 / mb-8 / mt-2 内边距和外边距
list-none 移除列表默认样式(项目符号)
no-underline 移除下划线
hover:text-red-600 悬停时文字变红
absolute 绝对定位(用于关闭按钮)
h-auto 高度自适应

📁 常量定义 + 组件路由

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

{
        id: 45,
        title: 'NetflixMobileNavigation',
        image: 'https://50projects50days.com/img/projects-img/45-netflix-mobile-navigation.png',
        link: 'NetflixMobileNavigation',
    },

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

{
        path: '/NetflixMobileNavigation',
        name: 'NetflixMobileNavigation',
        component: () => import('@/projects/NetflixMobileNavigation.vue'),
    },

🏁 总结

在这篇教程中,我们成功创建了一个视觉效果惊艳的移动端侧边导航菜单。我们巧妙地利用了 Vue 3 的响应式系统和 Tailwind CSS 的 transformtransition 以及 delay 实用类,通过三层嵌套延迟动画,实现了富有层次感和动感的交互效果。

这个导航组件已经很吸引人,但还可以进一步增强:

  • 遮罩层:在菜单展开时,在菜单背后添加一个半透明的遮罩层 (<div class="fixed inset-0 bg-black bg-opacity-50"></div>),点击遮罩层也能关闭菜单。
  • 键盘支持:监听 Escape 键来关闭菜单。
  • 更复杂的动画:结合 scalerotate 变换,创建更炫酷的展开/收起效果。
  • 响应式优化:调整 w-[60%] 等宽度值,使其在不同设备上表现更佳。
  • 动态内容:将导航菜单数据 (<li> 元素) 提取到一个 refreactive 对象中,便于管理和国际化。

👉 下一篇,我们将完成QuizApp组件,一个适合问卷调查的问答组件。🚀

感谢阅读,欢迎点赞、收藏和分享 😊

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐