(function(){
// 自定義 Vue, isVueLoaded
var Vue;
var isVueLoaded = true;
// 判斷是否有 require 方法,如果有則通過 require 方式引入 Vue,否則直接從 window 獲取
if(typeof require === 'undefined'){
Vue = window.Vue;
}else{
Vue = require('vue');
}
// 如果沒有 Vue, 則 isVueLoaded 賦值為 false, 并且 warn 提示用戶去下載 Vue
if(!Vue) {
isVueLoaded = false;
console.warn('Vue is not loaded yet. Please make sure it is loaded before installing vue-scroll.');
}
// 定義 scrollPlugin 對象阔蛉,以下是自定義 vue 插件的方式
var scrollPlugin = {
// 對象的 install 屬性, 屬性值是一個 function, 參數(shù)是 Vue 和 options
install: function(Vue, options){
// 定義常量
var EVENT_SCROLL = 'scroll';
// 定義一個 Q 構造函數(shù)
function Q(){
// 定義數(shù)組
var elements = [];
var listeners = [];
var addListener = function(element, eventType, funcs){
// 再次定義局部變量 EVENT_SCROLL
var EVENT_SCROLL = 'scroll';
var scrollData = {};
// https://github.com/wangpin34/vue-scroll/issues/1
// 如果 element 是 document.body 或者 window 等大視圖的話
if((element === document.body || element === document || element === window) && eventType === EVENT_SCROLL){
// 直接給 document 定義 onscroll 方法,原本為空,現(xiàn)在直接內(nèi)部定義,滾動時觸發(fā)
document.onscroll = function(e) {
// 自定義對象的屬性
// 此處的 scrollTop 取值直接是 document.body 的值
scrollData.scrollTop = document.body.scrollTop;
scrollData.scrollLeft = document.body.scrollLeft;
// 將傳入的方法一個一個執(zhí)行掉
funcs.forEach(function(func){
func && func(e, scrollData);
})
}
}else {
// element 應該是具體元素,如果不存在,則直接當做是 window.event
// 滾動時自動調(diào)用此方法, 此時 scroll 方法已經(jīng)添加到 funcs 里了
// 如果綁定的是具體元素滑臊,具體元素滾動會觸發(fā)下面方法的執(zhí)行
var listener = function(e) {
e = e || window.event;
e.target = e.target || e.srcElement;
if(eventType === EVENT_SCROLL){
scrollData.scrollTop = element.scrollTop;
scrollData.scrollLeft = element.scrollLeft;
}
funcs.forEach(function(func){
// 執(zhí)行所有的 func
// 從外部獲取 function,并且將元素和 scrollData 傳遞給外部的方法
(typeof func !== 'undefined') && func(e, scrollData);
})
}
// 如果是新版瀏覽器,則支持 addEventListener ,通過該方法添加監(jiān)聽事件
if(element.addEventListener){
// 參數(shù)分別是事件名和觸發(fā)事件時需要執(zhí)行的函數(shù)
element.addEventListener(eventType, listener);
}else{
// 老版本瀏覽器(IE8及以下)只支持 attachEvent, 則通過改該方法添加監(jiān)聽事件
element.attachEvent('on' + eventType, listener);
}
}
}
// _initialized 是標記位,用來標記 Q 對象是否初始化了(給原型賦予了任何方法)
// 如果這個值未定義蚪拦,構造函數(shù)將用原型方式繼續(xù)定義對象的方法
// 如果未初始化,則進行一系列操作,然后設置 _initialized 為 true,表示此時已經(jīng)被初始化了
// 此方法是動態(tài)原型模式,下方代碼只有初次調(diào)用構造函數(shù)的時候才會執(zhí)行
if(typeof Q._initialized == 'undefined'){
// element 為外部傳入元素, eventType 這里為 scroll, func 為外部傳入方法
Q.prototype.bind = (function(element, eventType, func){
var funcs;
// 查看元素是否在 elements 里,沒有則添加進去
if(elements.indexOf(element) < 0){
elements.push(element);
// 給 listeners 添加一個空對象
listeners.push({});
// listeners 的數(shù)組最后一個元素內(nèi)容賦值給 funcs
funcs = listeners[listeners.length - 1];
}else{
// 將 element 對應的 listener 賦值給 funcs
funcs = listeners[elements.indexOf(element)];
}
var eventFuncs;
// funcs 對應的事件類型的事件不存在
if(!funcs[eventType]){
//Initialize event listeners
funcs[eventType] = [];
//Bind to the element once
addListener(element, eventType, funcs[eventType]);
}
eventFuncs = funcs[eventType];
// func 為外部傳入的方法
eventFuncs.push(func);
}).bind(this);
Q.prototype.unbind = (function(element, eventType, func){
var funcs;
if(elements.indexOf(element) < 0){
console.warn('There are no listener could be removed.');
return 1;
}else{
funcs = listeners[elements.indexOf(element)];
}
var eventFuncs;
if(!funcs[eventType] || (eventFuncs = funcs[eventType]).indexOf(func) < 0){
console.warn('There are no listener could be removed.');
return;
}
eventFuncs.splice(eventFuncs.indexOf(func), 1);
console.log('A event listener is removed successfully');
}).bind(this);
Q._initialized = true;
}
}
// 生成實例
var q = new Q();
// 定義 Vue 指令
Vue.directive('scroll', {
bind: function(el, binding, vnode){
// 如果 binding.value 不是一個函數(shù),則進行錯誤提示
if(!binding.value || typeof binding.value !== 'function'){
throw new Error('The scroll listener is not a function');
}
// 三個參數(shù)分別表示元素,事件,以及需要執(zhí)行的方法
q.bind(el, EVENT_SCROLL, binding.value);
},
inserted: function(el, binding){
//To do, check whether element is scrollable and give warn message when not
},
update: function(el, binding){
// 如果綁定的函數(shù)和綁定之前的函數(shù)一致,則直接返回
if(binding.value === binding.oldValue){
return;
}
// 如果綁定的不是函數(shù),則報錯
if(!binding.value || typeof binding.value !== 'function'){
throw new Error('The scroll listener is not a function');
}
// 綁定新函數(shù),解綁舊函數(shù)
q.bind(el, EVENT_SCROLL, binding.value);
q.unbind(el, EVENT_SCROLL, binding.oldValue);
},
unbind: function(el, binding){
// 如果綁定的是函數(shù),則解綁,否則報錯
if(binding.value && typeof binding.value === 'function'){
q.unbind(el, EVENT_SCROLL, binding.oldValue);
}else{
throw new Error('The scroll listener is not a function');
}
}
})
}
}
export default scrollPlugin;
// 輸出
if(typeof module !== 'undefined' && typeof module.exports !== 'undefined'){
module.exports = scrollPlugin;
}else{
window.vScroll = scrollPlugin;
}
})()
該插件地址:https://github.com/wangpin34/vue-scroll
執(zhí)行順序:
- 通過 vue 指令牍疏,執(zhí)行指令中的 bind蠢笋,指令首次綁定元素時執(zhí)行,只執(zhí)行一次
- 該 bind 方法中有一個 q 實例執(zhí)行自己(構造函數(shù)中定義)的 bind 方法鳞陨,并傳遞了三個參數(shù)
- 構造函數(shù)的 bind 方法昨寞,給 listeners 和 funcs 分別添加對應的需要監(jiān)聽的元素和對應的方法,結構如下:
funcs = { scroll: [] }
listeners = [ { scroll: [] } ]
- 查看 funcs 是否有對應事件的方法厦滤,如果沒有援岩,則執(zhí)行 addListener
- 查看 element 是全局的元素如 document 之類的還是具體元素
- 獲取元素的 scrollTop 和 scrollLeft
- 執(zhí)行所有外部傳入的方法,由于此時 funcs 的 scroll 屬性還是空掏导,所以這時候并沒有執(zhí)行任何方法
- 給元素新增監(jiān)聽事件享怀,監(jiān)聽到事件的時候執(zhí)行外部傳入的方法。
- 將 funcs 的 scroll 賦值給 eventFuncs 并將外部方法傳遞到 eventFuncs 中趟咆。這樣eventFuncs 就有對應的事件類型和事件觸發(fā)時需要執(zhí)行的方法添瓷。
- 觸發(fā)事件,如 scroll 則會執(zhí)行對應方法值纱。
實測直接在元素上添加指令和對應的方法無效鳞贷,原因是滾動時無法觸發(fā) scroll 事件。因為視圖中只有 window 和 document 是固定不變的虐唠,其它元素相對于 window 是滾動的搀愧,換句話的意思就是滾動 window 內(nèi)部元素時觸發(fā)了 window 的 scroll 事件(表現(xiàn)為 window 出現(xiàn)了滾動條),但是其它元素相對于彼此之間是固定不變的,所以監(jiān)聽某一個元素的滾動是監(jiān)聽不到的咱筛。
解決方式很簡單搓幌,只需要控制元素的高度不變,內(nèi)部子元素超出父元素的高度眷蚓,設置父元素 overflow: auto 或者 scroll 即可鼻种。