50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | RangeSlider(范围滑块组件)

发布于:2025-08-02 ⋅ 阅读:(12) ⋅ 点赞:(0)

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

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

项目预览地址:https://50-vue-projects.vercel.app/在这里插入图片描述


使用 Vue 3<script setup> 语法结合 Tailwind CSS 来构建一个视觉效果出众的自定义范围滑块(Range Slider)。这个滑块的独特之处在于,它的数值标签会随着滑块的拖动而动态移动,并始终保持在滑块拇指(thumb)的正上方,提供极佳的用户体验。

让我们开始吧!🚀


📝 应用目标

  • 创建一个样式完全自定义的 HTML <input type="range"> 组件
  • 实现一个动态数值标签,该标签会跟随滑块拇指的位置移动
  • 确保标签始终位于滑块上方且居中对齐
  • 使用 Vue 3 的响应式系统和生命周期钩子管理复杂的状态和计算
  • 利用 Tailwind CSS 快速构建基础样式,并通过内联样式实现精确控制

🔧 技术实现点

技术点 描述
Vue 3 <script setup> 使用 ref, reactive, onMounted 管理状态、复杂对象和生命周期
v-model 双向绑定 将滑块的 valuesliderValue 响应式变量绑定
@input 事件监听 监听滑块值的变化
:style 动态绑定 使用 JavaScript 对象动态设置元素的 CSS 样式(特别是 left 位置)
getComputedStyle() 获取元素渲染后的实际宽度(包括 CSS 样式)
reactive 对象 存储需要响应式更新的复杂样式对象(labelStyle, sliderStyles
数学计算 (scale 函数) 实现平滑的标签位置偏移,避免标签在滑块两端被截断

🖌️ 组件实现

🎨 模板结构 <template>

<template>
    <div
        class="m-0 flex min-h-screen flex-col items-center justify-center overflow-hidden bg-gradient-to-br from-gray-100 to-gray-300 font-sans">
        <h2 class="absolute top-5 font-medium text-gray-700">Custom Range Slider</h2>

        <div class="relative mt-10">
            <!-- 自定义范围输入滑块 -->
            <input
                type="range"
                min="0"
                max="100"
                v-model="sliderValue"
                @input="handleSliderInput"
                class="relative z-10 h-3 w-[300px] cursor-pointer appearance-none rounded-md bg-purple-500 outline-none"
                :style="sliderStyles" />
            <!-- 动态数值标签 -->
            <label
                :style="labelStyle"
                class="absolute z-0 w-[80px] rounded-md bg-white py-1.5 text-center text-gray-700 shadow-md transition-all duration-300">
                {{ sliderValue }}
            </label>
        </div>
    </div>
</template>

模板结构简洁明了:

  1. 外层容器:一个全屏的 div,使用渐变背景 (from-gray-100 to-gray-300) 和 Flexbox 居中内容。
  2. 标题:固定在顶部的 h2 标签。
  3. 滑块容器:一个 relative 定位的 div,用于容纳滑块和标签。
  4. 滑块 (<input type="range">)
    • 使用 v-model 绑定 sliderValue
    • @input 事件触发 handleSliderInput 函数进行位置计算。
    • Tailwind 类设置基本样式:紫色背景 (bg-purple-500)、圆角 (rounded-md)、高度 (h-3)、宽度 (w-[300px])、手型光标 (cursor-pointer) 和移除默认轮廓 (outline-none)。
    • appearance-none 移除浏览器默认样式,relative z-10 确保它在标签之上。
    • :style="sliderStyles" 应用内联样式(主要是为了兼容性移除 appearance)。
  5. 标签 (<label>)
    • absolute 定位,z-0 确保它在滑块轨道之下(视觉上在滑块上方,因为滑块 z-10)。
    • :style="labelStyle" 动态绑定其 left 位置。
    • Tailwind 类设置标签样式:白色背景 (bg-white)、阴影 (shadow-md)、圆角、内边距 (py-1.5)、固定宽度 (w-[80px])、居中文本 (text-center) 和平滑过渡 (transition-all duration-300)。

💻 脚本逻辑 <script setup>


<script setup>
    import { ref, reactive, onMounted } from 'vue'

    // 响应式状态管理
    const sliderValue = ref(50)
    const labelStyle = reactive({
        left: '110px', // 初始位置(基于 50/100)
        top: '-30px', // 向上调整,显示在滑块上方
    })
    const sliderStyles = reactive({})

    // 初始化滑块样式 - 移除原生外观
    onMounted(() => {
        // 这些样式通常在CSS中设置,这里用:style是为了演示动态应用
        // 实际项目中建议写在<style>里
        sliderStyles.webkitAppearance = 'none'
        sliderStyles.mozAppearance = 'none'
        sliderStyles.appearance = 'none'
    })

    // 处理滑块输入事件 - 核心逻辑
    const handleSliderInput = (e) => {
        const value = +e.target.value // 当前滑块值
        const range = e.target // 滑块DOM元素

        // 获取滑块轨道和标签的渲染后宽度
        const rangeWidth = getComputedStyle(range).width
        const label = e.target.nextElementSibling // 获取<label>元素
        const labelWidth = getComputedStyle(label).width

        // 提取像素值(去掉'px'后缀)
        const numWidth = +rangeWidth.substring(0, rangeWidth.length - 2)
        const numLabelWidth = +labelWidth.substring(0, labelWidth.length - 2)

        const max = +range.max
        const min = +range.min

        // 核心计算:确定标签的left位置
        // 1. 计算value在0-max范围内的相对位置对应的像素
        // 2. 减去标签自身宽度的一半,使其水平居中于该位置
        // 3. 加上scale()函数返回的偏移量,实现两端防截断
        const left = value * (numWidth / max) - numLabelWidth / 2 + scale(value, min, max, 10, -10)

        // 更新响应式样式对象
        labelStyle.left = `${left}px`
        // 更新滑块值(虽然v-model已绑定,但此处显式更新以确保一致性)
        sliderValue.value = value
    }

    // 工具函数:将一个数值从一个范围线性映射到另一个范围
    // 用于在滑块两端为标签位置添加偏移,防止标签被裁剪
    const scale = (num, in_min, in_max, out_min, out_max) => {
        return ((num - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
    }
</script>

脚本部分是组件的核心:

  • 状态初始化sliderValue 默认为 50,labelStyle 初始化 lefttopsliderStyles 为空对象。
  • onMounted:组件挂载后,通过修改 sliderStyles 对象来应用 appearance: none 样式,彻底移除滑块的原生外观。(注意:在实际项目中,这些样式通常写在 <style> 标签内更合适,这里是为了演示 :style 的用法)
  • handleSliderInput 事件处理
    1. 获取当前滑块值 (value) 和滑块元素 (range)。
    2. 使用 getComputedStyle 获取滑块轨道 (rangeWidth) 和标签 (labelWidth) 的实际渲染宽度(像素值)。
    3. 将获取的字符串宽度(如 “300px”)转换为数字。
    4. 计算标签的 left 位置:
      • value * (numWidth / max):计算 value 在滑块总长度上对应的位置(像素)。
      • - numLabelWidth / 2:减去标签宽度的一半,使其左边缘位于计算位置的左侧,从而实现标签在该点的水平居中。
      • + scale(value, min, max, 10, -10):调用 scale 函数,当 value0100 时,返回的偏移量从 +10 线性变化到 -10。这使得标签在滑块最左端时右移 10px,在最右端时左移 10px,有效避免了标签在容器边界被截断的问题。
    5. 将计算出的 left 值(带 ‘px’ 单位)赋给 labelStyle.left,触发标签的重新渲染和位置更新。
    6. 更新 sliderValue(尽管 v-model 会自动处理,但显式更新更清晰)。
  • scale 函数:一个通用的线性插值函数,用于将输入值 num[in_min, in_max] 范围映射到 [out_min, out_max] 范围。

🎨 Tailwind CSS 样式重点

类名 作用
m-0 外边距为0
flex 启用 Flexbox 布局
min-h-screen 最小高度为视口高度
flex-col Flex 方向为垂直
items-center / justify-center 水平和垂直居中
overflow-hidden 隐藏溢出内容
bg-gradient-to-br from-gray-100 to-gray-300 从左上到右下的灰色渐变背景
font-sans 无衬线字体
absolute / relative 定位上下文和绝对定位
top-5 距离顶部 1.25rem
font-medium 中等粗细字体
text-gray-700 深灰色文字
mt-10 上外边距 2.5rem
h-3 高度 0.75rem
w-[300px] 宽度 300px (使用任意值语法)
cursor-pointer 手型光标
appearance-none 移除元素默认外观
rounded-md 中等圆角
bg-purple-500 紫色背景
outline-none 移除聚焦轮廓
z-10 / z-0 控制层叠顺序 (z-index)
w-[80px] 标签固定宽度 80px
py-1.5 垂直内边距 (0.375rem + 0.375rem)
text-center 文本居中
shadow-md 中等阴影
transition-all duration-300 所有属性变化在 300ms 内平滑过渡

📁 常量定义 + 组件路由

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 的响应式能力与原生 JavaScript 的 DOM 操作 (getComputedStyle),创建了一个功能完整且视觉上吸引人的自定义范围滑块。我们解决了动态元素定位的关键挑战,并通过 scale 函数实现了智能的边界处理。

这个自定义滑块组件有很大的扩展空间:

  • 样式美化:为滑块拇指(thumb)添加自定义样式(通常需要额外的 CSS 伪元素如 ::-webkit-slider-thumb)。
  • 步长 (step):添加 step 属性控制滑块的增量。
  • 多滑块:创建一个支持双滑块(范围选择)的组件。
  • 垂直滑块:修改样式和计算逻辑,创建垂直方向的滑块。
  • 键盘支持:添加键盘事件(如左右箭头键)来控制滑块。
  • 单位显示:在标签中添加单位(如 {{ sliderValue }}%)。

👉 下一篇,我们将完成NetflixMobileNavigation组件,一个模仿Netflix移动端的导航样式。🚀

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


网站公告

今日签到

点亮在社区的每一天
去签到