DOM event model (mechanism)

DOM event operations (listening and firing) are defined in the EventTarget interface. This interface is deployed on all node objects, as well as other built-in browser objects that require event communication (e.g., XMLHttpRequest, AudioNode, AudioContext).

This interface mainly provides three instance methods.

  • addEventListener: a listener function for binding events
  • removeEventListener: Removes listeners for events
  • dispatchEvent: Trigger event

The event model

Propagation after the occurrence of an event, propagation between child elements and parent elements will be divided into three stages.

(This three-phase propagation model allows the same event to be triggered at multiple nodes.)

  1. Looking in from the outside is called event capture
  2. Fires an event at the target node
  3. The inside-out listener is the event bubble

There are three stages of DOM event propagation: capture phase, target phase, and bubble phase

Here’s an example:

<div class="grandfather">
  <div class="father">
    <div class="son">The text</div>
  </div>
</div>
Copy the code

Add event listener fnYe/fnBa/fnEr to the three divs, then click the text, which function is executed first?

This involves a little history:

IE5 decided to call fnEr first, Netscape decided to call fnYe first, and finally met W3C

In 2002, DOM Level 2 Events Specification specifies that the browser should support both grandfather->father->son and son->father->grandfather FnYe/fnBa/fnEr are called twice. It is up to the developer to decide whether to put fnYe in the capture or bubble phase

W3C: baba.addEventListener(‘click’,fn,bool)

If bool is not passed or falsy

Let fn bubble, that is, when the browser discovers that BABA has fn listeners during the bubbling phase, fn is called and the time information is provided.

If bool is true

Let fn go capture, that is, when the browser discovers that BABA has fn listeners during the capture phase, it calls FN and provides event information.

Here’s an example:

HTML:

<div class="level1 x">
  <div class="level2 x">
    <div class="level3 x">
      <div class="level4 x">
        <div class="level5 x">
          <div class="level6 x">
            <div class="level7 x">
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
Copy the code

CSS:

* {
  box-sizing: border-box;
}
div[class^=level] {
  border: 1px solid;
  border-radius: 50%;
  display: inline-flex;
}
.level1 {
  padding: 10px;
  background: purple;
}
.level2 {
  padding: 10px;
  background: blue;
}
.level3 {
  padding: 10px;
  background: cyan;
}
.level4 {
  padding: 10px;
  background: green;
}
.level5 {
  padding: 10px;
  background: yellow;
}
.level6 {
  padding: 10px;
  background: orange;
}
.level7 {
  width: 50px;
  height: 50px;
  border: 1px solid;
  background: red;
  border-radius: 50%;
}
.x{
  background: transparent; // Make the element transparent}Copy the code

JavaScript:

const level1 = document.querySelector('.level1')
const level2 = document.querySelector('.level2')
const level3 = document.querySelector('.level3')
const level4 = document.querySelector('.level4')
const level5 = document.querySelector('.level5')
const level6 = document.querySelector('.level6')
const level7 = document.querySelector('.level7')

let n = 1
 const fm = (e) = >{
  const t = e.currentTarget
  setTimeout(() = >{  
    t.classList.remove('x')
  },n*1000)
  n+=1
}
 const fa = (e) = >{
   const t =e.currentTarget
   setTimeout(() = >{
     t.classList.add('x')
   },n*1000)
   n+=1
 }

level1.addEventListener('click',fm,true)
level1.addEventListener('click',fa)
level2.addEventListener('click',fm,true)
level2.addEventListener('click',fa)
level3.addEventListener('click',fm,true)
level3.addEventListener('click',fa)
level4.addEventListener('click',fm,true)
level4.addEventListener('click',fa)
level5.addEventListener('click',fm,true)
level5.addEventListener('click',fa)
level6.addEventListener('click',fm,true)
level6.addEventListener('click',fa)
level7.addEventListener('click',fm,true)
level7.addEventListener('click',fa)
Copy the code

Here are a few points

CurrentTarget Event attribute

Definitions and Usage

The currentTarget event attribute returns the node from which its listener triggered the event, that is, the element, document, or window that is currently processing the event. This property is useful in the capture and bubbling phases because it differs from the target property on both nodes. More details can be found ==>MDNcurrentTarget

DOM event model

Capture (first grandparent => son) then bubble (second son => Grandparent)

Note that the e object is passed to all listeners

After the event ends, the e object does not exist

Target v.S. currentTarget

The difference between:

This is e.currenttarget, and I personally don’t recommend using it

For example:

Div >span{text}, the user clicks on the text e.target to span e.currenttarget to div

A special case!!

Background:

Only one div is being listened on (not considering both parent and child are being listened on)

Fn listens for click events in the capture phase and the bubble phase, respectively

What the user clicks on is what the developer listens for

Code:

div.addEventListenter('click',f1)
div.addEventListenter('click',f2,true)
Copy the code

Will F1 or F2 be executed first?

What if I switch the two?

Summary: Who listens first who executes first.

level7.addEventListener('click'.() = >{
      console.log(1)})/ / the bubbling

level7.addEventListener('click'.() = >{
      console.log(2)},true)/ / capture

// Prints 1, 2
Copy the code

E.toppropagation () : Cancels bubbles

E.toppropagation () interrupts bubbling and the browser will no longer go up

Generally used to encapsulate some independent components

You cannot cancel bubbling

Specific ones can be viewed in MDN, for example, scroll

So what if I just want to stop it? Can = = >

x.addEventListener('wheel'.(e) = >{
  e.preventDefault()
})// Cancel the wheel effect
x.addEventListener('touchstart'.(e) = >{
  e.preventDefault()
})// Cancel the touch effect
Copy the code

But sometimes you’ll find that the scroll bar next to you can still drag it

::-webkit-scrollbar{
  width: 0 ! important}
Copy the code

Event delegation:

I delegate an element to help me listen for something I’m supposed to listen for, like onclick

Scenario 1:

How to add click events to 100 buttons?

A: Listen for the ancestor of the 100 buttons and wait for the bubble to determine if target is one of the 100 buttons

Code:

div1.addEventListener('click'.(e) = >{
  const t = e.target
  if(t.tagName.toLowerCase()==='button') {console.log('Button is clicked')}})/ / toLowerCase lowercase
Copy the code

Scenario 2:

You want to listen for click events for elements that don’t currently exist?

A: Listen for ancestors, wait until click to see if it is listening to the element.

Advantages: save the number of listening (memory), can dynamically listen to elements

Code:

setTimeout(() = >{
  const button = document.createElement('button')
  button.textContent='click 1'
  div1.appendChild(button)
},1000)

div1.addEventListener('click'.(e) = >{
  const t = e.target
  if(t.tagName.toLowerCase()==='button') {console.log('button is click')}})Copy the code

Encapsulate an event delegate

You only need to implement a function to implement event delegation

Requirements:

Write a function on(‘click’,’#testDiv’,’li’,fn)

The fn function is called when the user clicks the li element inside #testDiv

Event delegate is required

A: Check whether the target matches ‘li’. That is, add a listener to an element to see if the current target meets the condition in the listener function.

setTimeout(() = >{
  const button = document.createElement('button')
  const span = document.createElement('span')
  span.textContent='click 1'
  button.appendChild(span)
  div1.appendChild(button)
},1000)

on('click'.'#div1'.'button'.() = >{//'#div' is a selector, not an element
  console.log('Button clicked')})function on(eventType,element,selector,fn){
  if(! (elementinstanceof Element)){
       element = document.querySelector(element)
     }
  element.addEventListener(eventType,(e) = >{
  const t= e.target// The clicked element is span, not button
  if(t.matches(selector)){Matches is used to check whether an element matches a selector and whether a selector is matchedButton fn(e)}}Copy the code