Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

simulate a 3 pixel drag on draggable elem

I am simply trying to simulate a small click and drag on a draggable div elem — I've found several similar questions here on SO but all involving the use of additional plugins...

Is there a plain JavaScript or jQuery ability to handle specifically the drag? as I know .click(); or mouse down can be called to initiate.

I am not trying to create the ability of a drag and drop, I already have that. I am trying to create a small function that simulates this event automatically. click > hold > drag up 3 pixels


Update: Cannot find anything about this on SO or elsewhere without using a third party library, so creating a 500 bounty on it. Surely it's possible.

like image 732
fred randall Avatar asked Apr 22 '20 22:04

fred randall


People also ask

How do you handle a draggable element?

Handle events on the draggable element How it works: First, select the draggable element using the querySelector() . Second, attach a dragstart event handler to the draggable element. Third, define the dragStart() function to handle the dragstart event.

How do you make a draggable element?

To make an object draggable set draggable=true on that element. Just about anything can be drag-enabled: images, files, links, files, or any markup on your page.

How do mice detect drag?

On the “mouse up” event, the value of drag variable is checked. If the value is true, a “drag” event has occurred and the output is displayed in the console. If the value is 'false' which means there has been no “mouse move” event which also implies that a “click” event had occurred.


1 Answers

You can simulate mouse events using MouseEvent interface and drag events using the DragEvent interface. You have to trigger the right sequence using EventTarget.dispatchEvent().

I guess that you combine "HTML Drag and Drop API" with "Mouse events" to create a drag and drop behaviour, so I'll provide one to simulate Drag & Drop and one to simulate Mouse capture and Mouse move.

Simulating Mouse capture and Mouse move

Please read inline comments

// We create 3 mouse events using MouseEvent interface,
// one for mousedown to initiate the process,
// one for mousemove to handle to movement
// and one for mouseup to terminate the process

const mouseDownEvent = new MouseEvent('mousedown', {
  clientX: element.getBoundingClientRect().left,
  clientY: element.getBoundingClientRect().top,
  bubbles: true,
  cancelable: true
});

const mouseMoveEvent = new MouseEvent('mousemove', {
  clientX: element.getBoundingClientRect().left + 3,
  clientY: element.getBoundingClientRect().top,
  bubbles: true,
  cancelable: true
});

const mouseUpEvent = new MouseEvent('mouseup', {
  bubbles: true,
  cancelable: true
});

// Dispatch the mousedown event to the element that has the listener
element.dispatchEvent(mouseDownEvent);

// For mousemove, the listener may be the parent or even the document
<element|document>.dispatchEvent(mouseMoveEvent);

// Dispatch mouseup to terminate the process
element.dispatchEvent(mouseUpEvent);

Run the code snippet. The square div element is draggable and you can click and drag it when the simulation timer is not running. Click the simulate button to see the simulation in action:

// Timer ID holder
let linearSimulationTimer;

// Simulation X/Y calculation
let calcX = 0, calcY = 0;

// Simulation X/Y axis orientation to handle parent collisions
let xAxisOrientation = 1, yAxisOrientation = 1;

// How many pixels to move the element for X/Y axis
const pixelsShift = 3;

// Elements
const simulateButton = document.getElementById('simulate-dnm');
const movable = document.getElementById('movable');
const movableContainer = movable.parentNode;

simulateButton.addEventListener('click', function() {  
  // If there is a timer running
  if (linearSimulationTimer) {
    // Stop and clear the timer
    clearInterval(linearSimulationTimer);
    linearSimulationTimer = null;
    
    // Create a simple mouseup event with no custom properties
    const mouseUpEvent = new MouseEvent('mouseup', {
      bubbles: true,
      cancelable: true,
    });
    
    // Dispatch it to the movable element
    movable.dispatchEvent(mouseUpEvent);

    // Handle button label text (start/stop)
    simulateButton.classList.remove('running');
  // Else if there is no timer running
  } else {
    // Create the mousedown event using movable element client left/top for event clientX.clientY
    const mouseDownEvent = new MouseEvent('mousedown', {
      clientX: movable.getBoundingClientRect().left,
      clientY: movable.getBoundingClientRect().top,
      pageX: 0,
      pageY: 0,
      bubbles: true,
      cancelable: true,
      view: window
    });

    // Dispatch the mousedown event to the movable element
    movable.dispatchEvent(mouseDownEvent);

    // Get movable parent client rect
    const parentRect = movable.parentNode.getBoundingClientRect();

    // Start the simulation timer
    linearSimulationTimer = setInterval(() => {
      // Get movable element size
      const { width, height } = movable.getBoundingClientRect();

      // Calculate new X/Y position and orientation
      calcX += pixelsShift * xAxisOrientation;
      calcY += pixelsShift * yAxisOrientation;

      // If we hit movable parent X axis bounds, reverse X axis
      if (calcX + width > parentRect.width) {
        calcX  = parentRect.width - width;
        xAxisOrientation = -xAxisOrientation;
      } else if (calcX < 0) {
        calcX = 0;
        xAxisOrientation = -xAxisOrientation;
      }

      // If we hit movable parent Y axis bounds, reverse Y axis
      if (calcY + height > parentRect.height) {
        calcY  = parentRect.height - height;
        yAxisOrientation = -yAxisOrientation;
      } else if (calcY < 0) {
        calcY = 0;
        yAxisOrientation = -yAxisOrientation;
      }

      // Create mousemove event using calcX/calcY and the parent client position
      const mouseMoveEvent = new MouseEvent('mousemove', {
        clientX: parentRect.left + calcX,
        clientY: parentRect.top + calcY,
        pageX: 0,
        pageY: 0,
        bubbles: true,
        cancelable: true,
        view: window
      });

      // Dispatch the mousemove event to the parent which it has the listener
      movableContainer.dispatchEvent(mouseMoveEvent);
    }, 50);
    
    // Handle button label text (start/stop)
    simulateButton.classList.add('running');
  }
});

// Mouse capture and drag handler (https://javascript.info/mouse-drag-and-drop)
movable.onmousedown = function(event) {
  let shiftX = event.clientX - movable.getBoundingClientRect().left;
  let shiftY = event.clientY - movable.getBoundingClientRect().top;

  moveAt(event.pageX, event.pageY);

  function moveAt(pageX, pageY) {
    movable.style.left = pageX - shiftX - movableContainer.offsetLeft + 'px';
    movable.style.top = pageY - shiftY - movableContainer.offsetTop + 'px';
  }

  function onMouseMove(event) {
    moveAt(event.pageX, event.pageY);
  }

  movableContainer.addEventListener('mousemove', onMouseMove);

  movable.onmouseup = function() {
    movableContainer.removeEventListener('mousemove', onMouseMove);
    movable.onmouseup = null;
  }
}

movable.ondragstart = function() {
  return false;
}
#movable-container {
  position: relative;
  height: 80px;
  width: 200px;
  margin: auto;
  margin-bottom: 20px;
  border: 1px dotted silver;
}

#movable {
  position: relative;
  left: 0;
  width: 30px;
  height: 30px;
  background-color: cornflowerblue;
  border-radius: 5px;
  border: 1px solid grey;
}

#simulate-dnm > span:before {
  content: 'Start ';
}

#simulate-dnm.running > span:before {
  content: 'Stop ';
}
<div id="movable-container">
  <div id="movable"></div>
</div>
<div>
  <button id="simulate-dnm"><span>Mouse capture & move simulation</span></button>
</div>

Simulating Drag n Drop

Please read inline comments

// We create 3 drag events using DragEvent interface,
// one for dragstart to initiate the process,
// one for drop to handle the drag element drop inside the drop container
// and one for dragend to terminate the process

const dragStartEvent = new DragEvent('dragstart', {
  bubbles: true,
  cancelable: true
});
const dragEndEvent = new DragEvent('dragend', {
  bubbles: true,
  cancelable: true
});
const dropEvent = new DragEvent('drop', {
  bubbles: true,
  cancelable: true
});

// Dispatch the dragstart event to the source element to initiate
sourceNode.dispatchEvent(dragStartEvent);

// Dispatch the drop event to the destination element to get the drag
destinationNode.dispatchEvent(dropEvent);

// Dispatch the dragend event to the source element to finish
sourceNode.dispatchEvent(dragEndEvent);

Run the code snippet and hit the simulate button to trigger the drag n drop events sequence:

// The current parent index that the drag element is inside
let currentParentIndex = 0;

// Elements
const simulateButton = document.getElementById('simulate-dnd');
const sourceNode = document.getElementById('drag');

function simulateDragDrop(sourceNode, destinationNode) {
  // Create dragstart event
  const dragStartEvent = new DragEvent('dragstart', {
    bubbles: true,
    cancelable: true
  });
  
  // Create dragend event
  const dragEndEvent = new DragEvent('dragend', {
    bubbles: true,
    cancelable: true
  });
  
  // Create drop event
  const dropEvent = new DragEvent('drop', {
    bubbles: true,
    cancelable: true
  });

  // Dispatch dragstart event to the draggable element
  sourceNode.dispatchEvent(dragStartEvent);
  
  // Dispatch drop event to container element we want to drop the draggable
  destinationNode.dispatchEvent(dropEvent);
  
  // Dispatch dragend  event to the draggable element
  sourceNode.dispatchEvent(dragEndEvent);
}

simulateButton.addEventListener('click', function() {
  // Change drop container index to other container than the current
  const newParentIndex = currentParentIndex === 0 ? 1 : 0;
  
  // Get the drop container element
  const destinationNode = document.getElementsByClassName('dropzone')[newParentIndex];

  // Initiate simulation sequence
  simulateDragDrop(sourceNode, destinationNode);

  // Save the new container index
  currentParentIndex = newParentIndex;
});


// Drag n Drop handling

let dragged;

document.addEventListener("dragstart", function(event) {
  // store a ref. on the dragged elem
  dragged = event.target;
  // make it half transparent
  event.target.style.opacity = .5;
}, false);

document.addEventListener("dragend", function(event) {
  // reset the transparency
  event.target.style.opacity = "";
}, false);

/* events fired on the drop targets */
document.addEventListener("dragover", function(event) {
  // prevent default to allow drop
  event.preventDefault();
}, false);

document.addEventListener("dragenter", function(event) {
  // highlight potential drop target when the draggable element enters it
  if (event.target.className == "dropzone") {
    event.target.style.background = "yellow";
  }
}, false);

document.addEventListener("dragleave", function(event) {
  // reset background of potential drop target when the draggable element leaves it
  if (event.target.className == "dropzone") {
    event.target.style.background = "";
  }
}, false);

document.addEventListener("drop", function(event) {
  // prevent default action (open as link for some elements)
  event.preventDefault();
  // move dragged elem to the selected drop target
  if (event.target.className == "dropzone") {
    event.target.style.background = "";
    dragged.parentNode.removeChild(dragged);
    event.target.appendChild(dragged);
    currentParentIndex = Array.prototype.indexOf.call(event.target.parentNode.children, event.target);
  }  
}, false);
.dropzones {
  display: flex;
  justify-content: space-evenly;
}

.dropzone {
  width: 100px;
  height: 100px;
  background-color: mintcream;
  border-radius: 5px;
  border: 2px dashed darkgrey;
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: 10px;
}

#drag {
  margin: unset;
  width: 40px;
  height: 40px;
  background-color: coral;
  border-radius: 4px;
  border: 1px solid grey; 
}
<div class="dropzones">
  <div class="dropzone">
    <div id="drag" draggable="true"></div>
  </div>
  <div class="dropzone"></div>
</div>
<div>
  <button id="simulate-dnd">Simulate Drag & Drop</button>
</div>
like image 95
Christos Lytras Avatar answered Sep 25 '22 12:09

Christos Lytras