JavaScript:事件對象Event和冒泡

来源:https://www.cnblogs.com/smyhvae/archive/2018/02/04/8413602.html
-Advertisement-
Play Games

本文最初發表於 "博客園" ,併在 "GitHub" 上持續更新 前端的系列文章 。歡迎在GitHub上關註我,一起入門和進階前端。 以下是正文。 綁定事件的兩種方式 我們在上一篇文章 "DOM操作詳解" 中已經講過事件的概念。這裡講一下註冊事件的兩種方式,我們以onclick事件為例。 方式一:o ...


本文最初發表於博客園,併在GitHub上持續更新前端的系列文章。歡迎在GitHub上關註我,一起入門和進階前端。

以下是正文。

綁定事件的兩種方式

我們在上一篇文章 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)。

掃一掃,你將發現另一個全新的世界,而這將是一場美麗的意外:


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我悟出權力本來就是不講理的——蟑螂就是海米;也悟出要造反,內心必須強大到足以承受任何後果才行。 ——北島《城門開》 本文為讀 lodash 源碼的第十篇,後續文章會更新到這個倉庫中,歡迎 star: "pocket lodash" gitbook也會同步倉庫的更新,gitbook地址: "pocke ...
  • css框模型 (一)內邊距 padding 內邊距即邊框和內容之間的空白區域h1 {padding: 10px 0.25em 2ex 20%;}四個值這種則分別是按照上,右,下,左的順序設置內邊距 單邊內邊距屬性padding-toppadding-rightpadding-bottompaddin ...
  • 本文最初發表於 "博客園" ,併在 "GitHub" 上持續更新 前端的系列文章 。歡迎在GitHub上關註我,一起入門和進階前端。 以下是正文。 jQuery 的介紹 引入 jQuery 的原因 在用 js 寫代碼時,會遇到一些問題: window.onload 事件有事件覆蓋的問題,因此只能寫一 ...
  • Chrome:老哥我當初開始搞的時候實力不夠,只能偷蘋果的WebKit過來用著先,但現在翅膀硬了,想自己開發一款blink內核,儘早擺脫蘋果的影子; Safari:獨一無二的高貴,WebKit,我的原創發明! FireFox:原創Gecko,獨一無二,爵士人生! Opera:我唯谷歌爹馬首是瞻,現在 ...
  • 說到這個地方又想起以前高中還是初中學的《孔乙己》這個梗,但是這裡的this顯然實用性比那個要大很多,哈哈。 簡單來說,this有四種應用場景,分別是在構造函數上、對象屬性中、普通函數中、call和apply方法中。 先來看第一種,構造函數: 在這裡,this指向的是新對象f。而在對象屬性中,這又有了 ...
  • react創建組件的三種方式: 1、函數式無狀態組件 2、es5方式React.createClass組件 3、es6方式extends React.Component 三種創建方式的異同 1、函數式無狀態組件 (1)語法 (2)特點 ● 它是為了創建純展示組件,這種組件只負責根據傳入的props來 ...
  • 關於數組去重這個問題,我曾經在牛客網上就遇到過一次,後來在做一些網上的筆試的時候又碰到了這個問題,其實數組去重的方法有非常多種,五花八門的。但其實我覺得這隻是一件很小的事,何必弄得好像實現的方法越複雜就越厲害一樣。廢話不多說,這裡我只介紹我所認可的兩種方法。 第一種,就是很普通的思維,比如,對於[1 ...
  • 1.生成數組: 1.1 通過Set中轉,生成新的數組 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...