分享一则JavaScript滚动条插件源码

这是过年的时候自己写的js滚动条插件的源码,做出的效果自己并不满意,正因为做的并不满意所以回头重新巩固和深入学习js,这个插件有如下几个不太满意的地方:

内容的过度效果,可以参阅QQ客户端最近会话列表里的滚动条,它的滚动非常的平滑,简单的说就是缺少动画过渡效果。

并不算完美的兼容性,在IE6、7下的style仍然有点缺憾。

样式的不完美,例如鼠标悬浮才显示滚动条,移除后隐藏这种效果都没有写。

内部结构的混乱,需要调整内容结构。

滚动条那个图片毕竟不是美工,自己切图切的真是恶心到爆了...囧

总体来说还是可以看的,还是缺少一个动画。在写这个插件意识到自己的插件用到了一些比较基础的函数,于是想到把这些函数应该封装起来,最近仍然在深入学习js,把手头上这本书看完就应该着手写这个基础函数的插件了,当然,动画引擎必不可少。话不多说,源码在此(注意:本插件完整版的是有图片的,请在文末附件中下载完整的插件):

CSS

复制代码 代码如下:


.lf_Scroll, .lf_Scroll li { padding: 0; margin: 0; list-style: none; font: 14px/24px "Helvetica Neue" ,Helvetica,Arial, 'Microsoft Yahei' ,sans-serif; outline: none; }
.lf_Scroll { cursor: pointer; width: 10px; position: absolute; right: 0; top: 0; filter: alpha(opacity=50); -moz-opacity: 0.5; -khtml-opacity: 0.5; opacity: 0.5; }
.lf_ScrollFocus { filter: alpha(opacity=100); -moz-opacity: 1; -khtml-opacity: 1; opacity: 1; }
.lfs_Top, .lfs_Center, .lfs_Bottom { background: url('ScrollBar.gif'); width: 10px; height: 10px; }
.lfs_Top { background-position: 1px 0px; }
.lfs_Center { background-position: center 0; height: 100px; }
.lfs_Bottom { background-position: -22px 0; }
/*Developers config*/
.rollDiv { height: 100%; width: 100%; overflow: hidden; position: relative; }

JavaScript

复制代码 代码如下:


/*
* This plugin is defined on the simulation Webpage scroll bar, please insert after binding for DOM events
*
* Comment version: 1.0.0
* Author:linkfly
* Sina:为你聚焦半世纪 |  cnblogs: | Email:linkFly6@live.com
* date:2014-02-05 02:38:35
*
*
* Dual licensed under the MIT and GPL licenses:
*
*
*
*/
(function (window, undefined) {
    //配置参数信息
    var config = {
        auto: true,
        height: 'auto',
        width: 'auto'
    };
    var linkFlyScroll = function (dom, options) {
        /// <summary>
        ///     1: 生成模拟滚动条对象,【请在本对象工作之后再为您指定的对象绑定事件,否则您之前绑定的事件将不会进行工作】
        ///     &#10;    1.1 - linkFlyScroll(dom) - 在指定的dom上生成滚动条对象
        ///     &#10;    1.2 - linkFlyScroll(dom,options) - 生成滚动条对象,同时提供一系列的参数允许您自定义配置该对象的工作模型
        /// </summary>
        /// <param type="String Or element">
        ///     传入js的dom对象,或者为string类型的该对象ID
        /// </param>
        /// <param type="Json">
        ///     自定义配置该对象的工作模型,有如下选项:
        ///     &#10;  [可选]auto(Boolean):当内容并未达到容器的高度的时候,是否自动隐藏滚动条,默认为true(是)
        ///     &#10;  [可选]height(Int Or String):默认单位为px,可以为int和String.值为auto则默认采用css的高度
        ///     &#10;  [可选]width(Int Or String):默认单位为px,可以为int和String.值为auto则默认采用css的宽度
        /// </param>
        /// <returns type="linkFlyScroll" />
        if (typeof (dom) === 'string') {
            dom = document.getElementById(dom);
        }
        //没有指定dom和没有查找到有效的dom
        //linkFlyScroll("")、 linkFlyScroll(null)、linkFlyScroll(undefined)
        if (!dom || !dom.nodeType)
            return this;
        //创建容器对象
        var scrollObj = document.createElement('div');
        //深度克隆内容对象,并未包含事件,所以需要等到linkFlyScroll对象工作完毕后才可以为该dom对象绑定事件
        var cloneObj = dom.cloneNode(true);
        scrollObj.className = 'rollDiv';
        scrollObj.appendChild(cloneObj);
        //替换页面上当前对象
        dom.parentNode.replaceChild(scrollObj, dom);
        return new linkFlyScroll.prototype.init(scrollObj, options ? options : {});
    };
    linkFlyScroll.prototype.init = function (dom, options) {
        /// <summary>
        ///     1: 本对象才是真正意义上工作的对象,特殊的工作方式是因为可能存在linkFlyScroll的静态调用和实例化调用
        ///     &#10;    1.1 - init(dom,options) - 在指定的dom上生成滚动条对象
        /// </summary>
        /// <param type="element">
        ///     dom对象
        /// </param>
        /// <param type="Json">
        ///     自定义配置该对象的工作模型,有如下选项:
        ///     &#10;  [可选]auto(Boolean):当内容并未达到容器的高度的时候,是否自动隐藏滚动条,默认为true(是)
        ///     &#10;  [可选]height(Int Or String):默认单位为px,可以为int和String.值为auto则默认采用css的高度
        ///     &#10;  [可选]width(Int Or String):默认单位为px,可以为int和String.值为auto则默认采用css的宽度
        /// </param>
        /// <returns type="linkFlyScroll" />
        /*
        * 本对象包含以下属性:
        * isDrag:是否正在拖拽滚动条
        * startTop:(工作中)滚动条开始滚动位置
        * endTop:(工作中)滚动条结束滚动位置
        * topLimit:滚动条顶部极限位置
        * bottomLimit:滚动条底部极限位置
        * context:内容Dom
        * scrollRadix:滚动基数
        * target:容器Dom
        */
        //当前this对象,为防止this指针在环境中会经常改变(例如绑定事件的时候),所以将当前对象保存起来
        var currScroll = this;
        //DOMElement
        if (dom.nodeType) {
            //保存容器和内容DOM
            currScroll.target = dom;
            currScroll.context = dom.firstChild;
            //合并配置参数
            currScroll.options = tool.extend(config, options);
            if (currScroll.options.width !== 'auto') {
                dom.style.width = tool.convertValue(currScroll.options.width);
            }
            if (currScroll.options.height !== 'auto') {
                dom.style.height = tool.convertValue(currScroll.options.height);
            }
            //查找到有效的dom
            while (currScroll.context.nodeType != 1) {
                currScroll.context = currScroll.context.nextSibling;
            }
            //创建滚动条dom
            currScroll.scrollUl = document.createElement('ul');
            currScroll.scrollUl.className = 'lf_Scroll';
            currScroll.scrollUl.appendChild(tool.setClass('li', 'lfs_Top'));
            currScroll.scrollUl.appendChild(tool.setClass('li', 'lfs_Center'));
            currScroll.scrollUl.appendChild(tool.setClass('li', 'lfs_Bottom'));
            currScroll.context.style.position = 'relative';
            //先呈现在页面上才可以读取位置数据
            dom.appendChild(currScroll.scrollUl);
            this.change();
            tool.addScrollEvent(currScroll.context, function (e) {
                //绑定鼠标滚轮事件,3px滚动单位
                if (e.wheel > 0) {//滚轮向上滚动
                    var currTop = currScroll.endTop -= 3;
                    currScroll.scrollEvent.call(currScroll, currTop);
                } else {//滚轮向下滚动
                    var currTop = currScroll.endTop += 3;
                    currScroll.scrollEvent.call(currScroll, currTop);
                }
            });
            //需要处理禁止文字在拖动的时候被选中  TODO
            //鼠标点下事件,需要判断是否是左键点击,目前右键也会实现滚动  TODO
            tool.addEvent(currScroll.scrollUl, 'mousedown', function (e) {
                mouseDown.call(currScroll, e);
            });
            //追加事件,为了更好的用户体验在body上实现监听
            tool.addEvent(document.body, 'mousemove', function (e) {
                if (currScroll.isDrag) {
                    //获取当前鼠标位置
                    var position = tool.getMousePos(e);
                    //当前滚动条top位置
                    var currTop = (currScroll.endTop + position.y - currScroll.startTop);
                    //call是为了让this指针准确的指向本工作对象
                    currScroll.scrollEvent.call(currScroll, currTop);
                }
                return false;
            });
            //追加鼠标释放事件,为了准确的捕捉到释放事件在body上监听
            tool.addEvent(document.body, 'mouseup', function () {
                mouseUp.call(currScroll, []);
            });
            var mouseDown = function (e) {
                /// <summary>
                ///     1: 鼠标按下事件
                ///     &#10;    1.1 - mouseDown(e) - 滚动条中鼠标按下滚动条事件
                /// </summary>
                /// <param type="Event">
                ///     Event对象
                /// </param>
                /// <returns type="linkFlyScroll" />
                currScroll.isDrag = true;
                //获取当前鼠标y位置
                currScroll.startTop = tool.getMousePos(e).y;
                tool.addClass(currScroll.scrollUl, 'lf_ScrollFocus');
                return false;
            };
            var mouseUp = function () {
                /// <summary>
                ///     1: 鼠标释放事件
                ///     &#10;    1.1 - mouseUp() - 滚动条中鼠标释放滚动条事件
                /// </summary>
                /// <returns type="linkFlyScroll" />
                currScroll.isDrag = false;
                currScroll.endTop = currScroll.scrollUl.style.top ? parseInt(currScroll.scrollUl.style.top) : 0;
                tool.removeClass(currScroll.scrollUl, 'lf_ScrollFocus');
                return false;
            };
            currScroll.scrollEvent = function (currTop) {
                /// <summary>
                ///     1: 滚动事件(核心),传入需要滚动的坐标即可(滚动条top)
                ///     &#10;    1.1 - scrollEvent(currTop) - 核心滚动事件
                /// </summary>
                /// <param type="Int">
                ///     滚动条顶部距离上一层容器的top值
                /// </param>
                /// <returns type="void" />
                if (currTop <= currScroll.topLimit || currTop < 0) {//顶部极限
                    currTop = currScroll.topLimit;
                } else if (currTop >= currScroll.bottomLimit) {//底部极限
                    currTop = currScroll.bottomLimit;
                }
                //滚动条偏移效果
                currScroll.scrollUl.style.top = currTop + 'px';
                var tempTop = parseInt(currScroll.context.style.top ? currScroll.context.style.top : 0);
                //debug code
                //                document.getElementById('postionInfo').innerHTML = 'currTop:' + currTop + ' 滚动基数:' + currScroll.scrollRadix + ' bottomLimit:' + currScroll.bottomLimit + ' endTop:' + currScroll.endTop + ' startTop:' + currScroll.startTop + " Y:" + currTop + " offsetTop:" + currScroll.scrollUl.offsetTop + " compute:" + (currTop * currScroll.scrollRadix * -1) + 'px';
                //text code
                //内容滚动:当前滚动条top*基数取负数
                currScroll.context.style.top = currTop * currScroll.scrollRadix * -1 + 'px';
            };
            return currScroll;
        };
    };
    linkFlyScroll.prototype.init.prototype.change = function () {
        /// <summary>
        ///     1: 滚动条内容改变函数
        ///     &#10;    1.1 - change() - 本函数代表刷新本滚动条对象的数据,在某些情况下,内容的数据是一直在变化的,可以调用本函数对当前滚动条对象刷新数据
        /// </summary>
        /// <returns type="linkFlyScroll" />
        /*
        * linkFlyScroll包含的属性主要在本函数中初始化或重新定义:
        * isDrag:是否正在拖拽滚动条
        * startTop:(工作中)滚动条开始滚动位置
        * endTop:(工作中)滚动条结束滚动位置
        * topLimit:滚动条顶部极限位置
        * bottomLimit:滚动条底部极限位置
        * context:内容Dom
        * scrollRadix:滚动基数
        * target:容器Dom
        */
        //重置或读取一系列数据
        var currScroll = this;
        currScroll.isDrag = false,
        currScroll.startTop = 0,
        currScroll.endTop = (currScroll.scrollUl.style.top ? parseInt(currScroll.scrollUl.style.top) : 0),
        currScroll.topLimit = currScroll.target.scrollTop,
        currScroll.bottomLimit = currScroll.target.clientHeight,
        currScroll.scrollRadix = 10;
        //得出滚动条的高度:内容高度*(容器高度/内容高度=容器占内容百分比)
        var scrollPx = currScroll.target.clientHeight * (currScroll.target.clientHeight / currScroll.context.offsetHeight);
        //滚动条高度
        currScroll.scrollUl.childNodes[1].style.height = scrollPx + 'px';
        if (currScroll.context.clientHeight <= currScroll.target.clientHeight && currScroll.options.auto) {
            currScroll.scrollUl.style.display = 'none';
        } else {
            currScroll.scrollUl.style.display = 'block';
            //当滚动条显示,修正最大位置数据
            currScroll.bottomLimit -= currScroll.scrollUl.offsetHeight;
        }
        //设置滚动条滚动基数(滚动条没滚动1px内容滚动像素):(内容高度-容器高度[因为当前容器已经显示了一屏])/滚动条top(滚动条空白可滚动高度)
        currScroll.scrollRadix = (currScroll.context.offsetHeight - currScroll.target.clientHeight) / currScroll.bottomLimit;
        return currScroll;
    };
    linkFlyScroll.prototype.init.prototype.roll = function (value) {
        /// <summary>
        ///     1: 滚动条偏移方法
        ///     &#10;    1.1 - roll(value) - 滚动条滚动方法
        /// </summary>
        /// <param type="Int">
        ///     滚动条目标滚动的百分比
        /// </param>
        /// <returns type="linkFlyScroll" />
        var currScroll = this;
        if (typeof (value) !== 'number') {
            return currScroll;
        }
        var currTop = (currScroll.bottomLimit - currScroll.topLimit) * value / 100;
        currScroll.scrollEvent(currTop);
        currScroll.endTop = currTop;
        return currScroll;
    };
    /*
    * 工具类
    */
    var tool = {
        setClass: function (element, className) {
            /// <summary>
            ///     1: 设置元素节点的class属性
            ///     &#10;    1.1 - setClass(element,className) - 设置元素节点的class属性,如没有该节点则创建该节点,并返回修改后的节点对象
            /// </summary>
            /// <param type="Element Or String">
            ///     传入String则创建该节点,否则修改该节点
            /// </param>
            /// <param type="String">
            ///     Class Name
            /// </param>
            /// <returns type="Element" />
            if (typeof element === 'string') {
                element = document.createElement(element);
            }
            element.className = className;
            return element;
        },
        hasClass: function (element, className) {
            /// <summary>
            ///     1: 判断元素是否有class
            ///     &#10;    1.1 - hasClass(element,className) - 判断元素是否有class,在业务中异常(基本没有该情况的发生)和有该class返回true,否则返回false
            /// </summary>
            /// <param type="Element Or String">
            ///     节点对象
            /// </param>
            /// <param type="String">
            ///     Class Name
            /// </param>
            /// <returns type="Element" />
            if (!element || element.nodeType !== 1)//让异常通过,外面不进行处理
                return true;
            var elementClassName = element.className;
            if (elementClassName.length < 1) {
                return false;
            }
            if (elementClassName == className || elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) {
                return true;
            }
            return false;
        },
        addClass: function (element, className) {
            /// <summary>
            ///     1: 为元素【追加】class
            ///     &#10;    1.1 - addClass(element,className) - 为元素【追加】class,并返回修改后的class
            /// </summary>
            /// <param type="Element Or String">
            ///     节点对象
            /// </param>
            /// <param type="String">
            ///     Class Name
            /// </param>
            /// <returns type="Element" />
            if (!tool.hasClass(element, className)) {
                if (element.className.length < 1) {
                    element.className = className;
                } else {
                    element.className += ' ' + className;
                }
            }
            return element;
        },
        removeClass: function (element, className) {
            /// <summary>
            ///     1: 为元素移除class
            ///     &#10;    1.1 - addClass(element,className) - 为元素移除class,并返回修改后的class
            /// </summary>
            /// <param type="Element Or String">
            ///     节点对象
            /// </param>
            /// <param type="String">
            ///     Class Name
            /// </param>
            /// <returns type="Element" />
            if (tool.hasClass(element, className)) {
                var reg = new RegExp("(^|\\s)" + className + "(\\s|$)");
                element.className = element.className.replace(reg, '');
            }
            return element;
        },
        css: function (element, key) {
            /// <summary>
            ///     1: 获取元素css指定的属性值
            ///     &#10;    1.1 - css(element,className) - 获取元素css指定的属性值
            /// </summary>
            /// <param type="Element Or String">
            ///     节点对象
            /// </param>
            /// <param type="String">
            ///     要获取的css属性
            /// </param>
            /// <returns type="String" />
            return element.currentStyle ? element.currentStyle[key] : document.defaultView.getComputedStyle(element, false)[key];
        },
        addEvent: function (element, type, fn) {
            /// <summary>
            ///     1: 为元素追加事件
            ///     &#10;    1.1 - css(element, type, fn) - 为元素追加事件,函数中this指向事件源
            /// </summary>
            /// <param type="Element Or String">
            ///     节点对象
            /// </param>
            /// <param type="String">
            ///     追加的事件名,不含字符on
            /// </param>
            /// <param type="Function">
            ///     事件对象
            /// </param>
            /// <returns type="void" />
            if (element.attachEvent) {
                element['e' + type + fn] = fn;
                element[type + fn] = function () { element['e' + type + fn](window.event); }
                element.attachEvent('on' + type, element[type + fn]);
            } else if (element.addEventListener) {
                element.addEventListener(type, fn, false);
            }
        },
        //        removeEvent: function (element, type, fn) {
        //            /// <summary>
        //            ///     1: 为元素删除事件,本函数并未用到
        //            ///     &#10;    1.1 - removeEvent(element, type, fn) - 为元素删除事件
        //            /// </summary>
        //            /// <param type="Element Or String">
        //            ///     节点对象
        //            /// </param>
        //            /// <param type="String">
        //            ///     删除的事件名
        //            /// </param>
        //            /// <param type="String">
        //            ///     删除的事件的函数名
        //            /// </param>
        //            /// <returns type="void" />
        //            if (element.detachEvent) {
        //                element.detachEvent('on' + type, element[type + fn]);
        //                element[type + fn] = null;
        //            } else if (element.removeEventListener) {
        //                element.removeEventListener(type, fn, false);
        //            }
        //        },
        addScrollEvent: function (element, fn) {
            /// <summary>
            ///     1: 追加ScrollEvent事件
            ///     &#10;    1.1 - addScrollEvent(element,fn) - 在元素上追加ScrollEvent事件(特殊事件,在元素上鼠标滚轮滚动事件)
            /// </summary>
            /// <param type="Element Or String">
            ///     元素节点
            /// </param>
            /// <param type="Function">
            ///     事件方法
            /// </param>
            /// <returns type="void" />
            var bindScrollFn = function (e) {
                e = e || window.event;
                //判断滚轮滚动方向:Firefox和其他浏览器不同
                e.wheel = (e.wheelDelta ? e.wheelDelta : -e.detail) > 0 ? 1 : -1; // 通过事件判断鼠标滚轮反向,1是向上,-1是向下
                //阻止浏览器默认行为
                if (e.preventDefault) { //ff
                    e.preventDefault();
                } else {
                    e.returnValue = false; //ie
                }
                fn.call(element, e);
            }
            if (document.addEventListener) {
                //ff
                element.addEventListener('DOMMouseScroll', bindScrollFn, false);
                //w3c
                element.addEventListener('mousewheel', bindScrollFn, false);
            } else//ie
            {
                element.attachEvent('onmousewheel', bindScrollFn);
            }
        },
        getEvent: function () {
            /// <summary>
            ///     1: 获取Event对象
            ///     &#10;    1.1 - getEvent() - 在无参数的情况下获取Event对象,同时兼容性处理IE和FF
            /// </summary>
            /// <returns type="Event" />
            if (document.all) {
                return window.event;
            }
            func = getEvent.caller;
            while (func != null) {
                var arg0 = func.arguments[0];
                if (arg0) {
                    if ((arg0.constructor == Event || arg0.constructor == MouseEvent) || (typeof (arg0) == "object" && arg0.preventDefault && arg0.stopPropagation)) {
                        return arg0;
                    }
                }
                func = func.caller;
            }
            return null;
        },
        getMousePos: function (ev) {
            /// <summary>
            ///     1: 获取当前鼠标坐标
            ///     &#10;    1.1 - getMousePos(ev) - 获取当前鼠标坐标,兼容性处理,返回的对象格式:{ x:鼠标x坐标 , y:鼠标y坐标 }
            /// </summary>
            /// <param type="Event">
            ///     Event事件对象
            /// </param>
            /// <returns type="Json" />
            if (!ev) {
                ev = currScroll.getEvent();
            }
            if (ev.pageX || ev.pageY) {
                return {
                    x: ev.pageX,
                    y: ev.pageY
                };
            }
            if (document.documentElement && document.documentElement.scrollTop) {
                return {
                    x: ev.clientX + document.documentElement.scrollLeft - document.documentElement.clientLeft,
                    y: ev.clientY + document.documentElement.scrollTop - document.documentElement.clientTop
                };
            }
            else if (document.body) {
                return {
                    x: ev.clientX + document.body.scrollLeft - document.body.clientLeft,
                    y: ev.clientY + document.body.scrollTop - document.body.clientTop
                };
            }
        },
        extend: function (oldObj, newObj) {
            /// <summary>
            ///     1: 将两个对象进行合并
            ///     &#10;    1.1 - extend(oldObj,newObj) - 将两个对象合并,并返回合并后的对象,采用clone的方式实现,所以不会对两个对象产生任何影响
            /// </summary>
            /// <param type="Object">
            ///     要合并的对象A,该对象作为基础对象,将新对象的同名属性覆盖到基础对象中
            /// </param>
            /// <param type="Object">
            ///     要合并的对象B
            /// </param>
            /// <returns type="Object" />
            var tempObj = tool.clone(oldObj);
            for (var key in newObj) {
                if (newObj.hasOwnProperty(key) && !tempObj.hasOwnProperty(key)) {
                    tempObj[key] = newObj[key];
                }
            }
            return tempObj;
        },
        clone: function (obj) {
            /// <summary>
            ///     1: 克隆一个对象
            ///     &#10;    1.1 - clone(obj) - 克隆一个对象,并返回克隆后的新对象,该对象的原型是被克隆的对象
            /// </summary>
            /// <param type="Object">
            ///     要克隆的对象
            /// </param>
            /// <returns type="Object" />
            function Clone() { }
            Clone.prototype = obj;
            var newObj = new Clone();
            for (var key in newObj) {
                if (typeof newObj[key] == "object") {
                    newObj[key] = tool.clone(newObj[key]);
                }
            }
            return newObj;
        },
        convertValue: function (value) {
            /// <summary>
            ///     1: 将数值转换为有效的数值
            ///     &#10;    1.1 - convertValue(value) - 将Json配置的css数值转换为有效的数值,请保证value的值不为"auto"
            /// </summary>
            /// <param type="Object">
            ///     要转换的数值
            /// </param>
            /// <returns type="Object" />
            var reg = /^\d+$/g;
            if (typeof (value) === 'number' || reg.test(value)) {
                return value + 'px';
            } else
                return value;
        }
    };
    //注册到window下
    window.linkFlyScroll = linkFlyScroll;
    //注册到window.so命名空间下
    if (!window.so) {
        window.so = {};
    }
    window.so.scroll = window.linkFlyScroll;
})(window);


代码示例

复制代码 代码如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wgfgfy.html