JS模擬滾動條(有demo和原始碼下載,支援拖動 滾輪 點選事件)

龍恩0707發表於2013-12-06

    由於遊覽器自帶的滾動條在美觀方面並不是很好看,所以很多設計師希望通過自己設計出來的滾動條來做這樣的效果,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下載

相關文章