事件對(duì)象
- 在我們注冊(cè)事件后,并不會(huì)直接調(diào)用這個(gè)匿名函數(shù),而是觸發(fā)這個(gè)事件時(shí)才執(zhí)行內(nèi)部代碼。
- 所以真正調(diào)用匿名函數(shù)的是瀏覽器。瀏覽器在調(diào)用時(shí)會(huì)傳入一個(gè)對(duì)象參數(shù)(event),哪怕我們沒寫形參,也是會(huì)傳入的。這個(gè)就叫作事件對(duì)象。
- 事件對(duì)象內(nèi)部存儲(chǔ)的是一系列與該事件有關(guān)系的屬性值。所以如果想獲取事件對(duì)象,在給匿名函數(shù)寫一個(gè)形參就可以。
獲取事件對(duì)象兼容寫法
- 之前已經(jīng)說過了,通過形參可以直接獲取到事件對(duì)象,但是這種方法主流瀏覽器支持,ie678不支持,所以一般推薦使用兼容的方法。
document.onclick = function (event) {
var event = event || window.event;
}; - 上面是隨便注冊(cè)了一個(gè)事件作為例子,設(shè)置一個(gè)形參,并在函數(shù)內(nèi)部聲明一個(gè)event變量來存放事件對(duì)象。
- 寫或的原因是使用到了它的短路判斷,如果第一個(gè)值為true,說明瀏覽器支持這種方法,根據(jù)或的短路原理,第一個(gè)為true直接返回該值。如果第一個(gè)不為true才繼續(xù)往下判斷。
三個(gè)重要坐標(biāo)
-
事件對(duì)象.screenX & 事件對(duì)象.screenY獲取鼠標(biāo)在屏幕中的坐標(biāo) -
事件對(duì)象.clientX & 事件對(duì)象.clientY獲取鼠標(biāo)在窗口中的坐標(biāo) -
事件對(duì)象.pageX & 事件對(duì)象.pageY獲取鼠標(biāo)在頁面中的坐標(biāo) - clientXY和pageXY的區(qū)別在于:如果頁面很長,那么pageXY會(huì)以整個(gè)頁面來計(jì)算坐標(biāo),而clientXY則是只會(huì)根據(jù)當(dāng)前的顯示窗口來計(jì)算坐標(biāo)。
鼠標(biāo)在頁面中坐標(biāo)的兼容寫法
- 也是ie678不支持pageX和pageY,但是ie678卻支持clientXY,所以建議使用以下兼容寫法:
pageX = event.pageX || event.clientX + document.documentElement.scrollLeft //Y軸同理 - 獲取鼠標(biāo)在窗口中的位置,再加上被卷去的部分,也可以得到鼠標(biāo)在頁面中的坐標(biāo)
鼠標(biāo)跟隨案例總結(jié)
- 比較簡單的一個(gè)案例,主要就是獲取事件對(duì)象中的pageX和pageY值,并賦給添加了定位元素的left和top,就能實(shí)現(xiàn)鼠標(biāo)跟隨效果
- 有一個(gè)稍微要注意的是,如果直接賦值那么元素在跟隨的時(shí)候,是左上角對(duì)齊鼠標(biāo)坐標(biāo)。如果想要讓元素中心對(duì)齊鼠標(biāo)坐標(biāo),要減去元素自身高和寬分別的一半。
獲取鼠標(biāo)在盒子中的坐標(biāo)
- 首先要知道,鼠標(biāo)在盒子中的坐標(biāo)是不能直接獲取的,需要通過計(jì)算。
- 計(jì)算的原理,用鼠標(biāo)在頁面中的坐標(biāo)值,減去盒子本身到body兩側(cè)的距離,那么剩下的就是鼠標(biāo)在盒子中的坐標(biāo)值
- 下面例子做一個(gè)參考:
boxX = pageX - box.offsetLeft;
boxY = pageY - box.offsetTop; - 同時(shí)本節(jié)又講到了一個(gè)新的事件,
onmousemove鼠標(biāo)移動(dòng)事件
放大鏡案例總結(jié)
- 這個(gè)案例實(shí)際上運(yùn)用到了,上面講的幾個(gè)知識(shí)點(diǎn),包括獲取鼠標(biāo)在盒子內(nèi)坐標(biāo),設(shè)置鼠標(biāo)跟隨盒子。
- 有兩個(gè)重點(diǎn):
- 一是在盒子內(nèi)部設(shè)置鼠標(biāo)跟隨盒子時(shí),要限制跟隨盒子可以移動(dòng)的范圍,不能讓其超出盒子
- 二是讓大圖同步移動(dòng)時(shí),比例的計(jì)算問題。當(dāng)然比例不是一定要計(jì)算,直接給一個(gè)對(duì)應(yīng)的比值也行,不過為了精確顯示,建議還是要計(jì)算出來。公式就是:
大圖應(yīng)該設(shè)置坐標(biāo) = rate(比例) * 跟隨圖坐標(biāo)。rate的公式為:rate = 大圖可以移動(dòng)總距離 / 跟隨圖可以移動(dòng)總距離
- 詳細(xì)代碼參考案例,更容易理解
鼠標(biāo)拖拽案例總結(jié)
- 這個(gè)案例的重點(diǎn)在于:當(dāng)鼠標(biāo)移動(dòng)時(shí),如果不計(jì)算那么默認(rèn)盒子左上角跟隨鼠標(biāo)移動(dòng),這明顯不是我們想要的效果。
- 所以需要計(jì)算盒子應(yīng)該移動(dòng)的距離,首先要計(jì)算鼠標(biāo)在按下時(shí),在盒子的內(nèi)的坐標(biāo),然后再用鼠標(biāo)在頁面中的坐標(biāo)減去盒子的坐標(biāo),就可以讓鼠標(biāo)按下的那個(gè)點(diǎn)一直對(duì)準(zhǔn)鼠標(biāo)進(jìn)行跟隨。
- 實(shí)際上就是在移動(dòng)時(shí),默認(rèn)先讓盒子移動(dòng)了鼠標(biāo)在盒子中坐標(biāo)的距離,然后再跟隨鼠標(biāo)移動(dòng),這樣看起來就不是左上角跟隨了。
- 并且涉及到了清除選中文字狀態(tài)的代碼:
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
滾動(dòng)條案例總結(jié)
- 滾動(dòng)條案例邏輯其實(shí)是結(jié)合了上面的兩個(gè)案例,有以下幾個(gè)重點(diǎn)
- 滾動(dòng)條的長度不是定值,需要通過公式來計(jì)算:
滾動(dòng)條高度/側(cè)邊欄高度 = 內(nèi)容顯示高度/內(nèi)容總高度 - 想要鼠標(biāo)拖動(dòng)時(shí)不是滾動(dòng)條頂部對(duì)齊跟隨移動(dòng),就要求到鼠標(biāo)在滾動(dòng)條內(nèi)的坐標(biāo),用
鼠標(biāo)在頁面中的坐標(biāo)-盒子到頂部的距離-滾動(dòng)條到盒子頂部的距離,就可以得到。然后用鼠標(biāo)在盒子中的坐標(biāo)-鼠標(biāo)在滾動(dòng)條中的坐標(biāo),讓滾動(dòng)條移動(dòng)時(shí)默認(rèn)先往上走這個(gè)距離,就可以實(shí)現(xiàn)滾動(dòng)條根據(jù)鼠標(biāo)點(diǎn)擊的那個(gè)點(diǎn)來跟隨移動(dòng) - 要讓內(nèi)容根據(jù)滾動(dòng)條移動(dòng)的比例來移動(dòng),就要根據(jù)這個(gè)公式:
內(nèi)容應(yīng)該移動(dòng)的位置/滾動(dòng)條當(dāng)前的位置 = 內(nèi)容能夠移動(dòng)的總距離/滾動(dòng)條能夠移動(dòng)的總距離
瀑布流案例總結(jié)
- 瀑布流案例的幾個(gè)重點(diǎn):
- 求出每行剩余空白寬度后,還要除以列數(shù)-1,平均分配空白間距
- 第一行布局不需要設(shè)置top值
- left設(shè)置時(shí),
值 = (圖片寬度 + 間距) * (對(duì)應(yīng)索引||最小高度列索引值),這樣才能讓圖片的位置在其對(duì)應(yīng)列或者最小高度列上 - 第二行開始要把圖片放到高度最小的那列上,所以需要一個(gè)數(shù)組來存放每列的高度,然后比較數(shù)組中的值,找出高度最小的那列。
- 每次把圖片放到高度最小那列后,還要給那列的高度值重新賦值,
值 = 原高度+新圖片高度+間距 - 瀑布流需要瀏覽器滾動(dòng)事件
window.onscroll,為了避免出現(xiàn)滾動(dòng)時(shí)出現(xiàn)空白影響效果,所以在追加圖片時(shí),判斷條件為:窗口可視區(qū)高度 + 頁面被卷去高度 > 最小高度列的高度值,一旦大于肯定會(huì)出現(xiàn)空白,所以就要追加圖片,并再次調(diào)用瀑布流布局函數(shù)
事件冒泡
- 在一個(gè)對(duì)象上觸發(fā)某類事件(比如單擊onclick事件),如果此對(duì)象定義了此事件的處理程序,那么此事件就會(huì)調(diào)用這個(gè)處理程序,如果沒有定義此事件處理程序或者事件返回true,那么這個(gè)事件會(huì)向這個(gè)對(duì)象的父級(jí)對(duì)象傳播,從里到外,直至它被處理(父級(jí)對(duì)象所有同類事件都將被激活),或者它到達(dá)了對(duì)象層次的最頂層,即document對(duì)象(有些瀏覽器是window)。正常情況下這個(gè)不會(huì)有什么影響
- 但是也有特例,比如鼠標(biāo)離開事件,如果父元素注冊(cè)了鼠標(biāo)事件,子元素沒有注冊(cè)。當(dāng)鼠標(biāo)離開子元素時(shí),雖然沒有注冊(cè)事件不會(huì)執(zhí)行,但是該事件確實(shí)觸發(fā)了就會(huì)冒泡,到上級(jí)發(fā)現(xiàn)父元素注冊(cè)了。就會(huì)執(zhí)行父元素注冊(cè)的鼠標(biāo)離開事件,這顯然是不行的
阻止事件冒泡
- 為了避免事件冒泡而產(chǎn)生的一些不可控結(jié)果,我們需要阻止事件冒泡,要阻止誰的事件冒泡,就在讓該元素調(diào)用阻止冒泡的方法。
event.stopPropagation(); - 調(diào)用這個(gè)方法,需要先獲取事件對(duì)象來配合
阻止事件冒泡兼容寫法
- 寫法如下
if( event.stopPropagation ) {
event.stopPropagation();
} else {
event.cancelBubble = true;
} - 如果調(diào)用stopPropagation方法有返回值,說明瀏覽器支持,如果為空說明瀏覽器不支持,那就設(shè)置cancelBubble屬性。一般只有ie678不支持stopPropagation方法
獲取事件目標(biāo)
-
var target = event.target || event.srcElement可以獲取事件的源目標(biāo),就是說由哪個(gè)元素觸發(fā)了這個(gè)事件,就獲取到哪個(gè)元素,target.id還能獲取該元素的id值
事件監(jiān)聽器
- 如果使用之前的事件注冊(cè)方法,那么同一個(gè)元素相同的事件會(huì)相互覆蓋,一般只會(huì)保留后寫注冊(cè)的事件
- 但是如果使用事件監(jiān)聽器的方法來注冊(cè)事件,那么即使是同一元素的相同事件也不會(huì)覆蓋,而是根據(jù)注冊(cè)順序依次執(zhí)行
- 主流瀏覽器的寫法:
元素.addEventListener([事件名字符串,注意這里事件名不加on],[事件處理程序function],[是否捕獲true||false]); - ie678的寫法:
元素.attachEvent([事件名字符串,注意這里事件名要加on],[事件處理程序function]); - 捕獲參數(shù)詳解:如果傳入的參數(shù)是false,就是不使用捕獲,那么事件的執(zhí)行順序就是默認(rèn)順序冒泡,從內(nèi)到外;如果傳入true使用捕獲,那么瀏覽器會(huì)從外到內(nèi)先捕獲事件源,找到事件源后,再執(zhí)行冒泡。捕獲就是從外內(nèi)到尋找的過程。
監(jiān)聽器移除事件方法
- 普通注冊(cè)事件的方式,想要移除事件,利用事件會(huì)相互覆蓋的原理,重新注冊(cè)該事件并賦值為空,就能移除。但是如果是使用監(jiān)聽器來注冊(cè)的事件,那么需要用到以下方法:
- 主流瀏覽器:
元素.removeEventListener()[事件名字符串,注意這里事件名不加on],[處理函數(shù)名],[是否捕獲true||false]); - ie678
元素.detachEvent([事件名字符串,注意這里事件名要加on],[處理函數(shù)名]); - 從代碼可以看出,如果要移除事件,需要傳入事件處理函數(shù)的名稱,所以如果使用監(jiān)聽器注冊(cè)函數(shù),并且在之后想要移除,那么建議不要用匿名函數(shù),直接在外部寫好傳入函數(shù)名稱來注冊(cè),也方便后面移除
事件階段
- 事件階段也叫做事件流模型。通過
event.phase可以查看事件的各個(gè)階段 - 一個(gè)完整的事件三階段:從外到內(nèi)捕獲階段-事件目標(biāo)執(zhí)行階段-事件冒泡階段
- 但是傳統(tǒng)注冊(cè)事件方法,和監(jiān)聽器注冊(cè)事件方法傳入false參數(shù),它都是跳過了捕獲階段,直接找到事件源執(zhí)行再事件冒泡,目前捕獲階段我們很少接觸到