move element on mousemove event: FAILS when moving too fast (vuejs) - javascript

I'm building an element which could be moved on mouse events.
Here's an example PEN I found on the internet. General concept is: 1) get original cursor location on mousedown, 2) move the element on mousemove, 3) finish on mouseleave or mouseup.
var offset = [0,0];
var divOverlay = document.getElementById ("overlay");
var isDown = false;
divOverlay.addEventListener('mousedown', function(e) {
isDown = true;
offset = [
divOverlay.offsetLeft - e.clientX,
divOverlay.offsetTop - e.clientY
];
}, true);
document.addEventListener('mouseup', function() {
isDown = false;
}, true);
document.addEventListener('mousemove', function(e) {
event.preventDefault();
if (isDown) {
divOverlay.style.left = (e.clientX + offset[0]) + 'px';
divOverlay.style.top = (e.clientY + offset[1]) + 'px';
}
}, true);
I used the same concept but with Vuejs. It worked, but it's far from perfect. If I move faster, the cursor would somehow leave the element, which ends mousemove event. Couldn't figure out what was wrong. Here's my jsfiddle.
new Vue({
el: "#app",
data: {
blocks: [{
top: 0,
left: 0,
active: false
}],
offsetX: undefined,
offsetY: undefined,
},
methods: {
mousedown(e, i) {
this.blocks[i].active = true;
this.offsetX = e.offsetX;
this.offsetY = e.offsetY;
},
mouseup(i) {
this.blocks[i].active = false;
},
mousemove(e, i) {
if (this.blocks[i].active) {
let x = e.offsetX;
let y = e.offsetY;
let dx = x - this.offsetX;
let dy = y - this.offsetY;
this.blocks[i].top += dy;
this.blocks[i].left += dx;
}
}
}
})
How should I modify my code so that it works as smooth as the pure Javascript version?
Thanks!

Related

Drag multiple elements in JS

I'm new to JS so I need help to solve my problem :). I found a codepen that helped me drag one element of my website but the thing is that I would like to drag 4 elements separately. I applied the same class to all of them but it works only on the first one.
Link of the codepen : https://codepen.io/Coding-Artist/pen/zYWbYXV
I'm sure the solution is obvious to you (I would say a var or a for ?) but I'm learning and I really want to progress so if you could explain that would be perfect ! Thanks a lot
JS —
var draggableElem = document.querySelector(".draggable-elem");
let initialX = 0,
initialY = 0;
let moveElement = false;
//events object
let events = {
mouse: {
down: "mousedown",
move: "mousemove",
up: "mouseup"
},
touch: {
down: "touchstart",
move: "touchmove",
up: "touchend"
}
};
let deviceType = "";
//Detect touch device
const isTouchDevice = () => {
try {
//We try to create TouchEvent (it would fail for desktops and throw error)
document.createEvent("TouchEvent");
deviceType = "touch";
return true;
} catch (e) {
deviceType = "mouse";
return false;
}
};
isTouchDevice();
// start(mouse down/touch start)
draggableElem.addEventListener(events[deviceType].down, (e) => {
e.preventDefault();
//initial x and y points
initialX = !isTouchDevice() ? e.clientX : e.touches[0].clientX;
initialY = !isTouchDevice() ? e.clientY : e.touches[0].clientY;
// start movement
moveElement = true;
});
// Move
draggableElem.addEventListener(events[deviceType].move, (e) => {
//if movement==true then set top and left to new X and y while removing any offset
if (moveElement) {
e.preventDefault();
let newX = !isTouchDevice() ? e.clientX : e.touches[0].clientX;
let newY = !isTouchDevice() ? e.clientY : e.touches[0].clientY;
draggableElem.style.top = draggableElem.offsetTop - (initialY - newY) + "px";
draggableElem.style.left =
draggableElem.offsetLeft - (initialX - newX) + "px";
initialX = newX;
initialY = newY;
}
});
//mouse up/touch end
draggableElem.addEventListener(
events[deviceType].up,
(stopMovement = (e) => {
//stop movement
moveElement = false;
})
);
draggableElem.addEventListener("mouseleave", stopMovement);
document.addEventListener(events[deviceType].up, (e) => {
moveElement = false;
});
For it to work with multiple elements you should instantiate variables for each element and then add event listeners to them.
This can be done dynamically like in this codepen fork I made by using document.querySelectorAll and a for loop to iterate through the elements, instantiate variables, and add event listeners to each one.
My modified code (it's not perfect but it gets the job done):
let draggableElems = document.querySelectorAll("#draggable-elem");
let initialX = {},
initialY = {};
let moveElement = {};
//events object
let events = {
mouse: {
down: "mousedown",
move: "mousemove",
up: "mouseup"
},
touch: {
down: "touchstart",
move: "touchmove",
up: "touchend"
}
};
let deviceType = "";
//Detect touch device
const isTouchDevice = () => {
try {
//We try to create TouchEvent (it would fail for desktops and throw error)
document.createEvent("TouchEvent");
deviceType = "touch";
return true;
} catch (e) {
deviceType = "mouse";
return false;
}
};
isTouchDevice();
for (let i = 0; i < draggableElems.length; i++) {
var draggableElem = draggableElems[i];
// start(mouse down/touch start)
draggableElem.addEventListener(events[deviceType].down, (e) => {
e.preventDefault();
//initial x and y points
initialX[this] = !isTouchDevice() ? e.clientX : e.touches[0].clientX;
initialY[this] = !isTouchDevice() ? e.clientY : e.touches[0].clientY;
// start movement
moveElement[this] = true;
});
// Move
draggableElem.addEventListener(events[deviceType].move, (e) => {
//if movement==true then set top and left to new X and y while removing any offset
if (moveElement[this]) {
var elem = e.target;
e.preventDefault();
let newX = !isTouchDevice() ? e.clientX : e.touches[0].clientX;
let newY = !isTouchDevice() ? e.clientY : e.touches[0].clientY;
elem.style.top = elem.offsetTop - (initialY[this] - newY) + "px";
elem.style.left = elem.offsetLeft - (initialX[this] - newX) + "px";
initialX[this] = newX;
initialY[this] = newY;
}
});
//mouse up/touch end
draggableElem.addEventListener(
events[deviceType].up,
(stopMovement = (e) => {
//stop movement
moveElement[this] = false;
})
);
draggableElem.addEventListener("mouseleave", stopMovement);
document.addEventListener(events[deviceType].up, (e) => {
moveElement[this] = false;
});
}

How to prevent click while scrolling horizontally

I am using cards list with links and it is scrollable horizontally using mouse as well as arrows.
I Want to prevent clicking ( tags) while scrolling/dragging items left or right.
But clicking should work if i am not dragging items.
Here is what I am using in Javascript.
Code
var instance = $(".hs__wrapper");
$.each( instance, function(key, value)
{
var arrows = $(instance[key]).find(".arrow"),
prevArrow = arrows.filter('.arrow-prev'),
nextArrow = arrows.filter('.arrow-next'),
box = $(instance[key]).find(".hs"),
x = 0,
mx = 0,
maxScrollWidth = box[0].scrollWidth - (box[0].clientWidth / 2) - (box.width() / 2);
$(arrows).on('click', function() {
if ($(this).hasClass("arrow-next")) {
x = ((box.width() / 2)) + box.scrollLeft() - 10;
box.animate({
scrollLeft: x,
})
} else {
x = ((box.width() / 2)) - box.scrollLeft() -10;
box.animate({
scrollLeft: -x,
})
}
});
$(box).on({
mousemove: function(e) {
var mx2 = e.pageX - this.offsetLeft;
if(mx) this.scrollLeft = this.sx + mx - mx2;
},
mousedown: function(e) {
this.sx = this.scrollLeft;
mx = e.pageX - this.offsetLeft;
},
scroll: function() {
toggleArrows();
}
});
$(document).on("mouseup", function(){
mx = 0;
});
function toggleArrows() {
if(box.scrollLeft() > maxScrollWidth - 10) {
// disable next button when right end has reached
nextArrow.addClass('disabled');
} else if(box.scrollLeft() < 10) {
// disable prev button when left end has reached
prevArrow.addClass('disabled')
} else{
// both are enabled
nextArrow.removeClass('disabled');
prevArrow.removeClass('disabled');
}
}
});
Try adding a click event handler to the links which prevents default browser behaviour while scrolling. Then, remove the event handler, detecting when scrolling stops using e.g. this method.
var instance = $(".hs__wrapper");
$.each( instance, function(key, value)
{
var arrows = $(instance[key]).find(".arrow"),
prevArrow = arrows.filter('.arrow-prev'),
nextArrow = arrows.filter('.arrow-next'),
box = $(instance[key]).find(".hs"),
x = 0,
mx = 0,
maxScrollWidth = box[0].scrollWidth - (box[0].clientWidth / 2) - (box.width() / 2);
$(arrows).on('click', function() {
if ($(this).hasClass("arrow-next")) {
x = ((box.width() / 2)) + box.scrollLeft() - 10;
box.animate({
scrollLeft: x,
})
} else {
x = ((box.width() / 2)) - box.scrollLeft() -10;
box.animate({
scrollLeft: -x,
})
}
});
$(box).on({
mousemove: function(e) {
var mx2 = e.pageX - this.offsetLeft;
if(mx) this.scrollLeft = this.sx + mx - mx2;
},
mousedown: function(e) {
this.sx = this.scrollLeft;
mx = e.pageX - this.offsetLeft;
},
scroll: function() {
clearTimeout($.data(this, 'scrollTimer'));
$.data(this, 'scrollTimer', setTimeout(function() {
$(box).find('a').off('click');
}, 250));
toggleArrows();
$(box).find('a').on('click', function(e) {
e.preventDefault();
});
}
});
$(document).on("mouseup", function(){
mx = 0;
});
function toggleArrows() {
if(box.scrollLeft() > maxScrollWidth - 10) {
// disable next button when right end has reached
nextArrow.addClass('disabled');
} else if(box.scrollLeft() < 10) {
// disable prev button when left end has reached
prevArrow.addClass('disabled')
} else{
// both are enabled
nextArrow.removeClass('disabled');
prevArrow.removeClass('disabled');
}
}
});

How to use event listeners to make click, drag, and drop function? [duplicate]

Im struggling with seemingly a simple javascript exercise, writing a vanilla drag and drop. I think Im making a mistake with my 'addeventlisteners', here is the code:
var ele = document.getElementsByClassName ("target")[0];
var stateMouseDown = false;
//ele.onmousedown = eleMouseDown;
ele.addEventListener ("onmousedown" , eleMouseDown , false);
function eleMouseDown () {
stateMouseDown = true;
document.addEventListener ("onmousemove" , eleMouseMove , false);
}
function eleMouseMove (ev) {
do {
var pX = ev.pageX;
var pY = ev.pageY;
ele.style.left = pX + "px";
ele.style.top = pY + "px";
document.addEventListener ("onmouseup" , eleMouseUp , false);
} while (stateMouseDown === true);
}
function eleMouseUp () {
stateMouseDown = false;
document.removeEventListener ("onmousemove" , eleMouseMove , false);
document.removeEventListener ("onmouseup" , eleMouseUp , false);
}
Here's a jsfiddle with it working: http://jsfiddle.net/fpb7j/1/
There were 2 main issues, first being the use of onmousedown, onmousemove and onmouseup. I believe those are only to be used with attached events:
document.body.attachEvent('onmousemove',drag);
while mousedown, mousemove and mouseup are for event listeners:
document.body.addEventListener('mousemove',drag);
The second issue was the do-while loop in the move event function. That function's being called every time the mouse moves a pixel, so the loop isn't needed:
var ele = document.getElementsByClassName ("target")[0];
ele.addEventListener ("mousedown" , eleMouseDown , false);
function eleMouseDown () {
stateMouseDown = true;
document.addEventListener ("mousemove" , eleMouseMove , false);
}
function eleMouseMove (ev) {
var pX = ev.pageX;
var pY = ev.pageY;
ele.style.left = pX + "px";
ele.style.top = pY + "px";
document.addEventListener ("mouseup" , eleMouseUp , false);
}
function eleMouseUp () {
document.removeEventListener ("mousemove" , eleMouseMove , false);
document.removeEventListener ("mouseup" , eleMouseUp , false);
}
By the way, I had to make the target's position absolute for it to work.
you can try this fiddle too, http://jsfiddle.net/dennisbot/4AH5Z/
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>titulo de mi pagina</title>
<style>
#target {
width: 100px;
height: 100px;
background-color: #ffc;
border: 2px solid blue;
position: absolute;
}
</style>
<script>
window.onload = function() {
var el = document.getElementById('target');
var mover = false, x, y, posx, posy, first = true;
el.onmousedown = function() {
mover = true;
};
el.onmouseup = function() {
mover = false;
first = true;
};
el.onmousemove = function(e) {
if (mover) {
if (first) {
x = e.offsetX;
y = e.offsetY;
first = false;
}
posx = e.pageX - x;
posy = e.pageY - y;
this.style.left = posx + 'px';
this.style.top = posy + 'px';
}
};
}
</script>
</head>
<body>
<div id="target" style="left: 10px; top:20px"></div>
</body>
</html>
I've just made a simple drag.
It's a one liner usage, and it handles things like the offset of the mouse to the top left corner of the element, onDrag/onStop callbacks, and SVG elements dragging
Here is the code.
// simple drag
function sdrag(onDrag, onStop) {
var startX = 0;
var startY = 0;
var el = this;
var dragging = false;
function move(e) {
el.style.left = (e.pageX - startX ) + 'px';
el.style.top = (e.pageY - startY ) + 'px';
onDrag && onDrag(el, e.pageX, startX, e.pageY, startY);
}
function startDragging(e) {
if (e.currentTarget instanceof HTMLElement || e.currentTarget instanceof SVGElement) {
dragging = true;
var left = el.style.left ? parseInt(el.style.left) : 0;
var top = el.style.top ? parseInt(el.style.top) : 0;
startX = e.pageX - left;
startY = e.pageY - top;
window.addEventListener('mousemove', move);
}
else {
throw new Error("Your target must be an html element");
}
}
this.addEventListener('mousedown', startDragging);
window.addEventListener('mouseup', function (e) {
if (true === dragging) {
dragging = false;
window.removeEventListener('mousemove', move);
onStop && onStop(el, e.pageX, startX, e.pageY, startY);
}
});
}
Element.prototype.sdrag = sdrag;
and to use it:
document.getElementById('my_target').sdrag();
You can also use onDrag and onStop callbacks:
document.getElementById('my_target').sdrag(onDrag, onStop);
Check my repo here for more details:
https://github.com/lingtalfi/simpledrag
this is how I do it
var MOVE = {
startX: undefined,
startY: undefined,
item: null
};
function contentDiv(color, width, height) {
var result = document.createElement('div');
result.style.width = width + 'px';
result.style.height = height + 'px';
result.style.backgroundColor = color;
return result;
}
function movable(content) {
var outer = document.createElement('div');
var inner = document.createElement('div');
outer.style.position = 'relative';
inner.style.position = 'relative';
inner.style.cursor = 'move';
inner.style.zIndex = 1000;
outer.appendChild(inner);
inner.appendChild(content);
inner.addEventListener('mousedown', function(evt) {
MOVE.item = this;
MOVE.startX = evt.pageX;
MOVE.startY = evt.pageY;
})
return outer;
}
function bodyOnload() {
document.getElementById('td1').appendChild(movable(contentDiv('blue', 100, 100)));
document.getElementById('td2').appendChild(movable(contentDiv('red', 100, 100)));
document.addEventListener('mousemove', function(evt) {
if (!MOVE.item) return;
if (evt.which!==1){ return; }
var dx = evt.pageX - MOVE.startX;
var dy = evt.pageY - MOVE.startY;
MOVE.item.parentElement.style.left = dx + 'px';
MOVE.item.parentElement.style.top = dy + 'px';
});
document.addEventListener('mouseup', function(evt) {
if (!MOVE.item) return;
var dx = evt.pageX - MOVE.startX;
var dy = evt.pageY - MOVE.startY;
var sty = MOVE.item.style;
sty.left = (parseFloat(sty.left) || 0) + dx + 'px';
sty.top = (parseFloat(sty.top) || 0) + dy + 'px';
MOVE.item.parentElement.style.left = '';
MOVE.item.parentElement.style.top = '';
MOVE.item = null;
MOVE.startX = undefined;
MOVE.startY = undefined;
});
}
bodyOnload();
table {
user-select: none
}
<table>
<tr>
<td id='td1'></td>
<td id='td2'></td>
</tr>
</table>
While dragging, the left and right of the style of the parentElement of the dragged element are continuously updated. Then, on mouseup (='drop'), "the changes are committed", so to speak; we add the (horizontal and vertical) position changes (i.e., left and top) of the parent to the position of the element itself, and we clear left/top of the parent again. This way, we only need JavaScript variables for pageX, pageY (mouse position at drag start), while concerning the element position at drag start, we don't need JavaScript variables for that (just keeping that information in the DOM).
If you're dealing with SVG elements, you can use the same parent/child/commit technique. Just use two nested g, and use transform=translate(dx,dy) instead of style.left=dx, style.top=dy

On each mousemove, the draggable element positioning is getting reset to zero

I'm working on a component that moves a parts diagram around in a container. Right now everything works great on the first mousemove, but on the second the positioning styles are getting reset to zero.
I re-wrote the code outside of Vue and also made a codepen for your viewing.
Codepen: https://codepen.io/paytonburd/pen/WKqEjo
Code:
let diagram = document.getElementById('diagram')
let diagramImg = document.getElementById('diagram-image')
let startX;
let startY;
let walkX;
let walkY;
let dragging = false;
diagram.addEventListener('mousedown', (e) => {
dragging = true;
startX = e.pageX - diagram.offsetLeft;
startY = e.pageY - diagram.offsetTop;
})
diagram.addEventListener('mousemove', (e) => {
if (!dragging) return;
e.preventDefault();
let x = e.pageX - diagram.offsetLeft;
let y = e.pageY - diagram.offsetTop;
walkX = x - startX
walkY = y - startY
console.log(walkX, walkY)
diagramImg.style.top = walkY + 'px'
diagramImg.style.left = walkX + 'px'
})
diagram.addEventListener('mouseleave', () => {
dragging = false;
})
diagram.addEventListener('mouseup', () => {
dragging = false;
})
When you mouse down, you always set the startX and startY relative to the position of the diagram, which is always at 0, 0 and never moves.
I think what you want is to instead set them to relative to the current position of the diagram image instead:
let diagram = document.getElementById('diagram')
let diagramImg = document.getElementById('diagram-image')
let startX;
let startY;
let walkX;
let walkY;
let dragging = false;
diagram.addEventListener('mousedown', (e) => {
dragging = true;
//This is where it went wrong
startX = e.pageX - diagramImg.offsetLeft;
startY = e.pageY - diagramImg.offsetTop;
})
diagram.addEventListener('mousemove', (e) => {
if (!dragging) return;
e.preventDefault();
let x = e.pageX - diagram.offsetLeft;
let y = e.pageY - diagram.offsetTop;
walkX = x - startX
walkY = y - startY
console.log(walkX, walkY)
diagramImg.style.top = walkY + 'px'
diagramImg.style.left = walkX + 'px'
})
diagram.addEventListener('mouseleave', () => {
dragging = false;
})
diagram.addEventListener('mouseup', () => {
dragging = false;
})
https://codepen.io/anon/pen/yqdzyq?editors=1111

How I can get the X and Y from some move event on canvas?

I am developing an app with Cordova + Onsen Ui 2 + javascript, but I am getting problems to get the coordinates X and Y from javascript move events. I tried mousemove (it didn't fire) and drag (but I got undefined when I tried to get pageX or clientX from event object). I didn't find any example about drawing with canvas yet. Thanks you all in advance!
Javascript:
var canvasListener = function(){
canvas = document.getElementById("canvas");
canvas.addEventListener('mousedown', function(event){
var coordinates = painting(event);
});
canvas.addEventListener('drag', function(event){
var coordinates = painting(event);
});
canvas.addEventListener('mouseup', function(event){
var coordinates = painting(event);
});
}
function painting(event){
var x = event.clientX;
var y = event.clientY;
var touchX = x - signatureCanvas.offsetLeft;
var touchY = y - signatureCanvas.offsetTop;
var localCoordinates;
if(event.type == 'mouseup'){
localCoordinates = {
x: 0,
y: 0
};
}else{
localCoordinates = {
x: touchX,
y: touchY
};
}
return localCoordinates;
}
Html:
<canvas id="canvas"></canvas>
Try this code. On moving mouse over the canvas the current mouse(x,y) co-ordinates will be printed in screen. Hope this logic only you are looking for.
JsFiddle
HTML
<canvas width="300" height="225"></canvas>
<span class="first">Move the mouse over the div.</span>
<span></span>
JS
var canvas = $('canvas');
canvas.mousemove(function(e){
var pageCrds = '('+ e.pageX +', '+ e.pageY +')',
clientCrds = '('+ e.clientX +', '+ e.clientY +')';
$('span:first').text('(e.pageX, e.pageY) - '+ pageCrds);
$('span:last').text('(e.clientX, e.clientY) - '+ clientCrds);
});
Generally you'll want to use event.clientX, event.clientY, and the results of canvas.getBoundingClientRect();. Here's an example, using the MouseMove event:
can.addEventListener('mousemove', function(e) {
var canwidth = can.width;
var canheight = can.height;
var bbox = can.getBoundingClientRect();
var mx = e.clientX - bbox.left * (canwidth / bbox.width);
var my = e.clientY - bbox.top * (canheight / bbox.height);
// output it to see results:
ctx.fillRect(mx, my, 1, 1);
})
This works with all mouse/touch events on all platforms, and accommodates a lot of difficult CSS situations.
Live example: http://codepen.io/simonsarris/pen/NNVQpj?editors=1010
Try this..
var damagesCanvas = document.getElementById('damagesCanvas');
damagesCanvas.addEventListener('click', function (evt) {
var pos = getMousePos(damagesCanvas, evt);
}, false);
function getMousePos(damagesCanvas, evt) {
var rect = damagesCanvas.getBoundingClientRect();
return {
X: evt.clientX - rect.left,
Y: evt.clientY - rect.top
};
}

Categories