代码改变世界

JS模拟滚动条(有demo和源码下载,支持拖动 滚轮 点击事件)

2013-12-06 23:45  龙恩0707  阅读(5013)  评论(0编辑  收藏  举报

    由于游览器自带的滚动条在美观方面并不是很好看,所以很多设计师希望通过自己设计出来的滚动条来做这样的效果,JS模拟滚动条其实很早看到jQuery有这样的插件或者KISSY有这样的组件,一直想着自己什么时候也来研究下这个神秘的东东,思来想去 做demo 但是也没有研究出来,一开始有2点不了解:一是:当前的滚动条的宽度(水平滚动)或者高度(垂直滚动)不知道怎么计算? 二是:水平滚动的距离 和 垂直滚动的距离 一次性到底滚动多少?也不知道怎样计算?所以一直研究不出来!直到最近看到一篇博客关于这方面的 所以才慢慢知道计算方法。

   要JS模拟滚动条需要知道以下知识点:

   1. 动态设置滚动条的宽度 scrollBarWidth = (容器的宽度 * 容器的宽度 / 内容的宽度)

       动态设置滚动条的高度 scrollBarHeight = (容器的高度 * 容器的高度 / 内容的高度

   2. 一次性到底要移动多少距离:计算方法如下:

       xy = (内容的宽度 - 容器的宽度) * 移动的距离 / (容器的宽度 - 滚动条的宽度) 或者

   xy = (内容的高度 - 容器的高度) * 移动的距离 / (容器的高度 - 滚动条的高度)

      其中移动的距离 可以配置的 默认是100

   3. 鼠标滚轮事件:判断是向上滚动(或者向左滚动)还是向下滚动(或者向右滚动)的游览器的兼容性。

       1. 包括IE6以内的 滚轮事件用 onmousewheel  而firefox一个人还在用DOMMouseScroll事件。而游览器判断是向上滚动还是向下滚动如下:

              1. 火狐游览器判断是向下 是通过event.detail这个属性判断 如果是-3的话 那么向下 如果是3的话 那么向上。

     2. 其他游览器是通过event.wheelDelta来判断 如果是-120的话 那么向下 否则120的话 是向上

       除火狐游览器以外  我们可以通过以下函数来测试下:

       document.body.onmousewheel = function(event) { event = event || window.event; console.log(event.wheelDelta); };

      火狐游览器可以通过下面这个函数来测试下:

      document.body.addEventListener("DOMMouseScroll", function(event) { console.log(event.detail); });

   4. 关于拖动事件: 用到了setCapture 这个东东,这个东东到底是什么意思呢?在这之前我也不知道js拖动事件还有一个这样的东东?经过google才了解到:鼠标捕获(setCapture)作用是将鼠标事件捕获到当前文档的指定的对象。这个对象会为当前应用程序或整个系统接收所有鼠标事件。不过setCapture不支持键盘事件, 只能捕获以下鼠标事件:onmousedown、onmouseup、onmousemove、onclick、ondblclick、onmouseover和onmouseout。

    可以通过这个方法object.releaseCapture() 来释放.类似于Jquery中的bind 和 unbind绑定事件一样。

  5. 首先我们可以根据滚轮事件的兼容性来写一个方法出来 如下代码:

/*
     * 对游览器滚轮事件作了兼容处理
     * 通过调用函数 判断 event.delta 是否大于还是小于0 判断是向上滚动还是向下滚动
     * win7 火狐游览器判断是向下 是通过event.detail这个属性判断 如果是-3的话 那么向下 或者如果是3的话 那么向上
     * win7 其他游览器是通过event.wheelDelta来判断 如果是-120的话 那么向下 否则120的话 是向上
     */
    _addEvent: function(){
        var EventProcess = function(event) {
            var type = event.type;
            if(type == 'DOMMouseScroll' || type == 'mousewheel') {
                 event.delta = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
            }
            if (event.srcElement && !event.target) {
                event.target = event.srcElement;    
            }
            if (!event.preventDefault && event.returnValue !== undefined) {
                event.preventDefault = function() {
                    event.returnValue = false;
                };
            }
            return event;
        }
        if(window.addEventListener) {
            return function(el,type,fn,capture) {
                if (type === "mousewheel" && document.mozHidden !== undefined) {
                    type = "DOMMouseScroll";
                }
                el.addEventListener(type, function(event) {
                    fn.call(this, EventProcess(event));
                }, capture || false);
            }
        }else if (window.attachEvent) {
            return function(el, type, fn, capture) {
                el.attachEvent("on" + type, function(event) {
                    event = event || window.event;
                    fn.call(el, EventProcess(event));    
                });
            }
        }   
}

 

然后上面的方法 我们可以如下这样调用 就可以判断滚动条是向下(或者向右) 还是 向上(或者向左)滚动了。

var wheelEvent = self._addEvent();
wheelEvent(container,'mousewheel',function(event){
    var wheelDelta = event.delta;
    if(wheelDelta < 0){
    //向下滚动
    }else {
    // 向上滚动
    }
});

下面来谈谈我这个JS模拟滚动条组件:

 1. 支持可配置水平滚动条和垂直滚动条。

 2. 支持页面上有多个滚动条 只要初始化一次就ok。

 3. 默认显示滚动条 也可以通过参数isHiddenScroll 为true 隐藏滚动条 当鼠标移上那块区域 再显示滚动条。

 4. 默认情况下需要配置如下几个参数:

      containerCls: 外层容器class类名

      contentCls:  内容区域class类名

      wrapScrollBarCls: 当前滚动条容器class类名

      scrollBarCls :  当前滚动条class类名

      sMoveDis: 鼠标单击击或滚动滚轮时,滚动条单次移动的距离

      isVertical: 是否是垂直滚动条 默认是横向滚动条 false

     isHiddenScroll: 默认情况下 是否隐藏滚动条 鼠标移上去显示 默认为显示 false

下面我们来看看效果到底是个什么样的 横向滚动条如下图:

 

众向滚动条如下:

页面HTML代码如下:

<!-- 横向滚动条 -->
    <div class="container">        
        <div class="cont">我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩</div>
        <div class="wrap-scroll-bar">
            <div class="scroll-bar"></div>
        </div>
    </div>
    <div class="container">        
        <div class="cont" style="width:1000px;">我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩</div>
        <div class="wrap-scroll-bar">
            <div class="scroll-bar"></div>
        </div>
    </div> 
    <!--  众向滚动条 -->
    <!-- <div class="container">        
        <div class="cont">我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩我是龙恩</div>
        <div class="wrap-scroll-bar">
            <div class="scroll-bar"></div>
        </div>
    </div>-->

CSS代码如下:

 <style>
    .container {
        position:relative;
        width:500px;
        height:200px;
        margin:50px auto 0;
        overflow: hidden;
     }
     .cont {
        background:#999;
        color: #fff;
        height:185px;
        position:absolute;
        top:0;
        left:0;
        width:2000px;
     }
     .wrap-scroll-bar {
        position:absolute;
        bottom:0;
        left:0;
        height:15px;
        background:#e6e6e6;
        width:100%;
        overflow:hidden;
     }
     .scroll-bar {
        position:absolute;
        bottom:0;
        height:15px;
        background:#ccc;
        left:0;
        width:20px;
     }

     /** 众向滚动条css
     .container {
        position:relative;
        width:500px;
        height:200px;
        margin:50px auto 0;
        overflow: hidden;
     }
     .cont {
        color: #fff;
        background:#999;
        width:485px;
        height:1000px;
        position:absolute;
        top:0;
        left:0;
     }
     .wrap-scroll-bar {
        position:absolute;
        right:0;
        top:0;
        width:15px;
        background:#e6e6e6;
        height:100%;
        overflow:hidden;
     }
     .scroll-bar {
        position:absolute;
        top:0;
        height:20px;
        background:#ccc;
        left:0;
        width:15px;
     } **/
     .hidden {display:none;}

JS代码如下:

 

/**
 * JS模拟滚动条
 * @date 2013-12-06
 * @email 879083421
 */

 function SimulateScroll(options) {
     
     this.config = {
         containerCls                :  '.container',                    // 外层容器
         contentCls                  :  '.cont',                         // 内容区域
         wrapScrollBarCls            : '.wrap-scroll-bar',               // 当前滚动条的容器
         scrollBarCls                :  '.scroll-bar',                   // 当前滚动条
         sMoveDis                    : 100,                              // 鼠标单击击或滚动滚轮时,滚动条单次移动的距离
         isVertical                  : false,                            // 是否是垂直滚动条 默认是横向滚动条
         isHiddenScroll              : false                              // 默认情况下是否隐藏滚动条 鼠标移上去显示 默认为显示

     };

     this.cache = {
        diX       :  0,
        diY       : 0
     };
     this.init(options);
 }
 
 SimulateScroll.prototype = {
    init: function(options) {

        this.config = $.extend(this.config,options || {});

        var self = this,
            _config = self.config,
            _cache = self.cache;

        /*
         * 判断是否是垂直或者横向滚动条
         * 分别对横向滚动条或者垂直滚动条初始化宽度或者高度
         */
        if(!_config.isVertical) {
            
            $(_config.containerCls).each(function(index,item) {

                var containerWidth = $(item).width(),
                    contentWidth = $(_config.contentCls,item).width();

                // 设置滚动条按钮的宽度 (容器的宽度 * 容器的宽度 / 内容的宽度)
                $(_config.scrollBarCls,item).width(containerWidth * containerWidth /contentWidth);
                var scrollBarWidth = $(_config.scrollBarCls,item).width();

                // 拖动滚动条事件
                self._dragScroll();

                // 滚动条单击事件
                self._clickScroll();
                
                // 鼠标滚轮事件
                self._initMouseWheel(item);
            });
            
            
        }else {
            $(_config.containerCls).each(function(index,item) {
                var containerHeight = $(item).height(),
                    contentHeight = $(_config.contentCls,item).height();
                
                // 设置滚动条按钮的高度 (容器的高度 × 容器的高度 / 内容的高度)
                $(_config.scrollBarCls,item).height(containerHeight * containerHeight /contentHeight);
                var scrollBarHeight = $(_config.scrollBarCls,item).height();
                
                // 拖动滚动条事件
                self._dragScroll();

                // 滚动条单击事件
                self._clickScroll();
                
                // 鼠标滚轮事件
                self._initMouseWheel(item);
            });
        }
        
        // 是否隐藏滚动条
        if(_config.isHiddenScroll) {
            $(_config.wrapScrollBarCls).each(function(index,item){
                !$(item).hasClass('hidden') && $(item).addClass('hidden');
            });
            $(_config.containerCls).each(function(index,item){
                $(item).hover(function(){
                    $(_config.wrapScrollBarCls,item).hasClass("hidden") && 
                    $(_config.wrapScrollBarCls,item).removeClass('hidden');
                },function(){
                    !$(_config.wrapScrollBarCls,item).hasClass("hidden") && 
                    $(_config.wrapScrollBarCls,item).addClass('hidden');
                })
            });
        }
    },
    
    /**
     * 拖动滚动条
     */
    _dragScroll: function() {
        var self = this,
            _config = self.config,
            _cache = self.cache;
        /**
         * 判断是否是垂直或者横向滚动条
         */
        if(!_config.isVertical) {
            $(_config.scrollBarCls).mousedown(function(e){
                _cache.diX = e.pageX - $(this).position().left;
                
                if(this.setCapture) {
                    $(this).mousemove(function(event) {
                        var tagParent = $(event.target).closest(_config.containerCls);
                        self._fnChangePos(event.pageX - _cache.diX,tagParent);
                    });
                    this.setCapture();  //设置捕获范围

                    $(this).mouseup(function() {
                        $(this).unbind('mousemove mouseup');
                        this.releaseCapture(); //取消捕获范围
                    });
                }else {
                     $(document).mousemove(function(event) {
                        var tagParent = $(event.target).closest(_config.containerCls);
                        self._fnChangePos(event.pageX - _cache.diX,tagParent);
                     });
                    $(document).mouseup(function(){
                        $(document).unbind('mousemove mouseup');
                    });
                }
                return false;
            });

        }else {
            $(_config.scrollBarCls).mousedown(function(e){
                _cache.diY = e.pageY - $(this).position().top;
                if(this.setCapture) {
                    $(this).mousemove(function(event){
                        var tagParent = $(event.target).closest(_config.containerCls);
                        self._fnChangePos(event.pageY - _cache.diY,tagParent);
                    });
                    this.setCapture();  //设置捕获范围

                    $(this).mouseup(function() {
                        $(this).unbind('mousemove mouseup');
                        this.releaseCapture(); //取消捕获范围
                    });
                }else {
                    $(document).mousemove(function(event) {
                        var tagParent = $(event.target).closest(_config.containerCls);
                        self._fnChangePos(event.pageY - _cache.diY,tagParent);
                     });
                    $(document).mouseup(function(){
                        $(document).unbind('mousemove mouseup');
                    });
                }
                return false;
            });
        }
    },
    /**
     * 内容移动距离
     * @param xy {string} 移动的距离
     * @param tagParent {object} 父节点
     * 移动距离的方法 (内容的宽度 - 容器的宽度) * 移动的距离 / (容器的宽度 - 滚动条的宽度)
     */
    _fnChangePos: function(xy,tagParent) {
        var self = this,
            _config = self.config;
        /**
         * 判断是否是垂直或者横向滚动条
         */
        if(!_config.isVertical) {
            if(xy < 0) {
                xy = 0;
            }else if(xy > $(tagParent).width() - $(_config.scrollBarCls,tagParent).width()) {

                xy = $(tagParent).width() - $(_config.scrollBarCls,tagParent).width();
            }
            $(_config.scrollBarCls,tagParent).css('left',xy);
            var left = ($(_config.contentCls,tagParent).width() - $(tagParent).width()) * xy /($(tagParent).width() - $(_config.scrollBarCls,tagParent).width());
            $(_config.contentCls,tagParent).css({'left':-left});
        }else {
            if(xy < 0) {
                xy = 0;
            }else if(xy > $(tagParent).height() - $(_config.scrollBarCls,tagParent).height()) {

                xy = $(tagParent).height() - $(_config.scrollBarCls,tagParent).height();
            }
            $(_config.scrollBarCls,tagParent).css('top',xy);
            var top = ($(_config.contentCls,tagParent).height() - $(tagParent).height()) * xy /($(tagParent).height() - $(_config.scrollBarCls,tagParent).height());
            $(_config.contentCls,tagParent).css({'top':-top});
        }
        
    },
    /**
     * 滚动条单击事件
     */
    _clickScroll: function() {
        var self = this,
            _config = self.config,
            _cache = self.cache;

        $(_config.wrapScrollBarCls).mousedown(function(e){
            /**
             * 判断是否是垂直或者横向滚动条
             */
            if(!_config.isVertical) {
                var tagParent = $(e.target).closest(_config.containerCls),
                    relDisX = e.pageX - $(this,tagParent).offset().left;
            
                /**
                 *  relDisX = 鼠标相对于文档的左边缘的位置(左边)- 目标左侧相对于文档的位置
                 *  $(_config.scrollBarCls,tagParent).position().left  指元素相对于父元素的偏移位置
                 *  $(_config.scrollBarCls,tagParent).width() 当前滚动条的宽度
                 */

                if (relDisX > ($(_config.scrollBarCls,tagParent).position().left + $(_config.scrollBarCls,tagParent).width())) {
                    if(_config.sMoveDis <= relDisX) {
                        self._fnChangePos($(_config.scrollBarCls,tagParent).position().left + _config.sMoveDis,tagParent);
                    }else {
                        //console.log('滚动条单次移动的距离过大 请设置小点');
                        self._fnChangePos($(_config.scrollBarCls,tagParent).position().left + relDisX,tagParent);
                    }

                } else if (relDisX < $(_config.scrollBarCls,tagParent).position().left) {
                    self._fnChangePos($(_config.scrollBarCls,tagParent).position().left - _config.sMoveDis,tagParent);
                };
            }else {
                var tagParent = $(e.target).closest(_config.containerCls),
                    relDisY = e.pageY - $(this,tagParent).offset().top;
            
                /**
                 *  relDisX = 鼠标相对于文档的左边缘的位置(左边)- 目标左侧相对于文档的位置
                 *  $(_config.scrollBarCls,tagParent).position().left  指元素相对于父元素的偏移位置
                 *  $(_config.scrollBarCls,tagParent).width() 当前滚动条的宽度
                 */

                if (relDisY > ($(_config.scrollBarCls,tagParent).position().top + $(_config.scrollBarCls,tagParent).height())) {
                    if(_config.sMoveDis <= relDisY) {
                        self._fnChangePos($(_config.scrollBarCls,tagParent).position().top + _config.sMoveDis,tagParent);
                    }else {
                        //console.log('滚动条单次移动的距离过大 请设置小点');
                        self._fnChangePos($(_config.scrollBarCls,tagParent).position().top + relDisY,tagParent);
                    }

                } else if (relDisY < $(_config.scrollBarCls,tagParent).position().top) {
                    self._fnChangePos($(_config.scrollBarCls,tagParent).position().top - _config.sMoveDis,tagParent);
                };
            }
            
        });
    },
    /**
     * 鼠标滚轮事件
     */
    _initMouseWheel: function(container) {
        var self = this,
            _config = self.config,
            _cache = self.cache;
        
        var wheelEvent = self._addEvent();
        wheelEvent(container,'mousewheel',function(event){
            var wheelDelta = event.delta;
            if(wheelDelta < 0){

                if(!_config.isVertical) {

                    //滚轮向下滚动
                    self._fnChangePos($(_config.scrollBarCls,container).position().left + _config.sMoveDis,container);
                }else {

                    //滚轮向下滚动
                    self._fnChangePos($(_config.scrollBarCls,container).position().top + _config.sMoveDis,container);
                }
                
            }else {

                if(!_config.isVertical) {
                    //向上滚动
                    self._fnChangePos($(_config.scrollBarCls,container).position().left -  _config.sMoveDis,container);
                }else {
                    //向上滚动
                    self._fnChangePos($(_config.scrollBarCls,container).position().top -  _config.sMoveDis,container);
                }
            }
        });
    },
    /*
     * 对游览器滚轮事件作了兼容处理
     * 通过调用函数 判断 event.delta 是否大于还是小于0 判断是向上滚动还是向下滚动
     * win7 火狐游览器判断是向下 是通过event.detail这个属性判断 如果是-3的话 那么向下 或者如果是3的话 那么向上
     * win7 其他游览器是通过event.wheelDelta来判断 如果是-120的话 那么向下 否则120的话 是向上
     */
    _addEvent: function(){
        var EventProcess = function(event) {
            var type = event.type;
            if(type == 'DOMMouseScroll' || type == 'mousewheel') {
                 event.delta = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
            }
            if (event.srcElement && !event.target) {
                event.target = event.srcElement;    
            }
            if (!event.preventDefault && event.returnValue !== undefined) {
                event.preventDefault = function() {
                    event.returnValue = false;
                };
            }
            return event;
        }
        if(window.addEventListener) {
            return function(el,type,fn,capture) {
                if (type === "mousewheel" && document.mozHidden !== undefined) {
                    type = "DOMMouseScroll";
                }
                el.addEventListener(type, function(event) {
                    fn.call(this, EventProcess(event));
                }, capture || false);
            }
        }else if (window.attachEvent) {
            return function(el, type, fn, capture) {
                el.attachEvent("on" + type, function(event) {
                    event = event || window.event;
                    fn.call(el, EventProcess(event));    
                });
            }
        }   
    }
 };

 // 代码初始化
 $(function(){
     new SimulateScroll({});
 });

JS模拟滚动条demo下载