skip to Main Content

I am really confused about how preventDefault function works. In the following example I expect when I drag elem2, the drag event get cancelled and it does. But why the elem3 drag event is cancelled too when I drag elem3?

Lets clarify more: When you drag a draggable element , the dragstart event is fired on that element and all its ancestors. Here when you start dragging elem3, because event capture is false, the event bubbles up from elem3 to elem1 (a true event capture just makes the order of firing reversed). So when you start dragging elem3 the event fires first on elem3, then elem2 and finally elem1. When there is no preventDefault you can drag all three elements. When I add preventDefault to elem2, you cannot drag it anymore. But now I cannot drag elem3 either. Why? There is no preventDefault in elem3 event handler!

const elem1 = document.getElementById("elem1");
elem1.addEventListener("dragstart", elem1EventHandler, false);

const elem2 = document.getElementById("elem2");
elem2.addEventListener("dragstart", elem2EventHandler, false);

const elem3 = document.getElementById("elem3");
elem3.addEventListener("dragstart", elem3EventHandler, false);

function elem1EventHandler(event) {
  console.log("elem1");
}

function elem2EventHandler(event) {
  console.log("elem2");
  event.preventDefault();
}

function elem3EventHandler(event) {
  console.log("elem3");
}
#elem1 {
  display: block;
  position: relative;
  width: 200px;
  height: 150px;
  background-color: deepskyblue;
}

#elem2 {
  display: block;
  width: 150px;
  height: 100px;
  background-color: greenyellow;
}

#elem3 {
  display: block;
  width: 100px;
  height: 50px;
  background-color: coral
}
<div id="elem1" draggable="true">
  <div id="elem2" draggable="true">
    <div id="elem3" draggable="true">
    </div>
  </div>
</div>

2

Answers


  1. Applying preventDefault() to a div container causes the default behaviour to stop in the container itself and everything inside it, including other containers. In your example, elem3 is nested in elem2.

    In this example, you can drag the gray container, since it’s not nested in elem2, but can’t the dark green one, because it is.

    const elem1 = document.getElementById("elem1");
    elem1.addEventListener("dragstart", elem1EventHandler, false);
    
    const elem2 = document.getElementById("elem2");
    elem2.addEventListener("dragstart", elem2EventHandler, false);
    
    const elem3 = document.getElementById("elem3");
    elem3.addEventListener("dragstart", elem3EventHandler, false);
    
    function elem1EventHandler(event) {
    
    }
    
    function elem2EventHandler(event) {
      event.preventDefault();
    }
    
    function elem3EventHandler(event) {
    }
    #elem1 {
      display: block;
      position: relative;
      width: 200px;
      height: 150px;
      background-color: deepskyblue;
    }
    
    #elem2 {
      display: block;
      width: 150px;
      height: 100px;
      background-color: greenyellow;
    }
    
    #elem3 {
      display: block;
      width: 100px;
      height: 50px;
      background-color: coral
    }
    
    #elem4 {
      display: block;
      width: 50px;
      height: 25px;
      background-color: green
    }
    
    #elem0 {
      display: block;
      width: 400px;
      height: 300px;
      background-color: gray
    }
    <div id="elem0" draggable="true">
      <div id="elem1" draggable="true">
        <div id="elem2" draggable="true">
          <div id="elem3" draggable="true">
            <div id="elem4" draggable="true">
            </div>
          </div>
        </div>
      </div>
    </div>
    Login or Signup to reply.
  2. You have 2 stages with DOM events

    1. An event propagates in DOM from the source element to the document (unless it doesn’t bubble like focus and you don’t use capture). So methods like stopPropagation() don’t work to stop the default action.
    2. The browser could take a default action associated after the propagation is done (either stopped or not). That’s where the browser checks whether the default action was cancelled by preventDefault() in the propagation stage.

    By listening on a parent element you actually get both the parent’s events and its children’s events due event propagation/bubbling. To prevent the default action only for the parent check event.target. Now you can drag the orange child:

    const elem2 = document.getElementById("elem2");
    elem2.addEventListener("dragstart", event => event.target === elem2 && event.preventDefault());
    #elem1 {
      display: block;
      position: relative;
      width: 200px;
      height: 150px;
      background-color: deepskyblue;
    }
    
    #elem2 {
      display: block;
      width: 150px;
      height: 100px;
      background-color: greenyellow;
    }
    
    #elem3 {
      display: block;
      width: 100px;
      height: 50px;
      background-color: coral
    }
    <div id="elem1" draggable="true">
      <div id="elem2" draggable="true">
        <div id="elem3" draggable="true">
        </div>
      </div>
    </div>

    Note that if you use function as an event handler, this refers to the DOM element you listen with the handler, so it could be written also like this:

    document.getElementById("elem2").addEventListener("dragstart", function(event) {
      event.target === this && event.preventDefault();
    });
    #elem1 {
      display: block;
      position: relative;
      width: 200px;
      height: 150px;
      background-color: deepskyblue;
    }
    
    #elem2 {
      display: block;
      width: 150px;
      height: 100px;
      background-color: greenyellow;
    }
    
    #elem3 {
      display: block;
      width: 100px;
      height: 50px;
      background-color: coral
    }
    <div id="elem1" draggable="true">
      <div id="elem2" draggable="true">
        <div id="elem3" draggable="true">
        </div>
      </div>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search