前几天总结了IScroll的一些用法,总觉得IScroll有时候还是太庞大了些,有时候用它仅仅是为了自定义一个滚动条样式……,于是我决定自己动手,丰衣足食。
这是我封装的第一个插件,当然是先做一下总结啦!我认为这个插件有主要两个难点:
一、滑动处理动画(目前还没有做出来)
二、自定义时间的处理。
第一条还没有思路,我就针对第二条谈一下自己的心得吧!
自定义事件其实很简单,本质就是将自己定义事件看作是一个事件对象里面的一个存放方法的数组,那么我们在插件中需要触发我们自定义的事件的时候,去遍历这个数组里面的方法并且全部执行就可以了。
我封装的这个插件中只定义了事件绑定器和触发器。这里总结下这两个东西,所谓触发器,就是在this.events对象中建立一个自定义事件的数组(如果没有的话),然后去遍历里面的方法,并且依次执行;所谓事件绑定器,就是往事件对象里的自定义事件数组中push方法,这个方法是实例化代码的时候从外面传进来的。
然后,就像上面所说,在程序的核心代码合适的地方(自定义的方法含义),我们只要去调用事件触发器并且传入自定义的名称就可以了。
对于这个的理解,一篇文章和IScroll源码对我的启发很大!给出改片文章的链接:https://www.cnblogs.com/xcmylrl/p/5405797.html。
另外一个收获就是原型和对this的理解了,说白了,插件就是一个类,我们通过构造函数定义这个类具有的属性,然后通过定义构造函数的原型,去定义它应该有的功能。this困扰本猿很久,现在终于终于明白,this始终属于自己所处的对象,也最终始终指向自己所处的对象。构造函数本身就是一个未实例化的对象,那么它的this就指向实例化后的对象(是这样的吧?哈哈哈哈);在对象中的方法内,那么这个this也是指向这个对象;在普通方法中,由于普通方法不是对象,但它属于window,那么这个this就会指向window(对吧?哈哈哈哈)。所以,this的本质就是指向它本身所属的对象!哈哈,终于不用再看到this就迷路了!
献上我的插件源码和测试代码:
插件源码:
/*** * 用于前端列表不能动的插件,可以自定义滚动条 * 着手开发于2017-12-11 * author:一只神秘的猿 */ (function (win,doc,Math) { function AScroll(el,opations) { this.height = 0;//里面框的高度 this.boxHeight = 0;//容器的高度 this.element = null; this.children = null; this.style = null; this.scrollBox = null;//滚动条框 this.scrollItem = null;//滚动条 this.opations = opations;//参数 this.overHeight = 0;//为显示的内容高度 this.bottomHeight = 0;//底部未显示的高度 this.events = {}; this.startY = 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(); } AScroll.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.element.offsetHeight) { this.scrollBox = doc.createElement("div"); this.scrollItem = doc.createElement("div"); this.scrollBox.appendChild(this.scrollItem); this.element.appendChild(this.scrollBox); //设置滚动条类名 if (this.opations && typeof this.opations.barName === "string") { this.scrollBox.className = "clipScrollBox " + this.opations.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 () { this.children.style.transform = "translate3d(0," + this.y + "px,0)"; }, changePosition: function () { this.scrollItem.style.transform = "translate3d(0," + Math.abs(this.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) { e.preventDefault(); e.stopPropagation(); self.startY = e.touches[0].pageY; self.transform(); },false); this.element.addEventListener("touchmove",function (e) { e.preventDefault(); e.stopPropagation(); 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.element.offsetHeight - 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; self.changePosition(); } self.bottomHeight = self.overHeight + self.y; self._sendEvent("scrolling"); self.transform(); },false); this.element.addEventListener("touchend",function (e) { e.preventDefault(); e.stopPropagation(); self.endTime = Date.now(); self.endY = e.changedTouches[0].pageY; if (self.y > 0) { self.y = 0; self.changePosition(); self.transform(); } else if (self.y <= self.element.offsetHeight - self.height) { self.y = self.element.offsetHeight - self.height; self.changePosition(); self.transform(); } },false); }, //刷新列表 refresh: 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.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,arguments); } }, }; if (typeof module != "undefined" && module.exports) { module.exports = AScroll; } else if ( typeof define == ‘function‘ && define.amd ) { define( function () { return AScroll; } ); } else { window.AScroll = AScroll; } })(window,document,Math);
以下是测试源码:
<!DOCTYPE html> <html lang="zh_CN"> <head> <title>clip插件测试</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> #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="clip.js"></script> <script type="text/javascript"> var box = document.querySelector("#myBox>div"); var loaded = false; //模拟ajax添加条目 function createNewItem() { var i = 0, l = 10; for ( ; i < l; i++) { var myDom = document.createElement("p"); myDom.innerText = "我是添加的条目" + (i + 1); box.appendChild(myDom); } // console.log("created!"); }; var myTest = new AScroll("#myBox"); myTest.on("scrolling",function () { if (this.bottomHeight < 100 && !loaded) { loaded = true; createNewItem(); this.refresh(); } }); </script> </body> </html>
用法说明:
支持分段加载(refresh)、自定义滚动条(opations.barName = "string");