最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 如何实现一个可左右拉伸的滑块效果

    正文概述 掘金(rookie_fly)   2021-08-18   609

    前言

    别人家的效果

    如何实现一个可左右拉伸的滑块效果

    自家的效果 用的是 element ui的 滑块

    如何实现一个可左右拉伸的滑块效果

    果然还是别人家的效果,话说这个忙还是得帮,下面开始正题

    需求

    动手之前我们先捋一捋需求

    1. 滑块不能超出临界值(不能滑出轨道)

    如何实现一个可左右拉伸的滑块效果

    1. 手动进行向左、向右、滑块整体左右滑动

    2. 鼠标对滑块左按钮、右按钮、中间按钮 分别可以向左拉伸、向右拉伸、滑块整体左右滑动

    如何实现一个可左右拉伸的滑块效果

    如何实现一个可左右拉伸的滑块效果

    方案

    需求貌似不多,选什么样的方案来实现呢? C3的 transformtranslateX 还是 定位position呢?由于之前我对定位的拖拽比较熟悉,于是我选择使用 定位position来实现。

    具体思路:

    1. 创建一个容器container、容器下面创建一个滑轨track、滑轨羡慕创建一个滑块 slider、滑块下面分别创建 左边按钮、中间按钮、右边按钮
    2. 创建一个Slider类进行具体实现

    源码

    html部分

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>slider</title>
    </head>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        .container {
            width: 400px;
            margin: 50px auto;
        }
        .track {
            box-sizing: content-box;
            position: relative;
            background: #d1d1d1;
            height: 30px;
            line-height: 30px;
            font-size: 12px;
        }
        .track::before,
        .track::after {
            content: '';
            position: absolute;
            top: 0;
            height: 100%;
            width: 12px;
            background: #d1d1d1;
        }
        .track::before {
            left: -12px;
            border-radius: 2px 0 0 2px;
        }
        .track::after {
            right: -12px;
            border-radius: 0 2px 2px 0;
        }
        .slider {
            position: absolute;
            top: 0;
            height: 100%;
            background: rgba(66, 133, 244, 0.25);
            text-align: center;
            color: #fff;
            font-size: 12px;
            border-radius: 2px;
            z-index: 1;
            cursor: pointer;
        }
        .middle {
            width: inherit;
            position: absolute;
            top: 0;
            height: 100%;
            background: rgba(66, 133, 244, 0.25);
            text-align: center;
            color: #fff;
            font-size: 12px;
            border-radius: 2px;
            z-index: 1;
            cursor: pointer;
        }
        .left {
            position: absolute;
            top: 0;
            height: 100%;
            width: 12px;
            background: #4285f4;
            border: 0;
            padding: 0;
            cursor: ew-resize;
            left: -12px;
            border-radius: 2px 0 0 2px;
        }
        .right {
            position: absolute;
            top: 0;
            height: 100%;
            width: 12px;
            background: #4285f4;
            border: 0;
            padding: 0;
            cursor: ew-resize;
            outline: none;
            right: -12px;
            border-radius: 0 2px 2px 0;
            z-index: 10;
        }
        .right:not(:hover):not(:active) > span,
        .left:not(:hover):not(:active) > span {
            visibility: hidden;
            color: #000;
        }
        .right > span,
        .left > span {
            position: absolute;
            bottom: 33px;
            transform: translate(-50%);
            left: 50%;
            visibility: visible;
        }
        .right > span > span,
        .left > span > span {
            display: block;
            background: #4285f4;
            height: 20px;
            padding: 0 4px;
            border-radius: 2px;
            line-height: 20px;
        }
        .left::before,
        .left::after,
        .right::before,
        .right::after {
            content: '';
            position: absolute;
            width: 1px;
            height: 8px;
            background: #fff;
            margin: 1px;
            top: 10px;
            right: 3px;
        }
        .left::before,
        .right::before {
            left: 3px;
        }
    </style>
    <body>
        <!-- 容器 -->
        <div class="container">
            <!-- 轨道 -->
            <div class="track">
                <!-- 滑块 -->
                <div class="slider" style="left: 0px">
                    <!-- 中间按钮 -->
                    <div class="middle">0</div>
                    <!-- 左边按钮 -->
                    <div class="left">
                        <span>
                            <span style="margin-left: 30px" class="left-text">0</span>
                        </span>
                    </div>
                    <!-- 右边按钮 -->
                    <div class="right">
                        <span>
                            <span style="margin-left: 0px" class="right-text">0</span>
                        </span>
                    </div>
                </div>
            </div>
        </div>
    </body>
    </html>
    

    效果如图

    如何实现一个可左右拉伸的滑块效果

    下来我们来加上灵魂 js

    想怎么用比较合适?

    const slider = new Slider({
        slider_width: 50,// 滑块初始宽度
        track: "track", // 轨道class类名
        slider: "slider", // 滑块类名
        text: {
            left: "left-text", // 左边按钮文案class类名
            right: "right-text" // 右边按钮文案class类名
        },
        btn: {
            left: "left", // 左边按钮class类名
            right: "right",// 右边按钮class类名
            middle: "middle" //中间按钮class类名
        },
        range: {
            min: 50, // 滑块区间范围最小值
            max: 200 // 滑块区间范围最大值
        }
    });
    // 手动向右拉伸 滑块宽度至100
    //slider.rightStretch(100) 
    // 手动向左拉伸 滑块宽度至100
    //slider.leftStretch(100)
    // 手动移动滑块距离左边200
    //slider.move(200)
    

    想好了怎么用了,那么就是按怎么用进行逐步实现

    • 第一步,我们要把传入的参数转化为构造函数 Slider 内部可以用的值
    class Slider { 
        constructor(options) { 
            this._init(options)
        }
        _init(options){
             this.options = options // 传入的参数对象
            //
            this.init_width = options.slider_width // 初始滑块宽度
    
            options.range && options.range.min && (this.range_min = options.range.min) // 滑块宽度最小值
            options.range && options.range.max && (this.range_max = options.range.max) // 滑块宽度最大值
            //
            this.btn_left_slider = this._$(options.btn.left) // 滑块左边按钮类 - 用于向左边进行拉伸
            this.text_middle = this.btn_middle_slider = this._$(options.btn.middle) // 初始滑块宽度类  - 用于滑块整体左右移动  // 滑块中间按钮上面文案提示类
            this.btn_right_slider = this._$(options.btn.right) // 初始滑块宽度类  - 用于向右边边进行拉伸
            //
            this.track = this._$(options.track) // 滑块轨道类
            this.slider = this._$(options.slider) // 滑块轨道类
            //
            this.text_left = this._$(options.text.left) // 滑块左边按钮上面文案提示类
            this.text_right = this._$(options.text.right) // 滑块右边按钮上面文案提示类
            //
            this._initAttr() // 初始化属性值
            this._initevent() // 初始化事件
        }
    }
    

    第一步做了:同步用户传入参数于示例中、以及初始化滑块宽度、配置滑块的最大最小值、同步必须的dom、初始化属性值、初始化事件

    为什么需要一开始就执行初始化属性(this._initAttr())呢?是dom里面都是默认的 0,用户传入的值需要同步初始化到dom中。

    /**
     * @description: 初始化文案
     */
    _initAttr() {
        this.slider.style.width = this.options.slider_width + 'px'
        this.text_middle.innerText = this.options.slider_width
    }
    

    然后会初始化事件,主要用到的事件是 鼠标在 滑块元素 mousedown 事件,然后借助事件代理,来区分我们作用的事件源对象的class类名,最后根据作用类名不同来执行不同的逻辑,这个是整个实现的重点

    /**
     * @description: 初始化事件
     */
    _initevent() {
        this.slider.onmousedown = ({ target: { className }, clientX }) => {
            // 滑块宽度 动态值
            const sliderWidth = this.slider.offsetWidth
            // 滑块 距离左边 动态值
            const sliderLeft = this.slider.offsetLeft
    
            switch (className) {
                case this.options.btn.middle:
                    // 滑块 X 坐标计算
                    this.disX = clientX - this.slider.offsetLeft
                    // 滑块整体向右边移动 最大能移动的临界值
                    const valid_right = this.track.offsetWidth - sliderWidth
                    //
                    document.onmousemove = ({ clientX }) => {
                        this.slider.style.left =
                            clientX - this.disX < 0
                                ? 0 // 如果是滑块整体向左移动 超出临界值 就是临界值
                                : clientX - this.disX > valid_right
                                ? valid_right + 'px' // 如果是滑块整体向右移动 超出临界值 就是临界值
                                : clientX - this.disX + 'px'
                        this._syncText()
                    }
                    break
                case this.options.btn.left:
                    // 滑块 X 坐标计算
                    this.disX = clientX
                    // 滑块右边按钮 至 轨道 最左边变距离
                    const rightWidth = this.slider.getBoundingClientRect().right - this.track.getBoundingClientRect().left
                    //
                    document.onmousemove = ({ clientX }) => {
                        // 两点间的 x 差值 // 向左 一直移动,临界值是 0 超出边界 就会变成 负数  ,如果一直向右移动,超出右边临界值,右边临界值在移动到右边按钮时产生
                        const diff = this.disX - clientX
                        //  如果 一直向左移动 对应的 临界值
                        let valid_left = sliderWidth + diff
                        // 如果 一直向右移动 对应的 临界值
                        let valid_right = sliderLeft - diff
    
                        if (this.range_min && valid_left <= this.range_min) {
                            // 如果有设置最小范围值 并且 此时 向左拉伸超出最小值,那么此时的 值 就是最小值  range_min
                            valid_left = this.range_min
                            valid_right = rightWidth - valid_left
                        } else if (this.range_max && valid_left >= this.range_max) {
                            // 如果右设置最大范围值 并且此时移动 valid_left 超出 设置的最大范围值
                            valid_left = this.range_max >= rightWidth ? rightWidth : this.range_max // 如果设置的最大范围值 超出有效值 rightWidth,那么就设置 valid_left 为 有效值 rightWidth ,否则就是设置为最大范围值
                            valid_right = rightWidth - valid_left
                        } else if (valid_left >= rightWidth) {
                            // 如果一直向左边移动 , 超出 有效值 rightWidth,(抵死在左边情形)
                            valid_left = rightWidth
                            valid_right = 0
                        } else if (valid_right >= rightWidth) {
                            // 如果一直向右边移动 , 超出 有效值 rightWidth
                            valid_left = 0
                            valid_right = rightWidth
                        }
                        this.slider.style.left = valid_right + 'px'
                        this.slider.style.width = valid_left + 'px'
                        this.btn_right_slider.style.left = valid_left + 'px'
                        this._syncText()
                    }
                    break
                case this.options.btn.right:
                    // 滑块 X 坐标计算
                    this.disX = clientX - this.btn_right_slider.offsetLeft
                    //
                    document.onmousemove = ({ clientX }) => {
                        // track 轨道宽度
                        const track_width = this.track.offsetWidth
                        // 滑块左边可滑动距离
                        const valid_left = this.slider.offsetLeft
                        // 滑块整体向右边移动 最大能移动的临界值
                        const valid_right = this.range_max ? (this.range_max >= track_width - valid_left ? track_width - valid_left : this.range_max) : track_width - valid_left
                        // 两点x坐标差值
                        const diff = clientX - this.disX
                        // 滑块右边按钮的 left 值 设置
                        this.btn_right_slider.style.left = (diff <= (this.range_min || 0) ? this.range_min || 0 : diff >= valid_right ? valid_right : diff) + 'px'
                        // 滑块的宽度 width 设置
                        this.slider.style.width = this.btn_right_slider.offsetLeft + 'px'
                        // 文案同步
                        this._syncText()
                    }
                    break
            }
            document.onmouseup = () => {
                document.onmousemove = null
                document.onmouseup = null
            }
            return false
        }
    }
    

    其余部分方法实现没有_initevent事件复杂,分别是以下方法:

    • 获取dom _$
    • 同步文案 _syncText
    • 手动控制滑块右边按钮向右拉伸 rightStretch
    • 手动控制滑块左边按钮向左拉伸 leftStretch
    • 手动控制滑块整体左右滑动 move
    /**
     * @description: 获取dom
     * @param  { string } className
     */
    _$(className) {
        return document.querySelector(`.${className}`)
    }
    /**
     * @description: 同步文案信息
     */
    _syncText() {
        // 开始按钮距离 左边的距离
        const start = this.slider.offsetLeft
        // 结束按钮距离 左边的距离
        const end = this.slider.getBoundingClientRect().right - this.track.getBoundingClientRect().left
        // 容器的总宽度
        const total = this.track.offsetWidth
        // 刷新 左边按钮 tip 文案
        this.text_left.innerText = start
        // 刷新 右边按钮 tip 文案
        this.text_right.innerText = end
        // 刷新 内容按钮 tip 文案
        this.btn_middle_slider.innerText = end - start
    }
    /**
     * @description: 手动设置右边按钮拉伸到右边,
     * @param { Number } n:设置的值 范围 为: {0, rightMax:右边最大距离值}
     */
    rightStretch(n) {
        // 边界处理
        const rightMax = this.track.offsetWidth - this.slider.offsetLeft
        this.slider.style.width = this.btn_right_slider.style.left = (n >= rightMax ? rightMax : n) + 'px'
        this._syncText()
        return this
    }
    
    /**
     * @description: 手动设置左边按钮拉伸到左边,
     * @param { Number } n:设置的值 范围 为: { leftMax:左边最大距离值,0}
     */
    leftStretch(n) {
        // <左边最大距离值>
        const leftMax = this.slider.getBoundingClientRect().right - this.track.getBoundingClientRect().left
        // 如果左边按钮超出 最大距离左边边界值,则 就都设置成 <左边最大距离值>leftMax
        this.slider.style.width = this.btn_right_slider.style.left = (n >= leftMax ? leftMax : n) + 'px'
        // 边界处理
        this.slider.style.left = (n <= leftMax ? leftMax - n : 0) + 'px'
        this._syncText()
        return this
    }
    
    /**
     * @description: 手动设置滑块整体 向右 / 向右 移动
     * @param { Number } n :移动距离 {左边:0,右边:max }
     */
    move(n) {
        const max = this.track.offsetWidth - this.slider.offsetWidth
        this.slider.style.left = (n >= max ? max : n) + 'px'
        this._syncText()
        return this
    }
    

    最后附上完整代码,和效果

    class Slider {
        constructor(options) {
            this._init(options)
        }
        /**
            * @description: 初始化
            * @param {Object} options
            */
        _init(options) {
            this.options = options
            //
            this.init_width = options.slider_width || 0 // 初始滑块宽度
    
            options.range && options.range.min && (this.range_min = options.range.min) // 滑块宽度最小值
            options.range && options.range.max && (this.range_max = options.range.max) // 滑块宽度最大值
            //
            this.btn_left_slider = this._$(options.btn.left) // 滑块左边按钮类 - 用于向左边进行拉伸
            this.text_middle = this.btn_middle_slider = this._$(options.btn.middle) // 初始滑块宽度类  - 用于滑块整体左右移动  // 滑块中间按钮上面文案提示类
            this.btn_right_slider = this._$(options.btn.right) // 初始滑块宽度类  - 用于向右边边进行拉伸
            //
            this.track = this._$(options.track) // 滑块轨道类
            this.slider = this._$(options.slider) // 滑块轨道类
            //
            this.text_left = this._$(options.text.left) // 滑块左边按钮上面文案提示类
            this.text_right = this._$(options.text.right) // 滑块右边按钮上面文案提示类
            //
            this._initAttr() // 初始化属性值
            this._initEvent() // 初始化事件
        }
        /**
            * @description: 获取dom
            * @param  { string } className
            */
        _$(className) {
            return document.querySelector(`.${className}`)
        }
        /**
            * @description: 初始化文案
            */
        _initAttr() {
            this.slider.style.width = this.options.slider_width || 0 + 'px'
            this.text_middle.innerText = this.options.slider_width || 0
        }
       /**
     * @description: 初始化事件
     */
    _initevent() {
        this.slider.onmousedown = ({ target: { className }, clientX }) => {
            // 滑块宽度 动态值
            const sliderWidth = this.slider.offsetWidth
            // 滑块 距离左边 动态值
            const sliderLeft = this.slider.offsetLeft
    
            switch (className) {
                case this.options.btn.middle:
                    // 滑块 X 坐标计算
                    this.disX = clientX - this.slider.offsetLeft
                    // 滑块整体向右边移动 最大能移动的临界值
                    const valid_right = this.track.offsetWidth - sliderWidth
                    //
                    document.onmousemove = ({ clientX }) => {
                        this.slider.style.left =
                            clientX - this.disX < 0
                                ? 0 // 如果是滑块整体向左移动 超出临界值 就是临界值
                                : clientX - this.disX > valid_right
                                ? valid_right + 'px' // 如果是滑块整体向右移动 超出临界值 就是临界值
                                : clientX - this.disX + 'px'
                        this._syncText()
                    }
                    break
                case this.options.btn.left:
                    // 滑块 X 坐标计算
                    this.disX = clientX
                    // 滑块右边按钮 至 轨道 最左边变距离
                    const rightWidth = this.slider.getBoundingClientRect().right - this.track.getBoundingClientRect().left
                    //
                    document.onmousemove = ({ clientX }) => {
                        // 两点间的 x 差值 // 向左 一直移动,临界值是 0 超出边界 就会变成 负数  ,如果一直向右移动,超出右边临界值,右边临界值在移动到右边按钮时产生
                        const diff = this.disX - clientX
                        //  如果 一直向左移动 对应的 临界值
                        let valid_left = sliderWidth + diff
                        // 如果 一直向右移动 对应的 临界值
                        let valid_right = sliderLeft - diff
    
                        if (this.range_min && valid_left <= this.range_min) {
                            // 如果有设置最小范围值 并且 此时 向左拉伸超出最小值,那么此时的 值 就是最小值  range_min
                            valid_left = this.range_min
                            valid_right = rightWidth - valid_left
                        } else if (this.range_max && valid_left >= this.range_max) {
                            // 如果右设置最大范围值 并且此时移动 valid_left 超出 设置的最大范围值
                            valid_left = this.range_max >= rightWidth ? rightWidth : this.range_max // 如果设置的最大范围值 超出有效值 rightWidth,那么就设置 valid_left 为 有效值 rightWidth ,否则就是设置为最大范围值
                            valid_right = rightWidth - valid_left
                        } else if (valid_left >= rightWidth) {
                            // 如果一直向左边移动 , 超出 有效值 rightWidth,(抵死在左边情形)
                            valid_left = rightWidth
                            valid_right = 0
                        } else if (valid_right >= rightWidth) {
                            // 如果一直向右边移动 , 超出 有效值 rightWidth
                            valid_left = 0
                            valid_right = rightWidth
                        }
                        this.slider.style.left = valid_right + 'px'
                        this.slider.style.width = valid_left + 'px'
                        this.btn_right_slider.style.left = valid_left + 'px'
                        this._syncText()
                    }
                    break
                case this.options.btn.right:
                    // 滑块 X 坐标计算
                    this.disX = clientX - this.btn_right_slider.offsetLeft
                    //
                    document.onmousemove = ({ clientX }) => {
                        // track 轨道宽度
                        const track_width = this.track.offsetWidth
                        // 滑块左边可滑动距离
                        const valid_left = this.slider.offsetLeft
                        // 滑块整体向右边移动 最大能移动的临界值
                        const valid_right = this.range_max ? (this.range_max >= track_width - valid_left ? track_width - valid_left : this.range_max) : track_width - valid_left
                        // 两点x坐标差值
                        const diff = clientX - this.disX
                        // 滑块右边按钮的 left 值 设置
                        this.btn_right_slider.style.left = (diff <= (this.range_min || 0) ? this.range_min || 0 : diff >= valid_right ? valid_right : diff) + 'px'
                        // 滑块的宽度 width 设置
                        this.slider.style.width = this.btn_right_slider.offsetLeft + 'px'
                        // 文案同步
                        this._syncText()
                    }
                    break
            }
            document.onmouseup = () => {
                document.onmousemove = null
                document.onmouseup = null
            }
            return false
        }
    }
        /**
            * @description: 同步文案信息
            */
        _syncText() {
            // 开始按钮距离 左边的距离
            const start = this.slider.offsetLeft
            // 结束按钮距离 左边的距离
            const end = this.slider.getBoundingClientRect().right - this.track.getBoundingClientRect().left
            // 容器的总宽度
            const total = this.track.offsetWidth
            // 刷新 左边按钮 tip 文案
            this.text_left.innerText = start
            // 刷新 右边按钮 tip 文案
            this.text_right.innerText = end
            // 刷新 内容按钮 tip 文案
            this.btn_middle_slider.innerText = end - start
        }
        /**
            * @description: 手动设置右边按钮拉伸到右边,
            * @param { Number } n:设置的值 范围 为: {0, rightMax:右边最大距离值}
            */
        rightStretch(n) {
            // 边界处理
            const rightMax = this.track.offsetWidth - this.slider.offsetLeft
            this.slider.style.width = this.btn_right_slider.style.left = (n >= rightMax ? rightMax : n) + 'px'
            this._syncText()
            return this
        }
    
        /**
            * @description: 手动设置左边按钮拉伸到左边,
            * @param { Number } n:设置的值 范围 为: <leftMax> <左边最大距离值,0>
            */
        leftStretch(n) {
            // <左边最大距离值>
            const leftMax = this.slider.getBoundingClientRect().right - this.track.getBoundingClientRect().left
            // 如果左边按钮超出 最大距离左边边界值,则 就都设置成 <左边最大距离值>leftMax
            this.slider.style.width = this.btn_right_slider.style.left = (n >= leftMax ? leftMax : n) + 'px'
            // 边界处理
            this.slider.style.left = (n <= leftMax ? leftMax - n : 0) + 'px'
            this._syncText()
            return this
        }
    
        /**
            * @description: 手动设置滑块整体 向右 / 向右 移动
            * @param { Number } n :移动距离 <左边:0> <右边:max >
            */
        move(n) {
            const max = this.track.offsetWidth - this.slider.offsetWidth
            this.slider.style.left = (n >= max ? max : n) + 'px'
            this._syncText()
            return this
        }
    }
    

    如何实现一个可左右拉伸的滑块效果

    codesandbox查看

    后言

    后面我朋友反馈,借助我写的这个效果实现了需求并上线,后续未收到问题,对于把这案例分享出来,一是作者我没有搜索到相关类似案例,(或许github应该有只是我没有找到而已),二是我向通过分享这个效果实现过程,来突出我们在项目效果实现中,有必要注重细节,该效果中,边界值的计算参照值得思考,同时也希望大家一起完善该效果,共同进步。(技术分享的初衷),重要的一点提升自己的写作水平(狗头笑?)。

    结语

    如果你有更好的点子或是疑问,欢迎留言

    文中若有不准确或错误的地方,欢迎指出


    起源地下载网 » 如何实现一个可左右拉伸的滑块效果

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元