Search code examples
javascripthyperlinkdraggablepreventdefaultinteract.js

prevent click on hyperlink while dragging/holding


I have item div elements with anchor elements as children. The size of the anchor children stretches the parent item element.

I've made the item elements draggable with the library interact.js. The items get draggable when they are hold 300ms or longer. The problem is, that the anchor link of the item child get fired when the drag is released.

How can I prevent the child from firing the hyperlink when the parent element is held/dragged?

Here is a small example of the problem

let items = document.getElementsByClassName("item");

// add class .draggable to each item
for(var i = 0; i < items.length; i++)
{
   items[i].classList.add("draggable");
}

// target elements with the "draggable" class
interact('.draggable').draggable({
	autoScroll: true,
	hold: 300,
	// call this function on every dragmove event
	onmove: dragMoveListener,
	// call this function on every dragend event
	onend: function (event) {
		var target = event.target;
		target.style.webkitTransform =
		target.style.transform =
		'translate(0px, 0px)';

		target.setAttribute('data-x', 0);
		target.setAttribute('data-y', 0);
	}
});

// this function is calles on every dragmove event
function dragMoveListener (event) {
	var target = event.target,
		 // keep the dragged position in the data-x/data-y attributes
		 x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
		 y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;

	// translate the element
	target.style.webkitTransform =
		target.style.transform =
		'translate(' + x + 'px, ' + y + 'px)';

	// update the posiion attributes
	target.setAttribute('data-x', x);
	target.setAttribute('data-y', y);
};



interact('.dropzone').dropzone({

	
  ondropactivate: function (event) {
    event.target.classList.add('drop-active');
  },
	
  ondragenter: function (event) {
	  var draggableElement = event.relatedTarget,
			dropzoneElement = event.target;

	  // feedback the possibility of a drop
	  dropzoneElement.classList.add('drop-target');
	  draggableElement.classList.add('can-drop');
  },
	
  ondragleave: function (event) {
	  // remove the drop feedback style
	  event.target.classList.remove('drop-target');
	  event.relatedTarget.classList.remove('can-drop');
  },
	
  ondrop: function (event) {
	  //delete Bookmark here!
	  event.relatedTarget.classList.add('drop-ok');
  },
	
  ondropdeactivate: function (event) {
	  // remove active dropzone feedback
	  event.target.classList.remove('drop-active');
	  event.target.classList.remove('drop-target');
  }
});
body {
  background-color: #EDEFF3;
  padding: 40px 48px;
}

.item {
  display: inline-block;
  margin: 8px;
  background-color: RGBA(255, 255, 255, 1);
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
  border-radius: 10px;
  z-index: 999;
}
.item a {
  position: relative;
  display: inline-block;
  border-radius: 10px;
  line-height: 40px;
  padding: 0 32px 0 48px;
  font-weight: 400;
  text-decoration: none;
  font-size: 13px;
  color: black;
  font-size: 14px;
}
.item a .dott {
  position: absolute;
  top: 12px;
  left: 20px;
  height: 16px;
  width: 16px;
  background-color: tomato;
  border-radius: 100%;
}
.item.can-drop a {
  text-decoration: line-through;
}
.item.drop-ok {
  display: none;
}

.category {
  display: flex;
  flex-wrap: wrap;
  position: relative;
  align-items: flex-start;
  background-color: RGBA(127, 135, 147, 0.2);
  margin: 16px;
  padding: 8px;
}

.dropzone {
  height: 20%;
  width: 100%;
  position: fixed;
  bottom: 0;
  left: 0;
  background-color: tomato;
  opacity: 0;
}
.dropzone.drop-active {
  opacity: 1;
}
.dropzone.drop-target {
  background-color: #F15B52;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/interact.min.js"></script>
<div class="category">
	
	<div class="item">
		<a href="https://www.google.com/"><span class="dott"></span>bookmark</a>
	</div>
	
	<div class="item">
		<a href="https://www.google.com/">
			<span class="dott"></span>
		bookmark</a>
	</div>
	
	<div class="item">
		<a href="https://www.google.com/"><span class="dott"></span>bookmark</a>
	</div>
	
	<div class="item">
		<a href="https://www.google.com/"><span class="dott"></span>bookmark</a>
	</div>
	
	<div class="item">
		<a href="https://www.google.com/"><span class="dott"></span>bookmark</a>
	</div>	
</div>

<div class="dropzone"></div>

Here is my current state at Codepen: https://codepen.io/iamrbn/pen/pKGPMz


Solution

  • Perhaps not the most elegant solution but it works. My first attempt failed but I think I have something that works now. I created a flag system to keep track of events. Note that I added onstart to the draggable instance. I had to add a 300ms timeout to match the time of the hold. It seems onstart fired immediately on mousedown despite the 300ms hold. I'm not sure how that part of your library works ;)

    Anyway, wait 300ms and then set a flag of drag. Note that the variable is global for reference. Check your project variable scope before implementing this. You might want to create a public object instead to keep the global from mixing things up.

    I add a click event listener to each link. When the click fired, check the hold flag status. If it is a drag, prevent the event. Otherwise, proceed to register a click. Note: I tried adding this flag evaluator code to the onend method within the draggable instance but onend turns out to be a mouseup event which fires before click. Therefore, the evaluation needs to happen with a click event. The best way to do that within the scope is with adding a click event to each link.

    Whew! Gosh, that took probably an hour. Let me know if it works :)

    let items = document.getElementsByClassName("item");
    
    // add class .draggable to each item
    for (var i = 0; i < items.length; i++) {
      items[i].classList.add("draggable");
      items[i].children[0].addEventListener('click',function(e){
      if(drag){
        drag = false;
        e.preventDefault()
        }
      });
    }
    
    var drag = false;
    
    // target elements with the "draggable" class
    interact('.draggable').draggable({
      autoScroll: true,
      hold: 300,
      // call this function on every dragmove event
      onstart: function(){
        setTimeout(function(){
          drag = true;
        },300);
      },
      onmove: dragMoveListener,
      // call this function on every dragend event
      onend: function(event) {
      
        var target = event.target;
        target.style.webkitTransform =
          target.style.transform =
          'translate(0px, 0px)';
    
        target.setAttribute('data-x', 0);
        target.setAttribute('data-y', 0);
      }
    });
    
    // this function is calles on every dragmove event
    function dragMoveListener(event) {
      var target = event.target,
        // keep the dragged position in the data-x/data-y attributes
        x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
        y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
    
      // translate the element
      target.style.webkitTransform =
        target.style.transform =
        'translate(' + x + 'px, ' + y + 'px)';
    
      // update the posiion attributes
      target.setAttribute('data-x', x);
      target.setAttribute('data-y', y);
    };
    
    
    
    interact('.dropzone').dropzone({
    
    
      ondropactivate: function(event) {
        event.target.classList.add('drop-active');
      },
    
      ondragenter: function(event) {
        var draggableElement = event.relatedTarget,
          dropzoneElement = event.target;
    
        // feedback the possibility of a drop
        dropzoneElement.classList.add('drop-target');
        draggableElement.classList.add('can-drop');
      },
    
      ondragleave: function(event) {
        // remove the drop feedback style
        event.target.classList.remove('drop-target');
        event.relatedTarget.classList.remove('can-drop');
      },
    
      ondrop: function(event) {
        //delete Bookmark here!
        event.relatedTarget.classList.add('drop-ok');
      },
    
      ondropdeactivate: function(event) {
        // remove active dropzone feedback
        event.target.classList.remove('drop-active');
        event.target.classList.remove('drop-target');
      }
    });
    body {
      background-color: #EDEFF3;
      padding: 40px 48px;
    }
    
    .item {
      display: inline-block;
      margin: 8px;
      background-color: RGBA(255, 255, 255, 1);
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
      border-radius: 10px;
      z-index: 999;
    }
    
    .item a {
      position: relative;
      display: inline-block;
      border-radius: 10px;
      line-height: 40px;
      padding: 0 32px 0 48px;
      font-weight: 400;
      text-decoration: none;
      font-size: 13px;
      color: black;
      font-size: 14px;
    }
    
    .item a .dott {
      position: absolute;
      top: 12px;
      left: 20px;
      height: 16px;
      width: 16px;
      background-color: tomato;
      border-radius: 100%;
    }
    
    .item.can-drop a {
      text-decoration: line-through;
    }
    
    .item.drop-ok {
      display: none;
    }
    
    .category {
      display: flex;
      flex-wrap: wrap;
      position: relative;
      align-items: flex-start;
      background-color: RGBA(127, 135, 147, 0.2);
      margin: 16px;
      padding: 8px;
    }
    
    .dropzone {
      height: 20%;
      width: 100%;
      position: fixed;
      bottom: 0;
      left: 0;
      background-color: tomato;
      opacity: 0;
    }
    
    .dropzone.drop-active {
      opacity: 1;
    }
    
    .dropzone.drop-target {
      background-color: #F15B52;
    }
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/interact.min.js"></script>
    <div class="category">
    
      <div class="item">
        <a href="https://www.google.com/"><span class="dott"></span>bookmark</a>
      </div>
    
      <div class="item">
        <a href="https://www.google.com/">
          <span class="dott"></span> bookmark
        </a>
      </div>
    
      <div class="item">
        <a href="https://www.google.com/"><span class="dott"></span>bookmark</a>
      </div>
    
      <div class="item">
        <a href="https://www.google.com/"><span class="dott"></span>bookmark</a>
      </div>
    
      <div class="item">
        <a href="https://www.google.com/"><span class="dott"></span>bookmark</a>
      </div>
    </div>
    
    <div class="dropzone"></div>