/**
* @file events.js。事件系統(John Resig - JS 忍者的秘密 http://jsninja.com/)
*(原書版本不能完全使用,所以修復了一些東西並使 Closure Compiler 兼容)
* 這應該與 jQuery 的事件非常相似,但是它是基於書本版本的,它不像
* 像 jquery 一樣健壯,因此可能存在一些差異。
*
* @file events.js
* @模塊事件
*/
從 './dom-data' 導入 DomData;
從 './guid.js' 導入 * 作為 Guid;
從 './log.js' 導入日誌;
從“全局/窗口”導入窗口;
從“全局/文檔”導入文檔;
/**
* 清理監聽器緩存和調度器
*
* @param {元素|對象}元素
* 要清理的元素
*
* @param {string} 類型
* 要清理的事件類型
*/
函數_cleanUpEvents(元素,類型){
如果 (!DomData.has(elem)) {
返回;
}
const data = DomData.get(elem);
// 如果沒有剩餘,則刪除特定類型的事件
如果 (data.handlers[type].length === 0) {
刪除數據處理程序[類型];
// data.handlers[type] = null;
// 設置為 null 會導致 data.handlers 出錯
// 從元素中移除元處理程序
如果(elem.removeEventListener){
elem.removeEventListener(type, data.dispatcher, false);
} else if (elem.detachEvent) {
elem.detachEvent('on' + type, data.dispatcher);
}
}
// 如果沒有剩餘類型,則移除事件對象
如果 (Object.getOwnPropertyNames(data.handlers).length <= 0) {
刪除數據處理程序;
刪除 data.dispatcher;
刪除數據。禁用;
}
// 如果沒有剩餘數據,最後移除元素數據
如果 (Object.getOwnPropertyNames(data).length === 0) {
DomData.delete(elem);
}
}
/**
* 遍歷事件類型數組並為每種類型調用請求的方法。
*
* @param {函數} fn
* 我們要使用的事件方法。
*
* @param {元素|對象}元素
* 綁定監聽器的元素或對象
*
* @param {string} 類型
* 要綁定的事件類型。
*
* @param {EventTarget~EventListener} 回調
* 事件監聽器。
*/
函數 _handleMultipleEvents(fn、elem、類型、回調){
types.forEach(函數(類型){
// 為每種類型調用事件方法
fn(元素,類型,回調);
});
}
/**
* 修復本機事件以具有標準屬性值
*
* @param {Object} 事件
* 要修復的事件對象。
*
* @return {對象}
*固定事件對象。
*/
導出函數 fixEvent(事件){
如果(事件。固定_){
返回事件;
}
函數 returnTrue() {
返回真;
}
函數 returnFalse() {
返回假;
}
// 測試是否需要修復
// 用於檢查 !event.stopPropagation 而不是 isPropagationStopped
// 但原生事件為 stopPropagation 返回 true,但沒有
// 其他預期的方法,如 isPropagationStopped。好像有問題
// 使用 Javascript Ninja 代碼。所以我們現在只是覆蓋所有事件。
如果 (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) {
const 舊 = 事件 ||窗口事件;
事件={};
// 克隆舊對象,以便我們可以修改值 event = {};
// IE8 不喜歡你亂用本機事件屬性
// Firefox 為 event.hasOwnProperty('type') 和其他屬性返回 false
// 這使得複制更加困難。
// 去做:可能最好創建一個事件道具白名單
對於(舊的常量鍵){
// Safari 6.0.3 會在您嘗試複製已棄用的 layerX/Y 時發出警告
// 如果您嘗試複製已棄用的 keyboardEvent.keyLocation,Chrome 會警告您
// 和 webkitMovementX/Y
// 如果 Event.path 被複製,Lighthouse 會報錯
if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' &&
key !== 'webkitMovementX' && key !== 'webkitMovementY' &&
鍵!=='路徑'){
// 如果您嘗試複製已棄用的 returnValue,Chrome 32+ 會發出警告,但是
// 如果不支持 preventDefault (IE8),我們仍然想要。
如果 (!(key === 'returnValue' && old.preventDefault)) {
事件[鍵] = 舊[鍵];
}
}
}
// 事件發生在這個元素上
如果(!event.target){
event.target = event.srcElement ||文檔;
}
// 處理事件與哪個其他元素相關
如果(!event.relatedTarget){
event.relatedTarget = event.fromElement === event.target ?
事件.toElement:
事件.fromElement;
}
// 停止默認的瀏覽器操作
event.preventDefault = function() {
如果(舊的.preventDefault){
舊的.preventDefault();
}
event.returnValue = false;
old.returnValue = false;
event.defaultPrevented = true;
};
event.defaultPrevented = false;
// 阻止事件冒泡
event.stopPropagation = function() {
如果(舊的。停止傳播){
舊的停止傳播();
}
event.cancelBubble = true;
舊的.cancelBubble = true;
event.isPropagationStopped = returnTrue;
};
event.isPropagationStopped = returnFalse;
// 阻止事件冒泡並執行其他處理程序
event.stopImmediatePropagation = function() {
如果(舊的。stopImmediatePropagation){
old.stopImmediatePropagation();
}
event.isImmediatePropagationStopped = returnTrue;
事件.stopPropagation();
};
event.isImmediatePropagationStopped = returnFalse;
// 處理鼠標位置
如果(event.clientX !== null && event.clientX !== undefined){
const doc = document.documentElement;
const body = 文檔.body;
event.pageX = event.clientX +
(doc && doc.scrollLeft || body && body.scrollLeft || 0) -
(doc && doc.clientLeft || body && body.clientLeft || 0);
event.pageY = event.clientY +
(doc && doc.scrollTop || body && body.scrollTop || 0) -
(doc && doc.clientTop || body && body.clientTop || 0);
}
// 處理按鍵
event.which = event.charCode ||事件.keyCode;
// 修復鼠標點擊的按鈕:
// 0 == 左; 1 == 中間; 2 == 對
如果(事件。按鈕!==空&&事件。按鈕!==未定義){
// 下面是禁用的,因為它沒有通過videojs-standard
// 和...哎呀。
/* eslint 禁用 */
event.button = (event.button & 1 ?0 :
(事件按鈕 & 4 ?1 :
(事件按鈕 & 2 ?2 :0)));
/* eslint 啟用 */
}
}
event.fixed_ = true;
// 返回固定實例
返回事件;
}
/**
* 是否支持被動事件監聽器
*/
讓_supportsPassive;
const supportsPassive = function() {
如果(typeof _supportsPassive !== 'boolean'){
_supportsPassive = false;
嘗試{
const opts = Object.defineProperty({}, '被動', {
得到() {
_supportsPassive = 真;
}
});
window.addEventListener('test', null, opts);
window.removeEventListener('test', null, opts);
} 抓住 (e) {
// 無視
}
}
返回_supportsPassive;
};
/**
* Chrome 期望觸摸事件是被動的
*/
const passiveEvents = [
'觸摸啟動',
'觸摸移動'
];
/**
* 給元素添加一個事件監聽器
* 它將處理函數存儲在一個單獨的緩存對像中
* 並向元素的事件添加通用處理程序,
* 以及元素的唯一 ID (guid)。
*
* @param {元素|對象}元素
* 綁定監聽器的元素或對象
*
* @param {string|string[]} 類型
* 要綁定的事件類型。
*
* @param {EventTarget~EventListener} fn
* 事件監聽器。
*/
導出函數 on(elem, type, fn) {
如果(Array.isArray(類型)){
返回 _handleMultipleEvents(打開,元素,類型,fn);
}
如果 (!DomData.has(elem)) {
DomData.set(elem, {});
}
const data = DomData.get(elem);
// 我們需要一個地方來存儲我們所有的處理程序數據
如果(!data.handlers){
data.handlers = {};
}
如果(!數據處理程序[類型]){
data.handlers[類型] = [];
}
如果(!fn.guid){
fn.guid = Guid.newGUID();
}
data.handlers[type].push(fn);
如果(!data.dispatcher){
數據.disabled = false;
data.dispatcher = function(event, hash) {
如果(數據。禁用){
返回;
}
事件 = fixEvent(事件);
const handlers = data.handlers[event.type];
如果(處理程序){
// 複製處理程序,因此如果在此過程中添加/刪除處理程序,它不會丟棄所有內容。
const handlersCopy = handlers.slice(0);
對於(設 m = 0,n = handlersCopy.length;m < n;m++){
如果(事件。isImmediatePropagationStopped()){
休息;
}其他{
嘗試{
handlersCopy[m].call(elem, event, hash);
} 抓住 (e) {
日誌.錯誤(e);
}
}
}
}
};
}
如果 (data.handlers[type].length === 1) {
如果(elem.addEventListener){
讓選項=假;
如果(支持被動()&&
passiveEvents.indexOf(類型)> -1){
選項={被動:真};
}
elem.addEventListener(類型,data.dispatcher,選項);
} else if (elem.attachEvent) {
elem.attachEvent('on' + type, data.dispatcher);
}
}
}
/**
* 從元素中刪除事件監聽器
*
* @param {元素|對象}元素
* 從中刪除偵聽器的對象。
*
* @param {字符串|字符串[]} [類型]
* 要刪除的偵聽器類型。不包括從元素中刪除所有事件。
*
* @param {EventTarget~EventListener} [fn]
* 要刪除的特定偵聽器。不包含以刪除事件的偵聽器
* 類型。
*/
導出函數 off(elem, type, fn) {
// 如果不需要,不想通過getElData添加緩存對象
如果 (!DomData.has(elem)) {
返回;
}
const data = DomData.get(elem);
// 如果不存在任何事件,則無需解除綁定
如果(!data.handlers){
返回;
}
如果(Array.isArray(類型)){
返回 _handleMultipleEvents(關閉,elem,類型,fn);
}
// 實用功能
const removeType = function(el, t) {
data.handlers[t] = [];
_cleanUpEvents(el, t);
};
// 我們要刪除所有綁定事件嗎?
如果(類型===未定義){
for (const t in data.handlers) {
如果 (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
移除類型(元素,t);
}
}
返回;
}
const handlers = data.handlers[類型];
// 如果不存在處理程序,則無需解除綁定
如果(!處理程序){
返回;
}
// 如果沒有提供監聽器,移除所有類型的監聽器
如果(!fn){
刪除類型(元素,類型);
返回;
}
// 我們只刪除一個處理程序
如果(fn.guid){
for (let n = 0; n < handlers.length; n++) {
如果(處理程序[n].guid === fn.guid){
handlers.splice(n--, 1);
}
}
}
_cleanUpEvents(元素,類型);
}
/**
* 為元素觸發事件
*
* @param {元素|對象}元素
* 觸發事件的元素
*
* @param {EventTarget~Event|string} 事件
* 字符串(類型)或具有類型屬性的事件對象
*
* @param {對象} [哈希]
* 隨事件一起傳遞的數據散列
*
* @return {布爾值|未定義}
* 如果默認是,則返回 `defaultPrevented` 的反義詞
* 阻止。否則,返回 `undefined`
*/
導出函數觸發器(元素,事件,散列){
// 獲取元素數據和對父級的引用(用於冒泡)。
// 不想為每個父級添加一個數據對象來緩存,
// 所以先檢查 hasElData。
const elemData = DomData.has(elem) ?DomData.get(元素) : {};
const parent = elem.parentNode || elem.ownerDocument;
// type = event.type ||事件,
//處理程序;
// 如果事件名稱作為字符串傳遞,則從中創建一個事件
如果(事件類型==='字符串'){
事件 = {類型:事件,目標:元素};
} else if (!event.target) {
event.target = elem;
}
// 規範化事件屬性。
事件 = fixEvent(事件);
// 如果傳遞的元素有調度程序,則執行已建立的處理程序。
如果(elemData.dispatcher){
elemData.dispatcher.call(elem, event, hash);
}
// 除非明確停止或事件不冒泡(例如媒體事件)
// 遞歸調用此函數以將事件冒泡到 DOM 中。
如果(父母&&!event.isPropagationStopped()&& event.bubbles === true){
trigger.call(null, parent, event, hash);
// 如果在 DOM 的頂部,除非禁用,否則觸發默認操作。
} else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
如果 (!DomData.has(event.target)) {
DomData.set(event.target, {});
}
const targetData = DomData.get(event.target);
// 檢查目標是否有針對此事件的默認操作。
如果(事件目標[事件類型]){
// 暫時禁用目標上的事件調度,因為我們已經執行了處理程序。
targetData.disabled = true;
// 執行默認操作。
如果 (typeof event.target[event.type] === '函數') {
事件目標[事件類型]();
}
// 重新啟用事件調度。
targetData.disabled = false;
}
}
// 如果通過返回 false 來阻止默認設置,則通知觸發器
返回!event.defaultPrevented;
}
/**
* 一個事件只觸發一次監聽器。
*
* @param {元素|對象}元素
* 要綁定到的元素或對象。
*
* @param {string|string[]} 類型
* 活動名稱/類型
*
* @param {Event~EventListener} fn
* 事件監聽函數
*/
導出函數 one(elem, type, fn) {
如果(Array.isArray(類型)){
返回 _handleMultipleEvents(一個,元素,類型,fn);
}
const func = function() {
關閉(元素,類型,功能);
fn.apply(這個,參數);
};
// 將 guid 複製到新函數,以便使用原始函數的 ID 刪除它
func.guid = fn.guid = fn.guid || Guid.newGUID();
on(elem, type, func);
}
/**
* 只觸發一個監聽器一次然後關閉
* 配置的事件
*
* @param {元素|對象}元素
* 要綁定到的元素或對象。
*
* @param {string|string[]} 類型
* 活動名稱/類型
*
* @param {Event~EventListener} fn
* 事件監聽函數
*/
導出函數 any(elem, type, fn) {
const func = function() {
關閉(元素,類型,功能);
fn.apply(這個,參數);
};
// 將 guid 複製到新函數,以便使用原始函數的 ID 刪除它
func.guid = fn.guid = fn.guid || Guid.newGUID();
// 多次開啟,但一次關閉
on(elem, type, func);
}