js实现模拟滚动条

690 阅读1分钟

//2017-11-06

js模拟滚动条

//滚动条
function Scrollbar(options){
    var opts = $.extend({
        node : null,
        vertical : true,
        horizontal : true
    },options);
    this.node = opts.node;
    this.vertical = opts.vertical;
    this.horizontal = opts.horizontal;
    this.scrollState = true;
    this.interval = null;
};
Scrollbar.prototype = {
    constructor : Scrollbar,
    trigger : function(){
        var _this = this;
        if(!_this.node || !_this.node.length || (!_this.vertical && !_this.horizontal)){return}
        //创建滚动容器
        _this.createContainer();
        //创建滚动条
        _this.createVertical();
        _this.createHorizontal();
        //重置
        _this.handleReset();
        //绑定事件
        _this.bindEve();
    },
    //设置重置
    handleReset:function(){
        var _this = this;
        //初始化容器state
        _this.initState();
        //检测空间
        _this.checkVerticalSpace();
        _this.checkHorizontalSpace();
        //设置容器
        _this.setContainer();
        //设置垂直滚动条
        _this.setVScrollbar();
        //设置横向滚动条
        _this.setHScrollbar();
    },
    //创建容器
    createContainer:function(){
        var _this = this;
        if(_this.node.css('position') == 'static'){
            _this.node.css({
                position : 'relative'
            });
        }
        var $children = _this.node.children().clone(true);
        var style = "width:" + _this.node.width() + "px; height:" + _this.node.height() + "px;";
        _this.node.html("<div class='vo-scroll-container' style='" + style + "'></div>");
        _this.container = _this.node.find('.vo-scroll-container');
        _this.container.append($children);
    },
    //初始化state
    initState:function(){
        var _this = this;
        _this.state = {
            height: _this.container.height(),
            width: _this.container.width(),
            scrollTop: _this.container.scrollTop(),
            scrollLeft:_this.container.scrollLeft(),
            scrollHeight: _this.container.prop('scrollHeight'),
            scrollWidth: _this.container.prop('scrollWidth')
        };
    },
    //设置state
    setState:function(params,fn){
        var _this = this;
        var status = false;
        for(var key in params){
            if(typeof _this.state[key] == 'undefined' || params[key] == _this.state[key]){
                continue;
            }
            _this.state[key] = params[key];
            status = true;
        }
        if(typeof fn == 'function'){
            fn();
        }
    },
    //设置容器
    setContainer:function(){
        var _this = this;
        var style = {};
        style.width = _this.state.width + 'px';
        style.height = _this.state.height + 'px';
        _this.container.css(style);
    },
    //创建纵向
    createVertical:function(){
        var _this = this;
        if(!_this.vertical){return}
        _this.node.append(_this.getScrollHtml('vertical'));
        //设置纵向滚动条
        _this.vScrollbar = _this.node.find('.vertical');
        _this.vStartBtn = _this.vScrollbar.find('.start-button');
        _this.vEndBtn = _this.vScrollbar.find('.end-button');
        _this.vTrack = _this.vScrollbar.find('.track');
        _this.vThumb = _this.vScrollbar.find('.thumb');
        //绑定事件
        _this.bindVEve();
    },
    //创建横向
    createHorizontal:function(){
        var _this = this;
        if(!_this.horizontal){return}
        _this.node.append(_this.getScrollHtml('horizontal'));
        //设置横向滚动条
        _this.hScrollbar = _this.node.find('.horizontal');
        _this.hStartBtn = _this.hScrollbar.find('.start-button');
        _this.hEndBtn = _this.hScrollbar.find('.end-button');
        _this.hTrack = _this.hScrollbar.find('.track');
        _this.hThumb = _this.hScrollbar.find('.thumb');
        //绑定事件
        _this.bindHEve();
    },
    //获取滚动条
    getScrollHtml:function(type){
        var html = ["<div class='vo-scrollbar " + type + "'>"];
        html.push("<b class='start-button vo-null'>上按钮</b>");
        html.push("<div class='track'><b class='thumb vo-null'>滑块</b></div>");
        html.push("<b class='end-button vo-null'>下按钮</b>");
        html.push("</div>");
        return html.join('');
    },
    //检测是否展示纵向滚动条
    checkVerticalSpace:function(){
        var _this = this;
        if(!_this.vertical){return}
        //纵向
        if(!_this.vScrollbar.is(':visible') && _this.state.height < _this.state.scrollHeight){
            _this.vScrollbar.show();
            //更新state
            _this.setState({
                width: _this.state.width - _this.vScrollbar.width()
            });
        }else if(_this.vScrollbar.is(':visible') && _this.state.height >= _this.state.scrollHeight){
            _this.vScrollbar.hide();
            //更新state
            _this.setState({
                width: _this.state.width + _this.vScrollbar.width()
            });
        }
    },
    //检测是否展示横向滚动条
    checkHorizontalSpace:function(){
        var _this = this;
        if(!_this.horizontal){return}
        //横向
        if(!_this.hScrollbar.is(':visible') && _this.state.width < _this.state.scrollWidth){
            _this.hScrollbar.show();
            _this.setState({
                height: _this.state.height - _this.hScrollbar.height()
            });
        }else if(_this.hScrollbar.is(':visible') && _this.state.width >= _this.state.scrollWidth){
            _this.hScrollbar.hide();
            _this.setState({
                height: _this.state.height + _this.hScrollbar.height()
            });
        }
    },
    //设置纵向滚动条
    setVScrollbar:function(){
        var _this = this;
        if(!_this.vertical){return}
        //滚动条
        var startHeight = _this.vStartBtn.height(),
            endHeight = _this.vEndBtn.height(),
            trackHeight = _this.state.height - startHeight - endHeight,
            thumbHeight = trackHeight * _this.state.height / _this.state.scrollHeight,
            thumbTop = _this.state.scrollTop * trackHeight / _this.state.scrollHeight;
        //设置滚动条
        _this.vScrollbar.css({
            height:_this.state.height
        });
        //设置履带
        _this.vTrack.css({
            height:trackHeight + 'px'
        });
        //设置滑块
        _this.vThumb.css({
            top:Math.ceil(thumbTop) + 'px',
            height:Math.ceil(thumbHeight) + 'px'
        });
    },
    //设置垂直滑块
    setVThumb:function(){
        var _this = this;
        thumbTop = _this.state.scrollTop * _this.vTrack.height() / _this.state.scrollHeight;
        //设置滑块
        _this.vThumb.css({
            top:Math.ceil(thumbTop) + 'px'
        });
    },
    //设置横向滚动条
    setHScrollbar:function(){
        var _this = this;
        if(!_this.horizontal){return}
        //滚动条
        var startWidth = _this.hStartBtn.width(),
            endWidth = _this.hEndBtn.width(),
            trackWidth = _this.state.width - startWidth - endWidth,
            thumbWidth = trackWidth * _this.state.width / _this.state.scrollWidth,
            thumbLeft = _this.state.scrollLeft * trackWidth / _this.state.scrollWidth;
        //设置滚动条
        _this.hScrollbar.css({
            width:_this.state.width
        });
        //设置履带
        _this.hTrack.css({
            width:trackWidth + 'px'
        });
        //设置滑块
        _this.hThumb.css({
            left:Math.ceil(thumbLeft) + 'px',
            width:Math.ceil(thumbWidth) + 'px'
        });
    },
    //设置水平滑块
    setHThumb:function(){
        var _this = this;
        thumbLeft = _this.state.scrollLeft * _this.hTrack.width() / _this.state.scrollWidth;
        //设置滑块
        _this.hThumb.css({
            left:Math.ceil(thumbLeft) + 'px'
        });
    },
    //监听scroll事件
    listenerScroll:function(){
        var _this = this;
        return function(){
            if(!_this.scrollState){return}
            _this.scrollState = false;
            var $self = $(this);
            setTimeout(function(){
                _this.scrollState = true;
                //判断是否重置
                if(_this.state.scrollHeight != $self.prop('scrollHeight') || _this.state.scrollWidth != $self.prop('scrollWidth')){
                    _this.handleReset();
                    return;
                }
                //纵向
                if(_this.state.scrollTop != $self.prop('scrollTop')){
                    _this.setState({
                        scrollTop:$self.scrollTop()
                    },function(){
                        _this.setVScrollbar();
                    });
                    return;
                }
                //横向
                if(_this.state.scrollLeft != $self.prop('scrollLeft')){
                    _this.setState({
                        scrollLeft:$self.scrollLeft()
                    },function(){
                        _this.setHScrollbar();
                    });
                    return;
                }
            },20);
        }
    },
    //操作滚动条
    handleScroll:function(type,val,fn){
        var _this = this;
        var params = {};
        switch(type){
            case 'vertical':
                params.scrollTop = val;
                break;
            case 'horizontal':
                params.scrollLeft = val;
                break;
            default:
                params = null;
                break;
        }
        if(params == null){return}
        _this.container.stop(true).animate(params,20,function(){
            if(typeof fn == 'function'){
                fn();
            }
        });
    },
    //操作滑块
    handleThumb:function(type,val){
        var _this = this;
        switch(type){
            case 'vertical':
                var top = val * _this.state.scrollHeight / _this.vTrack.height();
                _this.container.scrollTop(Math.ceil(top));
                break;
            case 'horizontal':
                var left = val * _this.state.scrollWidth / _this.hTrack.width();
                _this.container.scrollLeft(Math.ceil(left));
                break;
        }
    },
    //停止计时器
    stopInterval:function(){
        var _this = this;
        if(_this.interval === null){return}
        clearInterval(_this.interval);
        _this.interval = null;
    },
    //绑定垂直滚动条事件
    bindVEve:function(){
        var _this = this;
        //上按钮
        _this.vStartBtn.on('mousedown',function(){
            _this.stopInterval();
            _this.interval = setInterval(function(){
                _this.handleScroll('vertical',_this.state.scrollTop - 10);
            },50);
            return false;
        });
        _this.vStartBtn.on('mouseup mouseleave',function(){
            _this.stopInterval();
            return false;
        });
        //下按钮
        _this.vEndBtn.on('mousedown',function(){
            _this.stopInterval();
            _this.interval = setInterval(function(){
                _this.handleScroll('vertical',_this.state.scrollTop + 10);
            },50);
            return false;
        });
        _this.vEndBtn.on('mouseup mouseleave',function(){
            _this.stopInterval();
            return false;
        });
        //拖动
        var top = null;
        _this.vThumb.vo_mouseDrag({
            moveCallback:function(info){
                top = top === null ? parseInt($(this).css('top')) : top;
                _this.handleThumb('vertical',info.offsetXY.y + top);
            },
            endCallback:function(){
                top = null;
            }
        });
        //滚轮
        var wheelState = true;
        _this.container.vo_mouseWheel(function(delta){
            if(!wheelState || _this.state.height >= _this.scrollHeight){return}
            wheelState = false;
            _this.handleScroll('vertical',_this.state.scrollTop + (delta > 0 ? -30 : 30),function(){
                wheelState = true;
            });
        });
    },
    //绑定横向滚动条事件
    bindHEve:function(){
        var _this = this;
        //左按钮
        _this.hStartBtn.on('mousedown',function(){
            _this.stopInterval();
            _this.interval = setInterval(function(){
                _this.handleScroll('horizontal',_this.state.scrollLeft - 10);
            },50);
            return false;
        });
        _this.hStartBtn.on('mouseup mouseleave',function(){
            _this.stopInterval();
            return false;
        });
        //右按钮
        _this.hEndBtn.on('mousedown',function(){
            _this.stopInterval();
            _this.interval = setInterval(function(){
                _this.handleScroll('horizontal',_this.state.scrollLeft + 10);
            },50);
            return false;
        });
        _this.hEndBtn.on('mouseup mouseleave',function(){
            _this.stopInterval();
            return false;
        });
        //拖动
        var left = null;
        _this.hThumb.vo_mouseDrag({
            moveCallback:function(info){
                left = left === null ? parseInt($(this).css('left')) : left;
                _this.handleThumb('horizontal',info.offsetXY.x + left);
            },
            endCallback:function(){
                left = null;
            }
        });
    },
    //绑定事件
    bindEve:function(){
        var _this = this;
        _this.container.on('scroll',_this.listenerScroll());
    }
}

//******针对jquery拓展******
$.fn.scrollbar = function(options){
    if(!$(this).length){return}
    var opts = $.extend({
        vertical : true,
        horizontal : true
    },options);
    //循环实例化
    $(this).each(function(){
        new Scrollbar({
            node : $(this),
            vertical : opts.vertical,
            horizontal : opts.horizontal
        }).trigger();
    });
};

sCss:

.vo-scroll-container{
    overflow: hidden;
}
.vo-scrollbar{
    $height:14px;
    $width:14px;
    position: absolute;
    width: $width;
    height: $height;
    overflow: hidden;
    display: none;
   
    cursor: pointer;
    .track{
        position: relative;
        background: #f4f4f4;
    }
    .thumb{
        position: absolute;
        background: #fff;
        border-radius: 2px;
        margin: 1px;
    }
    .start-button,
    .end-button{
        width:$width;
        height: $height;
         background:#f4f4f4 url(../../../image/virgo/scrollbar-icon.png) 0px 0px no-repeat;
    }
    
    &.vertical{
        top: 0;
        right: 0;
        height: 100%;
        .track{
            width: $width;
            .thumb{
                width:12px;
                box-shadow: 0px 2px 10px rgba(0,0,0,0.1);
            }
        }
        .start-button{
            background-position: 3px 5px;
            &:hover{
                background-position: -47px 5px;
            }
        }
        .end-button{
            background-position: 3px -45px;
            &:hover{
                background-position: -47px -45px;
            }
        }
    }
    &.horizontal{
        bottom: 0;
        left: 0;
        width: 100%;
        .track{
            float: left;
            height: $height;
            .thumb{
                height: 12px;
                box-shadow: 0px 2px 10px rgba(0,0,0,0.1);
            }
        }
        .start-button,
        .end-button{
            float: left;
        }
        .start-button{
            background-position: 3px -95px;
            &:hover{
                background-position: -47px -95px;
            }
        }
        .end-button{
            background-position: 3px -145px;
            &:hover{
                background-position: -47px -145px;
            }
        }
    }
}