本文最初發表於 "博客園" ,併在 "GitHub" 上持續更新 前端的系列文章 。歡迎在GitHub上關註我,一起入門和進階前端。 以下是正文。 綁定事件的兩種方式 我們在上一篇文章 "DOM操作詳解" 中已經講過事件的概念。這裡講一下註冊事件的兩種方式,我們以onclick事件為例。 方式一:o ...
以下是正文。
綁定事件的兩種方式
我們在上一篇文章 DOM操作詳解 中已經講過事件的概念。這裡講一下註冊事件的兩種方式,我們以onclick事件為例。
方式一:onclick
舉例:
<body>
<button>點我</button>
<script>
var btn = document.getElementsByTagName("button")[0];
//這種事件綁定的方法容易被層疊。
btn.onclick = function () {
console.log("事件1");
}
btn.onclick = function () {
console.log("事件2");
}
</script>
</body>
點擊按鈕後,上方代碼的列印結果:
事件2
我們可以看到,這種綁定事件的方式,會層疊掉之前的事件。
方式二:addEventListener
addEventListener()里的參數:
參數1:事件名(註意,沒有on)
參數2:事件名(執行函數)
參數3:事件名(捕獲或者冒泡)
舉例:
<body>
<button>按鈕</button>
<script>
var btn = document.getElementsByTagName("button")[0];
//addEventListener: 事件監聽器。 原事件被執行的時候,後面綁定的事件照樣被執行
//第二種事件綁定的方法不會出現層疊。(更適合團隊開發)
btn.addEventListener("click", fn1);
btn.addEventListener("click", fn2);
function fn1() {
console.log("事件1");
}
function fn2() {
console.log("事件2");
}
</script>
</body>
點擊按鈕後,上方代碼的列印結果:
事件1
事件2
我們可以看到,這種綁定事件的方式,不會層疊掉之前的事件。
事件對象
在觸發DOM上的某個事件時,會產生一個事件對象event,這個對象中包含著所有與事件有關的信息。比如滑鼠操作時候,會添加滑鼠位置的相關信息到事件對象中。
所有瀏覽器都支持event對象,但支持的方式不同。如下。
(1)普通瀏覽器支持 event。比如:
(2)ie 678 支持 window.event。
於是,我們可以採取一種相容性的寫法。如下:
event = event || window.event; ////相容性寫法
代碼舉例:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script>
//點擊頁面的任何部分
document.onclick = function (event) {
event = event || window.event; ////相容性寫法
console.log(event);
console.log(event.timeStamp);
console.log(event.bubbles);
console.log(event.button);
console.log(event.pageX);
console.log(event.pageY);
console.log(event.screenX);
console.log(event.screenY);
console.log(event.target);
console.log(event.type);
console.log(event.clientX);
console.log(event.clientY);
}
</script>
</body>
</html>
event 屬性
event 有很多屬性,比如:
由於pageX 和 pageY的相容性不好,我們可以這樣做:
- 滑鼠在頁面的位置 = 被捲去的部分+可視區域部分。
Event舉例
舉例1:滑鼠跟隨
代碼實現:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
body {
height: 5000px;
}
img {
position: absolute;
padding: 10px 0;
border: 1px solid #ccc;
cursor: pointer;
background-color: yellowgreen;
}
</style>
</head>
<body>
<img src="" width="100" height="100"/>
<script>
//需求:點擊頁面的任何地方,圖片跟隨滑鼠移動到點擊位置。
//思路:獲取滑鼠在頁面中的位置,然圖片緩慢運動到滑鼠點擊的位置。
// 相容ie67做pageY和pageX;
// 原理: 滑鼠在頁面的位置 = 被捲去的部分+可視區域部分。
//步驟:
//1.老三步。
//2.獲取滑鼠在頁面中的位置。
//3.利用緩動原理,慢慢的運動到指定位置。(包括左右和上下)
//1.老三步。
var img = document.getElementsByTagName("img")[0];
var timer = null;
var targetx = 0;
var targety = 0;
var leaderx = 0;
var leadery = 0;
//給整個文檔綁定點擊事件獲取滑鼠的位置。
document.onclick = function (event) {
//新五步
//相容獲取事件對象
event = event || window.event;
//滑鼠在頁面的位置 = 被捲去的部分+可視區域部分。
var pagey = event.pageY || scroll().top + event.clientY;
var pagex = event.pageX || scroll().left + event.clientX;
targety = pagey - 30;
targetx = pagex - 50;
//要用定時器,先清定時器
clearInterval(timer);
timer = setInterval(function () {
//為盒子的位置獲取值
leaderx = img.offsetLeft;
//獲取步長
var stepx = (targetx - leaderx) / 10;
//二次處理步長
stepx = stepx > 0 ? Math.ceil(stepx) : Math.floor(stepx);
leaderx = leaderx + stepx;
//賦值
img.style.left = leaderx + "px";
//為盒子的位置獲取值
leadery = img.offsetTop;
//獲取步長
var stepy = (targety - leadery) / 10;
//二次處理步長
stepy = stepy > 0 ? Math.ceil(stepy) : Math.floor(stepy);
leadery = leadery + stepy;
//賦值
img.style.top = leadery + "px";
//清定時器
if (Math.abs(targety - img.offsetTop) <= Math.abs(stepy) && Math.abs(targetx - img.offsetLeft) <= Math.abs(stepx)) {
img.style.top = targety + "px";
img.style.left = targetx + "px";
clearInterval(timer);
}
}, 30);
}
</script>
</body>
</html>
實現效果:
event應用舉例:獲取滑鼠距離所在盒子的距離
關鍵點:
滑鼠距離所在盒子的距離 = 滑鼠在整個頁面的位置 - 所在盒子在整個頁面的位置
代碼演示:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
.box {
width: 300px;
height: 200px;
padding-top: 100px;
background-color: pink;
margin: 100px;
text-align: center;
font: 18px/30px "simsun";
cursor: pointer;
}
</style>
</head>
<body>
<div class="box">
</div>
<script src="animate.js"></script>
<script>
//需求:滑鼠進入盒子之後只要移動,哪怕1像素,隨時顯示滑鼠在盒子中的坐標。
//技術點:新事件,onmousemove:在事件源上,哪怕滑鼠移動1像素也會觸動這個事件。
//一定程度上,模擬了定時器
//步驟:
//1.老三步和新五步
//2.獲取滑鼠在整個頁面的位置
//3.獲取盒子在整個頁面的位置
//4.用滑鼠的位置減去盒子的位置賦值給盒子的內容。
//1.老三步和新五步
var div = document.getElementsByTagName("div")[0];
div.onmousemove = function (event) {
event = event || window.event;
//2.獲取滑鼠在整個頁面的位置
var pagex = event.pageX || scroll().left + event.clientX;
var pagey = event.pageY || scroll().top + event.clientY;
//3.獲取盒子在整個頁面的位置
// var xx =
// var yy =
//4.用滑鼠的位置減去盒子的位置賦值給盒子的內容。
var targetx = pagex - div.offsetLeft;
var targety = pagey - div.offsetTop;
this.innerHTML = "滑鼠在盒子中的X坐標為:" + targetx + "px;<br>滑鼠在盒子中的Y坐標為:" + targety + "px;"
}
</script>
</body>
</html>
實現效果:
舉例:商品放大鏡
代碼實現:
(1)index.html:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
* {
margin: 0;
padding: 0;
}
.box {
width: 350px;
height: 350px;
margin: 100px;
border: 1px solid #ccc;
position: relative;
}
.big {
width: 400px;
height: 400px;
position: absolute;
top: 0;
left: 360px;
border: 1px solid #ccc;
overflow: hidden;
display: none;
}
/*mask的中文是:遮罩*/
.mask {
width: 175px;
height: 175px;
background: rgba(255, 255, 0, 0.4);
position: absolute;
top: 0;
left: 0;
cursor: move;
display: none;
}
.small {
position: relative;
}
img {
vertical-align: top;
}
</style>
<script src="tools.js"></script>
<script>
window.onload = function () {
//需求:滑鼠放到小盒子上,讓大盒子裡面的圖片和我們同步等比例移動。
//技術點:onmouseenter==onmouseover 第一個不冒泡
//技術點:onmouseleave==onmouseout 第一個不冒泡
//步驟:
//1.滑鼠放上去顯示盒子,移開隱藏盒子。
//2.老三步和新五步(黃盒子跟隨移動)
//3.右側的大圖片,等比例移動。
//0.獲取相關元素
var box = document.getElementsByClassName("box")[0];
var small = box.firstElementChild || box.firstChild;
var big = box.children[1];
var mask = small.children[1];
var bigImg = big.children[0];
//1.滑鼠放上去顯示盒子,移開隱藏盒子。(為小盒子綁定事件)
small.onmouseenter = function () {
//封裝好方法調用:顯示元素
show(mask);
show(big);
}
small.onmouseleave = function () {
//封裝好方法調用:隱藏元素
hide(mask);
hide(big);
}
//2.老三步和新五步(黃盒子跟隨移動)
//綁定的事件是onmousemove,而事件源是small(只要在小盒子上移動1像素,黃盒子也要跟隨)
small.onmousemove = function (event) {
//新五步
event = event || window.event;
//想要移動黃盒子,必須要知道滑鼠在small小圖中的位置。
var pagex = event.pageX || scroll().left + event.clientX;
var pagey = event.pageY || scroll().top + event.clientY;
//x:mask的left值,y:mask的top值。
var x = pagex - box.offsetLeft - mask.offsetWidth / 2; //除以2,可以保證滑鼠mask的中間
var y = pagey - box.offsetTop - mask.offsetHeight / 2;
//限制換盒子的範圍
//left取值為大於0,小盒子的寬-mask的寬。
if (x < 0) {
x = 0;
}
if (x > small.offsetWidth - mask.offsetWidth) {
x = small.offsetWidth - mask.offsetWidth;
}
//top同理。
if (y < 0) {
y = 0;
}
if (y > small.offsetHeight - mask.offsetHeight) {
y = small.offsetHeight - mask.offsetHeight;
}
//移動黃盒子
console.log(small.offsetHeight);
mask.style.left = x + "px";
mask.style.top = y + "px";
//3.右側的大圖片,等比例移動。
//如何移動大圖片?等比例移動。
// 大圖片/大盒子 = 小圖片/mask盒子
// 大圖片走的距離/mask走的距離 = (大圖片-大盒子)/(小圖片-黃盒子)
// var bili = (bigImg.offsetWidth-big.offsetWidth)/(small.offsetWidth-mask.offsetWidth);
//大圖片走的距離/mask盒子都的距離 = 大圖片/小圖片
var bili = bigImg.offsetWidth / small.offsetWidth;
var xx = bili * x; //知道比例,就可以移動大圖片了
var yy = bili * y;
bigImg.style.marginTop = -yy + "px";
bigImg.style.marginLeft = -xx + "px";
}
}
</script>
</head>
<body>
<div class="box">
<div class="small">
<img src="images/001.jpg" alt=""/>
<div class="mask"></div>
</div>
<div class="big">
<img src="images/0001.jpg" alt=""/>
</div>
</div>
</body>
</html>
(2)tools.js:
/**
* Created by smyhvae on 2018/02/03.
*/
//顯示和隱藏
function show(ele) {
ele.style.display = "block";
}
function hide(ele) {
ele.style.display = "none";
}
function scroll() { // 開始封裝自己的scrollTop
if (window.pageYOffset != null) { // ie9+ 高版本瀏覽器
// 因為 window.pageYOffset 預設的是 0 所以這裡需要判斷
return {
left: window.pageXOffset,
top: window.pageYOffset
}
}
else if (document.compatMode === "CSS1Compat") { // 標準瀏覽器 來判斷有沒有聲明DTD
return {
left: document.documentElement.scrollLeft,
top: document.documentElement.scrollTop
}
}
return { // 未聲明 DTD
left: document.body.scrollLeft,
top: document.body.scrollTop
}
}
效果演示:
事件冒泡
事件傳播的三個階段是:事件捕獲、事件冒泡和目標。
事件捕獲階段:事件從最上一級標簽開始往下查找,直到捕獲到事件目標 target。(從祖先元素往子元素查找,DOM樹結構)。在這個過程中,事件相應的監聽函數是不會被觸發的。
事件目標:當到達目標元素之後,執行目標元素該事件相應的處理函數。如果沒有綁定監聽函數,那就不執行。
事件冒泡階段:事件從事件目標 target 開始,往上冒泡直到頁面的最上一級標簽。(從子元素到祖先元素冒泡)
如下圖所示:
PS:這個概念類似於 Android 里的 touch 事件傳遞。
事件冒泡的概念
事件冒泡: 當一個元素上的事件被觸發的時候(比如說滑鼠點擊了一個按鈕),同樣的事件將會在那個元素的所有祖先元素中被觸發。這一過程被稱為事件冒泡;這個事件從原始元素開始一直冒泡到DOM樹的最上層。
通俗來講,冒泡指的是:子元素的事件被觸發時,父盒子的同樣的事件也會被觸發。取消冒泡就是取消這種機制。
代碼演示:
//事件冒泡
box3.onclick = function () {
alert("child");
}
box2.onclick = function () {
alert("father");
}
box1.onclick = function () {
alert("grandfather");
}
document.onclick = function () {
alert("body");
}
上圖顯示,當我點擊兒子 box3的時候,它的父親box2、box1、body都依次被觸發了。即使我改變代碼的順序,也不會影響效果的順序。
冒泡順序:
一般的瀏覽器: (除IE6.0之外的瀏覽器)
- div -> body -> html -> document -> window
IE6.0:
- div -> body -> html -> document
事件捕獲
addEventListener可以捕獲事件:
box1.addEventListener("click", function () {
alert("捕獲 box3");
}, true);
上面的方法中,參數為true,代表捕獲;參數為false或者不寫參數,代表冒泡。
代碼演示:
//參數為true,代表捕獲;參數為false或者不寫參數,代表冒泡
box3.addEventListener("click", function () {
alert("捕獲 child");
}, true);
box2.addEventListener("click", function () {
alert("捕獲 father");
}, true);
box1.addEventListener("click", function () {
alert("捕獲 grandfather");
}, true);
document.addEventListener("click", function () {
alert("捕獲 body");
}, true);
效果演示:
不是所有的事件都能冒泡
以下事件不冒泡:blur、focus、load、unload、onmouseenter、onmouseleave。意思是,事件不會往父元素那裡傳遞。
我們檢查一個元素是否會冒泡,可以通過事件的以下參數:
event.bubbles
如果返回值為true,說明該事件會冒泡;反之則相反。
舉例:
box1.onclick = function (event) {
alert("冒泡 child");
event = event || window.event;
console.log(event.bubbles); //列印結果:true
}
阻止冒泡的方法
w3c的方法:(火狐、谷歌、IE11)
event.stopPropagation();
IE10以下則是:
event.cancelBubble = true
相容代碼如下:
box3.onclick = function (event) {
alert("child");
//阻止冒泡
event = event || window.event;
if (event && event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
上方代碼中,我們對box3進行了阻止冒泡,產生的效果是:事件不會繼續傳遞到 father、grandfather、body了。
事件委托
事件委托,通俗地來講,就是把一個元素響應事件(click、keydown......)的函數委托到另一個元素。
比如說有一個列表 ul,列表之中有大量的列表項 li,我們需要在點擊列表項li的時候響應一個事件。如果給每個列表項一一都綁定一個函數,那對於記憶體消耗是非常大的,效率上需要消耗很多性能。
因此,比較好的方法就是把這個點擊事件綁定到他的父層,也就是 ul
上,然後在執行事件的時候再去匹配判斷目標元素。
所以事件委托可以減少大量的記憶體消耗,節約效率。
事件委托的參考鏈接:JavaScript 事件委托詳解
我的公眾號
想學習代碼之外的軟技能?不妨關註我的微信公眾號:生命團隊(id:vitateam
)。
掃一掃,你將發現另一個全新的世界,而這將是一場美麗的意外: