H5自定义滚动条

在一些项目中,用户总是要求自定义一下滚动条,以前一般用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);

    }

插件使用实例:


```c

```c

```c

```javascript
<!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 {
            width: 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; width: 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","width: 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","width: 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; width: 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","width: 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","width: 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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值