HTML JS CSS 实现一个两端可拖拽的范围选择控件

    最近有个需要 需要在网页上选择一个数值范围 网上找了下没找到合适的或许始终不会用baidu搜索。关键词换了不少怎么感觉搜索出来的结果都差不多呢,翻了十几页实在没信心翻下去了,算了自己动手吧!

XRangeBar.js

/* 
XRangeBar 范围选择控件 by zmz 2023-10-28
依赖 jquery sprintf 请在页面中引入相关JS

配置参数 opt : {
    elid        父容器
    min         最小值  默认值 0
    max         最大值  默认值 100
    tiptype     数值文字显示方式 
    orient      朝向 取值 'H' 或者 'V' 默认值 'H'
    barcss      滑轨css json 描述 如 { width:'100px', height:'100px' }    
    btncss      滑块css 同barcss
    tipcss      提示文字CSS 同barcss
}
*/

//加载CSS
let url = new URL( document.currentScript.src );
let cssurl = url.protocol + '//' + url.host + url.pathname.replace( '.js', '.css');
cssurl += "?" + Math.random();
url = null;
var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.href = cssurl;
link.rel = 'stylesheet';
link.type = 'text/css';
head.appendChild(link);

//数值文字显示方式
const XRANGEBAR_TIP_LR = 1; 
const XRANGEBAR_TIP_RL = 2;
const XRANGEBAR_TIP_TB = 3;
const XRANGEBAR_TIP_BT = 4;

const XRANGEBAR_DEFAULT_STYPE = {
    'H' : {
        'BAR' : {
            'width'         : '100%',
            'height'        : '10px',
            'top'           : '50%',
            'background'    : '#0000FF',
            'font-size'     : '1px',
            'transform'     : 'translate(0, -50%)',
            'border-radius' : '5px',    
        },
        'BTN' : {
            'width'         : '30px',
            'height'        : '30px',
            'border-radius' : '15px',
            'background'    : '#FF0000',
            'transform'     : 'translate(-50%, -50%)',
            'border'        : '2px solid #00f',
        },
        'TIP' : {
            'text-align'    : 'center',
            'width'         : '80px',
            'height'        : '30px',
            'line-height'   : '30px',
            'background'    : '#333',
            'color'         : '#fff',
            'font-size'     : '14px',
            'border-radius' : '4px',
        },
    },
    'V' : {
        'BAR' : {
            'width'         : '10px',
            'height'        : '100%',
            'background'    : '#0000FF',
            'font-size'     : '1px',
            'left'          : '50%',
            'transform'     : 'translate(-50%,0)',
            'border-radius' : '5px',    
        },
        'BTN' : {
            'width'         : '30px',
            'height'        : '30px',
            'border-radius' : '15px',
            'background'    : '#FF0000',
            'transform'     : 'translate(-50%, -50%)',
            'border'        : '2px solid #00f',
        },
        'TIP' : {
            'text-align'    : 'center',
            'width'         : '80',
            'height'        : '30px',
            'line-height'   : '30px',
            'background'    : '#333',
            'color'         : '#fff',
            'font-size'     : '14px',
            'border-radius' : '4px',
        }
    }
};

class XRangeBar {
    constructor( opt ) {
        this.Option = typeof( opt ) == 'undefined' ? {} : opt; 
        /* 默认值处理 Begin */
        this.Option.min = typeof( opt.min ) != 'undefined' ? opt.min : 0;
        this.Option.max = typeof( opt.max ) != 'undefined' ? opt.max : 100; 
        this.Option.format = typeof( opt.format ) != 'undefined' ? opt.format : '%.02f';
        this.Option.orient = typeof( opt.orient ) != 'undefined' ? opt.orient : 'H';  
        if( this.Option.orient == 'H' || this.Option.orient == 'h' ) {
            this.Option.tiptype = typeof( opt.tiptype ) != 'undefined' ? opt.tiptype : XRANGEBAR_TIP_TB;
            this.Option.barcss = Object.assign( {}, XRANGEBAR_DEFAULT_STYPE.H.BAR, typeof( opt.barcss ) != 'undefined' ? opt.barcss : {} );
            this.Option.btncss = Object.assign( {}, XRANGEBAR_DEFAULT_STYPE.H.BTN, typeof( opt.btncss ) != 'undefined' ? opt.btncss : {} );
            this.Option.tipcss = Object.assign( {}, XRANGEBAR_DEFAULT_STYPE.H.TIP, typeof( opt.tipcss ) != 'undefined' ? opt.tipcss : {} );
        } else {
            this.Option.tiptype = typeof( opt.tiptype ) != 'undefined' ? opt.tiptype : XRANGEBAR_TIP_LR;
            this.Option.barcss = Object.assign( {}, XRANGEBAR_DEFAULT_STYPE.V.BAR, typeof( opt.barcss ) != 'undefined' ? opt.barcss : {} );
            this.Option.btncss = Object.assign( {}, XRANGEBAR_DEFAULT_STYPE.V.BTN, typeof( opt.btncss ) != 'undefined' ? opt.btncss : {} );
            this.Option.tipcss = Object.assign( {}, XRANGEBAR_DEFAULT_STYPE.V.TIP, typeof( opt.tipcss ) != 'undefined' ? opt.tipcss : {} );
        }
        /* 默认值处理 End */

        this.MinVal = this.Option.min;
        this.MaxVal = this.Option.max;
        this.Container = $("#"+opt.elid);
        this.Touch = false;
        this.old_X = 0;
        this.old_Y = 0;
        this.deltaY = 0;
        this.deltaX = 0;

        this.CurBtn = null;
        this.BtnLess = null;
        this.BtnGreater = null;
        this.TipLess = null;
        this.TipGreater = null;

        window.addEventListener('touchstart', ( function( pThis ) {
            return function( e ) {
                if( $(e.target).closest('#'+pThis.Option.elid).length > 0 ) {
                    if( $(e.target).closest('.XRangeBar-btn-less').length > 0 ||
                        $(e.target).closest('.XRangeBar-tip-less').length > 0 ) {
                        e.preventDefault();
                        e.stopPropagation();
                        pThis.Touch = true;
                        pThis.old_Y = e.touches[0].clientY;
                        pThis.old_X = e.touches[0].clientX;
                        pThis.daltaY = 0;
                        pThis.daltaX = 0;
                        pThis.CurBtn = $(e.target).closest('.XRangeBar-btn-less');
                    }

                    if( $(e.target).closest('.XRangeBar-btn-greater').length > 0 ||
                        $(e.target).closest('.XRangeBar-tip-greater').length > 0 ) {
                        e.preventDefault();
                        e.stopPropagation();
                        pThis.Touch = true;
                        pThis.old_Y = e.touches[0].clientY;
                        pThis.old_X = e.touches[0].clientX;
                        pThis.daltaY = 0;
                        pThis.daltaX = 0;
                        pThis.CurBtn = $(e.target).closest('.XRangeBar-btn-greater');
                    }
                }
            };
        } )( this ), { passive: false } );
        //------------------------------------------------------------------------------------
        
        window.addEventListener('touchend', ( function( pThis ) {
            return function( e ) {
                if( $(e.target).closest('#'+pThis.Option.elid).length > 0 ) {
                    pThis.Touch = false;
                    pThis.CurBtn = null;
                }
            };
        } )( this ), { passive: false } );
        //------------------------------------------------------------------------------------

        window.addEventListener('touchmove', ( function( pThis ) {
            return function( e ) { 
                if( !pThis.Touch ) {
                    return;
                }
                e.preventDefault();
                e.stopPropagation();
                if( pThis.Option.orient == 'H' ) {
                    var npos, minpos, maxpos, val;
                    pThis.deltaX = e.touches[0].clientX - pThis.old_X;
                    npos = parseInt( pThis.CurBtn.css('left') ) + pThis.deltaX;
                    if( pThis.CurBtn.hasClass('XRangeBar-btn-less') ) {
                        pThis.BtnGreater.css({'z-index':10});
                        pThis.BtnLess.css({'z-index':11});
                        minpos = 0;
                        maxpos = parseFloat( pThis.BtnGreater.css('left') ) + parseFloat( pThis.BtnGreater.css('width') );
                        if( npos < minpos ) { npos = minpos; }
                        if( npos > maxpos ) { npos = maxpos; }
                        pThis.CurBtn.css('left', npos + 'px');
                        pThis.MinVal = npos * ( pThis.Option.max - pThis.Option.min ) / parseFloat( pThis.Container.css('width') ) + pThis.Option.min;
                    }
                    if( pThis.CurBtn.hasClass('XRangeBar-btn-greater') ) {
                        pThis.BtnLess.css({'z-index':10});
                        pThis.BtnGreater.css({'z-index':11});  
                        minpos = parseFloat( pThis.BtnLess.css('left') ) - parseFloat( pThis.BtnLess.css('width') );;
                        maxpos = parseFloat( pThis.Container.css('width') ) - parseFloat( pThis.BtnGreater.css('width') );;
                        if( npos < minpos ) { npos = minpos; }
                        if( npos > maxpos ) { npos = maxpos; }
                        pThis.CurBtn.css('left', npos + 'px');
                        pThis.MaxVal = ( npos + parseFloat( pThis.BtnGreater.css('width') ) ) * ( pThis.Option.max - pThis.Option.min ) / parseFloat( pThis.Container.css('width') ) + pThis.Option.min;
                    }
                    
                } else if ( pThis.Option.orient == 'V' ) {
                    var npos, minpos, maxpos, val;
                    pThis.deltaY = e.touches[0].clientY - pThis.old_Y;
                    npos = parseInt( pThis.CurBtn.css('top') ) + pThis.deltaY;
                    if( pThis.CurBtn.hasClass('XRangeBar-btn-less') ) {
                        pThis.BtnGreater.css({'z-index':10});
                        pThis.BtnLess.css({'z-index':11});
                        minpos = 0;
                        maxpos = parseFloat( pThis.BtnGreater.css('top') ) + parseFloat( pThis.BtnGreater.css('height') );
                        if( npos < minpos ) { npos = minpos; }
                        if( npos > maxpos ) { npos = maxpos; }
                        pThis.CurBtn.css('top', npos + 'px');
                        pThis.MinVal = npos * ( pThis.Option.max - pThis.Option.min ) / parseFloat( pThis.Container.css('height') ) + pThis.Option.min;
                    }
                    if( pThis.CurBtn.hasClass('XRangeBar-btn-greater') ) {
                        pThis.BtnLess.css({'z-index':10});
                        pThis.BtnGreater.css({'z-index':11});  
                        minpos = parseFloat( pThis.BtnLess.css('top') ) - parseFloat( pThis.BtnLess.css('height') );;
                        maxpos = parseFloat( pThis.Container.css('height') ) - parseFloat( pThis.BtnGreater.css('height') );;
                        if( npos < minpos ) { npos = minpos; }
                        if( npos > maxpos ) { npos = maxpos; }
                        pThis.CurBtn.css('top', npos + 'px');
                        pThis.MaxVal = ( npos + parseFloat( pThis.BtnGreater.css('height') ) ) * ( pThis.Option.max - pThis.Option.min ) / parseFloat( pThis.Container.css('height') ) + pThis.Option.min;
                    }
                }
                pThis.old_Y = e.touches[0].clientY;
                pThis.old_X = e.touches[0].clientX;
                pThis.TipLess.text( sprintf( pThis.Option.format, pThis.MinVal ) ); 
                pThis.TipGreater.text( sprintf( pThis.Option.format, pThis.MaxVal ) ); 
                pThis.Container.trigger( 'change', [pThis.MinVal,pThis.MaxVal] );
            };
        } )( this ), { passive: false } );
        //------------------------------------------------------------------------------------

        this.InitElement();
    }
    
    //初始化HTML元素
    InitElement() {
        var bar = $(`<div class="XRangeBar-bar"><div class="XRangeBar-btn XRangeBar-btn-less"><div class="XRangeBar-tip XRangeBar-tip-less">${sprintf(this.Option.format,this.MinVal)}</div></div><div class="XRangeBar-btn XRangeBar-btn-greater"><div class="XRangeBar-tip XRangeBar-tip-greater">${sprintf(this.Option.format,this.MaxVal)}</div></div></div>`);
        $('#'+this.Option.elid).append( bar );        
        this.BtnLess = bar.find('.XRangeBar-btn-less');
        this.BtnGreater = bar.find('.XRangeBar-btn-greater');
        this.TipLess = bar.find('.XRangeBar-tip-less');
        this.TipGreater = bar.find('.XRangeBar-tip-greater');
        if( typeof( this.Option.barcss ) != 'undefined' ) {
            bar.css( this.Option.barcss );
        }
        if( typeof( this.Option.btncss ) != 'undefined' ) {
            bar.find('.XRangeBar-btn-less, .XRangeBar-btn-greater').css( this.Option.btncss );
            if( this.Option.orient == 'H' || this.Option.orient == 'h' ) {
                var halfH = ( parseFloat( bar.find('.XRangeBar-btn-less').css('height') ) - parseFloat( bar.css('height') ) ) / 2;
                bar.find('.XRangeBar-btn-less').css({
                    left:'0px',
                    transform: `translate(-50%,-${halfH}px)`,
                });                    
                bar.find('.XRangeBar-btn-greater').css({
                    right:'0px',
                    transform: `translate(50%,-${halfH}px)`,
                });  
            }
            if( this.Option.orient == 'V' || this.Option.orient == 'v' ) {
                var halfW = ( parseFloat( bar.find('.XRangeBar-btn-less').css('width') ) - parseFloat( bar.css('width') ) ) / 2;
                bar.find('.XRangeBar-btn-less').css({
                    top:'0px',
                    transform: `translate(-${halfW}px,-50%)`,
                });                    
                bar.find('.XRangeBar-btn-greater').css({
                    bottom:'0px',
                    transform: `translate(-${halfW}px,50%)`,
                });  
            }
        }
        if( typeof( this.Option.tipcss ) != 'undefined' ) {
            bar.find('.XRangeBar-tip-less, .XRangeBar-tip-greater').css( this.Option.tipcss );
            var tw = parseInt( this.TipLess.width() );  
            var th = parseInt( this.TipLess.height() );  
            var bw = parseInt( this.BtnLess.css('width'));
            var bbw = parseInt( this.BtnLess.css('border-width'));
            var bh = parseInt( this.BtnLess.css('height'));
            var bgcolor = this.TipLess.css('background-color'); 
            var styleElement = document.createElement('style');
            styleElement.type = 'text/css';
            styleElement.appendChild( document.createTextNode(`#${this.Option.elid} .XRANGEBAR_TIP_L::before { border-left-color:${bgcolor}}`) );
            styleElement.appendChild( document.createTextNode(`#${this.Option.elid} .XRANGEBAR_TIP_R::before { border-right-color:${bgcolor}}`) );
            styleElement.appendChild( document.createTextNode(`#${this.Option.elid} .XRANGEBAR_TIP_T::before { border-top-color:${bgcolor}}`) );
            styleElement.appendChild( document.createTextNode(`#${this.Option.elid} .XRANGEBAR_TIP_B::before { border-bottom-color:${bgcolor}}`) );
            document.getElementsByTagName('head')[0].appendChild( styleElement );

            console.log( tw, th, bw, bh );
            if( this.Option.tiptype == XRANGEBAR_TIP_LR ) {
                this.TipLess.addClass( 'XRANGEBAR_TIP_L' );
                this.TipGreater.addClass( 'XRANGEBAR_TIP_R' );
                this.TipLess.css( {right: `${bw+5}px`} );
                this.TipLess.css( {top: `-${(th-bh)/2+bbw}px`} );
                this.TipGreater.css( {left: `${bw+5}px`} );
                this.TipGreater.css( {top: `-${(th-bh)/2+bbw}px`} );
            } else if( this.Option.tiptype == XRANGEBAR_TIP_RL ) {
                this.TipLess.addClass( 'XRANGEBAR_TIP_R' );
                this.TipGreater.addClass( 'XRANGEBAR_TIP_L' );
                this.TipGreater.css( {right: `${bw+5}px`} );
                this.TipGreater.css( {top: `-${(th-bh)/2+bbw}px`} );
                this.TipLess.css( {left: `${bw+5}px`} );
                this.TipLess.css( {top: `-${(th-bh)/2+bbw}px`} );
            } else if( this.Option.tiptype == XRANGEBAR_TIP_TB ) {
                this.TipLess.addClass( 'XRANGEBAR_TIP_T' );       
                this.TipGreater.addClass( 'XRANGEBAR_TIP_B' );
                this.TipLess.css( {top: `-${th+7+bbw}px`} );
                this.TipLess.css( {left: `-${(tw-bw)/2+bbw}px`} );
                this.TipGreater.css( {bottom: `-${th+7+bbw}px`} );
                this.TipGreater.css( {left: `-${(tw-bw)/2+bbw}px`} );
            } else if( this.Option.tiptype == XRANGEBAR_TIP_BT ) {
                this.TipGreater.addClass( 'XRANGEBAR_TIP_T' );       
                this.TipLess.addClass( 'XRANGEBAR_TIP_B' );
                this.TipGreater.css( {top: `-${th+7+bbw}px`} );
                this.TipGreater.css( {left: `-${(tw-bw)/2+bbw}px`} );
                this.TipLess.css( {bottom: `-${th+7+bbw}px`} );
                this.TipLess.css( {left: `-${(tw-bw)/2+bbw}px`} );
            }
        }
    }
}


XRangeBar.css

.XRangeBar-bar {
    box-sizing: border-box;
    position: absolute;
    background-color:#c5dcd3;
    -moz-user-select: none; 
    -webkit-user-select: none;
    -ms-user-select: none;
    -khtml-user-select: none; 
    user-select: none; 
}

.XRangeBar-btn-less, .XRangeBar-btn-greater {
    box-sizing: border-box;
    position:absolute;
}

.XRangeBar-tip-less, .XRangeBar-tip-greater {
    box-sizing: border-box;
    position: absolute;
}

.XRANGEBAR_TIP_L::before {
    right: 0px;
    content: "";
    position: absolute;
    display: block;
    width: 0;
    height: 0;
    top: 50%;
    transform: translate(100%, -50%);
    border-left: 5px solid rgba(0,0,0,0);
    border-top: 5px solid transparent;
    border-bottom: 5px solid transparent;
}

.XRANGEBAR_TIP_R::before {
    left: 0px;
    content: "";
    position: absolute;
    display: block;
    width: 0;
    height: 0;
    top: 50%;
    transform: translate(-100%, -50%);
    border-right: 5px solid rgba(0,0,0,0);
    border-top: 5px solid transparent;
    border-bottom: 5px solid transparent;
}

.XRANGEBAR_TIP_T::before {
    left: 50%;
    content: "";
    position: absolute;
    display: block;
    width: 0;
    height: 0;
    bottom: 0;
    transform: translate(-50%, 100%);
    border-top: 5px solid rgba(0,0,0,0);
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
}

.XRANGEBAR_TIP_B::before {
    left: 50%;
    content: "";
    position: absolute;
    display: block;
    width: 0;
    height: 0;
    top: 0;
    transform: translate(-50%, -100%);
    border-bottom: 5px solid rgba(0,0,0,0);
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
}

测试HTML

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>XRangeBar Test</title>
<style>
    html, body {
        width:100%;
        height:100%;
    }
</style>
<script type="text/javascript" src="/js/jq.js"></script>
<script type="text/javascript" src="/js/sprintf.js"></script>
<script type="text/javascript" src="/js/Component/XRangeBar.js"></script>
<script language="JavaScript">
    var barh, barv;
    $(document).ready(function() {
        barh = new XRangeBar( {
            elid : 'barH',
        } );

        barv = new XRangeBar( {
            elid : 'barV',
            min : 100,
            max : 211,
            orient : 'V',
        } );

        $("#barV").on('change', function( e, min, max ) {
            //范围变更通知
            console.log( min, max );
        } );
    });
</script>
</head>
<body>
    <div id="barH" style="background:#ccc;width:400px;height:100px;position:absolute; top:50px;left:50px;"></div>
    <div id="barV" style="background:#ccc;width:100px;height:400px;position:absolute; top:200px;left:300px;"></div>
</body>
</html>

看疗效

 改变外观只需要在new的时候给出不同的样式即可 如:

barh = new XRangeBar( {
    elid : 'barH',
    barcss : {
        'height'        : '4px',
        'border-radius' : '2px',
        'background'    : '#ccc',
    },
    btncss : {
        'width'         : '20px',
        'height'        : '20px',
        'border-radius' : '6px',
        'border'        : '2px solid #aaa',
        'background'    : '#ccc',
    },
    tipcss : {
        'color'         : 'blue',
        'background'    : '#ccc',
        'height'        : '40px',
        'line-height'   : '40px',
        'border-radius' : '20px',
    },
} );

 完整代码 GitHub - cnxbb/XRangeBar: HTML JS CSS 实现一个两端可拖拽的范围选择控件

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值