// 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>