zoukankan      html  css  js  c++  java
  • H5自定义滚动插件——DeftScroll.js,可自定义滚动条

    在一些项目中,用户总是要求自定义一下滚动条,以前一般用iscroll解决,但是发现iscroll有很多不方便的地方,而且也比较大,索性自己琢磨一个类似的插件吧!目的有两个:要足够小,易于上手使用;功能一定要足够实用,能满足广大H5开发者的基本需求。

    介绍一下这个插件的主要功能:

    1、隐藏或显示滚动条,自定义滚动条样式。

    2、滚动dom的刷新:refresh;

    3、滚动内容的懒加载;

    4、子元素绑定tap事件;

    5、支持scrolling、scrollEnd等插件内事件绑定;

    6、scrollTo方法和其他的一些方法。

    相比上一个测试版本(详见上一篇博客),我在这个版本支持了滚动动画,并且加入了Tap事件和destroy方法。总结一下以下技术难点:

    1、支持用户自定义事件绑定到列表元素上,我采用用户传入dom和自定义的方法,利用tap接口传入插件,在插件中做tap的处理和回调。

    2、当懒加载成功后,给加载的内容绑定自定义事件。这时需要执行refresh(刷新)方法,在插件内执行destroy方法,将removeEventListener放在this.events.destory中,利用sendEvent执行,这会销毁掉在tap中用户绑定的自定义方法。在刷星完毕后重新绑定就可以了。

    3、利用requestAnimationFrame和css的transition-timing-function分段做列表的滚动动画。

    使用说明:

    1、自定义滚动条:

    var scroll = new Dscroll(selector,{
        scrollBar: true,
        barName: "myClassName",
    });

    2、懒加载

    //this.bottomHeight为底部未显示的高度,利用scrolling监听该值。
     myTest.on("scrolling",function () {
            if (this.bottomHeight < 100 && !loaded) {
                loaded = true;
                createNewItem();
    
                //刷新操作会清空子元素的绑定事件
                myTest.refresh();
    
                //刷新后统一绑定点击事件
                bindTouch();
            }
        });

    3、子元素绑定点击事件

    var i = 0,
                l = document.querySelectorAll("#myBox>div>p").length;
    
            for (; i < l; i++) {
    
                (function (k) {
                    var dom = document.querySelectorAll("#myBox>div>p").item(k);
                    myTest.tap(dom,function () {
                        alert("您点击的是第" + (k + 1) + "个段落。");
                    });
                })(i);
    
            }

    插件使用实例:

    <!DOCTYPE html>
    <html lang="zh_CN">
    <head>
        <title>DeftScroll插件测试</title>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
        <meta name="apple-mobile-web-app-title" content=""/>
        <meta name="apple-touch-fullscreen" content="YES" />
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="format-detection" content="telephone=no" />
        <meta name="HandheldFriendly" content="true" />
        <meta http-equiv="x-rim-auto-match" content="none" />
        <meta name="format-detection" content="telephone=no" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <style>
            body {
                position: absolute;
                left: 0;
                right: 0;
                top: 0;
                bottom: 0;
                margin: auto;
                overflow: hidden;
            }
            #myBox {
                 90%;
                height: 90%;
                position: absolute;
                left: 0;
                right: 0;
                top: 0;
                bottom: 0;
                margin: auto;
                overflow: hidden;
            }
    
            #myBox p {
                margin: 5px auto;
                line-height: 60px;
                text-align: center;
                background: #ddd;
            }
        </style>
    </head>
    <body>
    <div id="myBox">
        <div>
            <p>1</p>
            <p>2</p>
            <p>3</p>
            <p>4</p>
            <p>5</p>
            <p>6</p>
            <p>7</p>
            <p>8</p>
            <p>9</p>
            <p>10</p>
            <p>11</p>
            <p>12</p>
            <p>13</p>
            <p>14</p>
            <p>15</p>
            <p>16</p>
            <p>17</p>
            <p>18</p>
            <p>19</p>
            <p>20</p>
        </div>
    </div>
    <script type="text/javascript" src="DeftScroll.js"></script>
    <script type="text/javascript">
    
        document.body.addBehavior("touchmove",function (e) {
            e.preventDefault();
        },false);
    
        var box = document.querySelector("#myBox>div");
        var loaded = false;
        var myTest = new DScroll("#myBox",{
            scrollBar: true,
        });
    
        //模拟ajax添加条目
        function createNewItem() {
            var i = 0, l = 10;
    
            for ( ; i < l; i++) {
                var myDom = document.createElement("p");
                myDom.innerText = "我是添加的条目" + (i + 1);
                box.appendChild(myDom);
            }
        };
    
        //子元素绑定点击事件
        function bindTouch() {
            var i = 0,
                l = document.querySelectorAll("#myBox>div>p").length;
    
            for (; i < l; i++) {
    
                (function (k) {
                    var dom = document.querySelectorAll("#myBox>div>p").item(k);
                    myTest.tap(dom,function () {
                        alert("您点击的是第" + (k + 1) + "个段落。");
                    });
                })(i);
    
            }
        };
    
        myTest.on("scrolling",function () {
            if (this.bottomHeight < 100 && !loaded) {
                loaded = true;
                createNewItem();
    
                //刷新操作会清空子元素的绑定事件
                myTest.refresh();
    
                //刷新后统一绑定点击事件
                bindTouch();
            }
        });
    
        bindTouch();
    </script>
    </body>
    </html>

    插件源码:

    /***
     * 着手开发于2017-12-11
     * author:一只神秘的猿
     * name: DeftScroll
     */
    
    /****1.2版本
     * 开发于2017-12-21
     */
    (function (win,doc,Math) {
        var rAF = window.requestAnimationFrame    ||
            window.webkitRequestAnimationFrame    ||
            window.mozRequestAnimationFrame        ||
            window.oRequestAnimationFrame        ||
            window.msRequestAnimationFrame        ||
            function (callback) { window.setTimeout(callback, 1000 / 60); };
        function DScroll(el,options) {
    
            this.height = 0;//里面框的高度
            this.boxHeight = 0;//容器的高度
            this.element = null;
            this.children = null;
            this.style = null;
            this.scrollBox = null;//滚动条框
            this.scrollItem = null;//滚动条
            this.options = options;//参数
            this.overHeight = 0;//未显示的内容高度
            this.bottomHeight = 0;//底部未显示的高度
            this.events = {};
            this.startY = 0;
            this.isAnimating = false;
            this.oStartY = 0;
            this.endY = 0;
            this.y = 0;
    
            if (typeof el === "string") {
                this.element = doc.querySelector(el);
            } else {
                throw "获取不到正确的dom。";
            }
    
            if (this.element) {
                var child = this.element.children[0];
                this.children = child;
            } else {
                throw "无法获取列表父级盒子。"
            }
    
            this._init();
            this._eventHandle();
        }
    
        DScroll.prototype = {
    
            _init: function () {
                if (this.children) {
                    this.height = this.children.scrollHeight;
                    this.boxHeight = this.element.offsetHeight;
                    this.overHeight = this.height - this.boxHeight;
                    this.style = this.children.style;
                }
    
                if (this.height > this.boxHeight) {
    
                    if (!this.options || !this.options.scrollBar) {
                        return;
                    }
    
                    this.scrollBox = doc.createElement("div");
                    this.scrollItem = doc.createElement("div");
                    this.scrollBox.appendChild(this.scrollItem);
                    this.element.appendChild(this.scrollBox);
    
                    //设置滚动条类名
                    if (this.options && typeof this.options.barName === "string") {
                        this.scrollBox.className = "clipScrollBox " + this.options.barName;
                    } else {
                        this.scrollBox.className = "clipScrollBox";
                    }
    
                    this.scrollItem.className = "clipScrollItem";
    
                    if (this.scrollBox.className === "clipScrollBox") {
                        this.scrollBox.setAttribute(
                            "style","position:absolute;  5px; height:100%; top: 0; right: 0; border: 1px solid #fff; background: rgba(255,255,255,.7); border-radius: 4px; overflow: hidden; z-index: 1000");
                        this.scrollItem.setAttribute("style"," 100%; height: " +  this.boxHeight * 100 / this.height + "%; background: #999; border-radius: 4px;")
                    } else {
                        this.scrollBox.setAttribute("style","position: absolute; height:100%; top: 0; right: 0; overflow: hidden; z-index: 1000");
                        this.scrollItem.setAttribute("style"," 100%; height: " +  this.boxHeight * 100 / this.height + "%;")
                    }
                }
    
            },
    
            transform: function (destY) {
    
                if (destY) {
                    this.y = destY;
                }
    
                this.children.style.transform = "translate3d(0," + this.y + "px,0)";
            },
    
            changePosition: function () {
                var y = 0;
                if (this.y <= 0 && this.y >= -this.overHeight) {
    
                    this.scrollItem.style.transform = "translate3d(0," + Math.abs(this.y) * (this.boxHeight - this.boxHeight * this.boxHeight / this.height) / (this.height - this.boxHeight) + "px,0)";
    
                } else if (this.y > 0) {
    
                    y = 0;
                    this.scrollItem.style.transform = "translate3d(0," + Math.abs(y) * (this.boxHeight - this.boxHeight * this.boxHeight / this.height) / (this.height - this.boxHeight) + "px,0)";
    
                } else {
    
                    y = -this.overHeight;
    
                    this.scrollItem.style.transform = "translate3d(0," + Math.abs(y) * (this.boxHeight - this.boxHeight * this.boxHeight / this.height) / (this.height - this.boxHeight) + "px,0)";
                }
            },
    
            //事件控制器
            _eventHandle: function (e) {
                var self = this;
    
                this.element.addEventListener("touchstart",function (e) {
    
                    self.startY = e.touches[0].pageY;
                    self.oStartY = self.startY;
                    self.startTime = utils.getTime();
    
                    self.isAnimating && self.stop();
                },false);
    
                this.element.addEventListener("touchmove",function (e) {
    
                    if (self.y > 0) {
    
                        self.diffY = e.touches[0].pageY - self.startY;
                        self.startY = e.touches[0].pageY;
                        self.y += self.diffY * .3;
    
                    } else if (self.y <= self.boxHeight - self.height) {
    
                        self.diffY = e.touches[0].pageY - self.startY;
                        self.startY = e.touches[0].pageY;
                        self.y += self.diffY * .3;
    
                    } else {
    
                        self.diffY = e.touches[0].pageY - self.startY;
                        self.startY = e.touches[0].pageY;
                        self.y += self.diffY;
    
                        if (self.options && self.options.scrollBar) {
                            self.changePosition();
                        }
    
                    }
    
                    self.bottomHeight = self.overHeight + self.y;
    
                    //利用requestAnimationFrame做transform的动画过程中,不允许添加DOM,个人猜测js机制不允许……暂时关闭scrolling接口
                    self._sendEvent("scrolling");
                    self.transform();
                },false);
    
                this.element.addEventListener("touchend",function (e) {
    
                    self.endTime = utils.getTime();
                    self.endY = e.changedTouches[0].pageY;
    
                    self._end(e);
    
    
                },false);
            },
    
            stop: function () {
                if (this.isAnimating) {
                    this.isAnimating = false;
                }
            },
    
            _end: function (e) {
                var duration = this.endTime - this.startTime,
                    newY = Math.round(this.endY);
    
                if (duration < 300) {
    
                    aniData = utils.momentum(newY,this.oStartY,duration,this.y,this.boxHeight,-this.overHeight);
                    this.speed = aniData.speed;
    
                    this.children.style.transitionTimingFunction = utils.ease.quadratic.style;
                    this._animate(aniData.destination,aniData.duration,utils.ease.quadratic.fn,aniData.speed);
                } else if (this.y > 0) {
    
                    this.scrollTo(0,20,200);
    
    
                } else if (this.y <= -this.overHeight) {
    
    
                    this.scrollTo(-this.overHeight,20,200);
    
    
                } else {
    
                    if (this.events["scrollEnd"]) {
                        this._sendEvent("scrollEnd");
                    }
    
                }
            },
    
            //刷新列表
            refresh: function () {
    
                this._sendEvent("destroy");
                this.events.destroy = [];
    
                if (this.children) {
                    this.height = this.children.scrollHeight;
                    this.boxHeight = this.element.offsetHeight;
                    this.overHeight = this.height - this.boxHeight;
                    this.style = this.children.style;
                }
    
                if (this.options && this.options.scrollBar) {
    
                    if (this.scrollBox.className === "clipScrollBox") {
                        this.scrollBox.setAttribute(
                            "style","position:absolute;  5px; height:100%; top: 0; right: 0; border: 1px solid #fff; background: rgba(255,255,255,.7); border-radius: 4px; overflow: hidden; z-index: 1000");
                        this.scrollItem.setAttribute("style"," 100%; height: " +  this.boxHeight * 100 / this.height + "%; background: #999; border-radius: 4px;")
                    } else {
                        this.scrollBox.setAttribute("style","position: absolute; height:100%; top: 0; right: 0; overflow: hidden; z-index: 1000");
                        this.scrollItem.setAttribute("style"," 100%; height: " +  this.boxHeight * 100 / this.height + "%;")
                    }
    
                    this.changePosition();
                }
            },
    
            //事件绑定,实质就是自定义一个事件名称,将需要执行的方法存放在这个数组中,在代码需要的时候遍历这个事件数组,去执行里面的方法。
            on: function (type,fn) {
    
                if (!this.events[type]) {
                    this.events[type] = [];
                }
    
                this.events[type].push(fn);
    
            },
    
            //事件触发器,在代码合适的地方调用该方法,这个方法会遍历events中的对应的事件名下的所有方法,并且依次执行。这里,我们的方法都是实例化改对象时候使用者写入的方法。
            _sendEvent: function (type) {
    
                if (!this.events[type]) {
                    this.events[type] = [];
                }
    
                var l = this.events[type].length,i = 0;
    
                for ( ; i < l; i++) {
                    this.events[type][i].apply(this,[].slice.call(arguments, 1));//保证从第一个参数传递
                }
    
            },
    
            _animate: function (destY,duration,easingFn,speed) {
                var startTime = utils.getTime(),
                    self = this,
                    startY = this.y,
                    destTime = startTime + duration,
                    time = 0;
    
                function stepAnimation() {
                    var now = utils.getTime(),
                        newY,
                        easing;
                    if ( now >= destTime ) {
                        self.isAnimating = false;
    
                        // INSERT POINT: _end
    
                        if ( destY > 0 ) {
    
                            time = destY / speed;
                            self.scrollTo(0, time,speed);
    
                        } else if (destY < -self.overHeight) {
    
                            time = (Math.abs(destY) - self.overHeight) / speed;
                            self.scrollTo(-self.overHeight, time,speed);
    
                        } else {
    
                            self.transform(destY);
                            self._sendEvent('scrollEnd');
                        }
    
                        return;
                    }
    
                    self._sendEvent("scrolling");
                    now = (now - startTime) / duration;
                    easing = easingFn(now);
                    newY = (destY - startY) * easing + startY;
                    self.transform(newY);
    
                    self.bottomHeight = self.overHeight + self.y;
    
                    if (self.options && self.options.scrollBar) {
                        self.changePosition();
                    }
    
                    if (self.isAnimating) {
                        rAF(stepAnimation);
                    }
                }
    
                this.isAnimating = true;
                stepAnimation();
            },
    
            scrollTo: function (position,time,speed) {
    
                this._animate(position,time * 15,utils.ease.quadratic.fn,speed / 15);
            },
    
            tap: function (element,callBack) {
                var startY = 0,
                    endY = 0,
                    isMove = false,
                    startTime = 0,
                    endTime = 0,
                    maxTime = 500;
    
                function start(e) {
                    startY = e.touches[0].pageY;
                    startTime = utils.getTime();
                }
    
                function move(e) {
                    isMove = true;
                }
    
                function end(e) {
                    endTime = utils.getTime();
                    endY = e.changedTouches[0].pageY;
    
                    if (Math.abs(endY - startY > 10)) {
                        return;
                    }
    
                    if (isMove) {
                        isMove = false;
                        return;
                    }
    
                    if (endTime - startTime > maxTime) {
                        return;
                    }
    
                    callBack();
                }
    
                element.addEventListener("touchstart",start,false);
    
                element.addEventListener("touchmove",move,false);
    
                element.addEventListener("touchend",end,false);
    
                this.on("destroy",function () {
                    element.removeEventListener("touchstart",start,false);
                    element.removeEventListener("touchmove",move,false);
                    element.removeEventListener("touchend",end,false);
                });
            },
        };
    
        //工具对象
        var utils = (function () {
            var me = {};
    
            me.getTime =  function () {
                return Date.now() || new Date().getTime();
            };
    
            //计算执行动画所需的参数
            me.momentum =  function (current,startY,time,y,wrapperSize,lowerMargin) {
                var deceleration = 0.0006,
                    distance = current - startY,
                    speed = Math.abs(distance / time),
                    data = null;
    
                destination = y + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
                duration = Math.round(Math.abs(speed / deceleration));
    
                if (destination < lowerMargin) {
                    destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin;
                    distance = Math.abs(destination - y);
                    duration = distance / speed;
                } else if (destination > 0) {
                    destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0;
                    distance = Math.abs(y) + destination;
                    duration = distance / speed;
                }
    
                data = {
                    destination: Math.round(destination),
                    duration: duration,
                    speed: speed,
                };
    
                return data;
            };
    
            me.bounce = function (current,targetY,speed) {
                var distance = Math.abs(targetY - current),
                    speed = speed * .6,
    
                time = distance / speed;
    
                return {
                    time: time,
                    speed: speed,
                };
    
            };
    
            me.extend = function (ease,obj) {
                for (var i in obj) {
                    ease[i] = obj[i];
                }
            };
    
            me.extend(me.ease = {}, {
                quadratic: {
                    style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
                    fn: function (k) {
                        return k * ( 2 - k );
                    }
                },
                circular: {
                    style: 'cubic-bezier(0.1, 0.57, 0.1, 1)',    // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1)
                    fn: function (k) {
                        return Math.sqrt(1 - ( --k * k ));
                    }
                },
                back: {
                    style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
                    fn: function (k) {
                        var b = 4;
                        return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1;
                    }
                },
                bounce: {
                    style: '',
                    fn: function (k) {
                        if (( k /= 1 ) < ( 1 / 2.75 )) {
                            return 7.5625 * k * k;
                        } else if (k < ( 2 / 2.75 )) {
                            return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;
                        } else if (k < ( 2.5 / 2.75 )) {
                            return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;
                        } else {
                            return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;
                        }
                    }
                },
                elastic: {
                    style: '',
                    fn: function (k) {
                        var f = 0.22,
                            e = 0.4;
    
                        if (k === 0) {
                            return 0;
                        }
                        if (k == 1) {
                            return 1;
                        }
    
                        return ( e * Math.pow(2, -10 * k) * Math.sin(( k - f / 4 ) * ( 2 * Math.PI ) / f) + 1 );
                    }
                }
            });
    
            return me;
        })();
    
        DScroll.utils = utils;
    
        if (typeof module != "undefined" && module.exports) {
            module.exports = DScroll;
        } else if ( typeof define == 'function' && define.amd ) {
            define( function () { return DScroll; } );
        } else {
            window.DScroll = DScroll;
        }
    })(window,document,Math);

    github下载地址:https://github.com/definedUserName/DeftScroll.js

  • 相关阅读:
    Cocos Creator Editor 第一个编辑器扩展(扩展菜单)
    Rider 设置
    unity 使用GameObject.SetActive(true)激活对象时,会在SetActive内部调用Awake和OnEnable函数
    unity/C# 结构体属性使用set和get访问器应注意的问题
    unity 自定义AssetImporter导入指定资源
    Duilib部分源码解析
    TreeView树形控件的使用
    JQuery 文档资源收集
    排序和搜索(一)插入排序系列
    字符相关类型和编码概念
  • 原文地址:https://www.cnblogs.com/zhangbob/p/8110413.html
Copyright © 2011-2022 走看看