Detect mouse drag and direction using pure javascript - javascript

I am trying to detect the mouse direction when dragging the mouse. When the mouse button is down and the user drags the mouse, I want the text to change to left or right depending on the mouse drag direction.
Here's my code.
var divOverlay = document.getElementById ("div");
var dragged = false
window.addEventListener('mousedown', function () { dragged = false })
document.addEventListener('mousemove', function () { dragged = true })
window.addEventListener('mouseup', function(e) {
if (dragged == true && e.pageX <= 0) {
direction = "left"
} else if (dragged == true && e.pageX >= 0) {
direction = "right"
}
divOverlay.innerHTML = direction;
oldx = e.pageX;
})
#div {
width: 100px;
height: 100px;
background: red;
}
<div id="div"></div>
I don't think I'm too far off but I can't workout what I am doing wrong so I need some help.

Here you go. It just needed a minor tweak.
var divOverlay = document.getElementById ("div");
var dragged = false
var oldX = 0;
window.addEventListener('mousedown', function (e) { oldX = e.pageX; dragged = false })
document.addEventListener('mousemove', function () { dragged = true })
window.addEventListener('mouseup', function(e) {
if (dragged == true && e.pageX < oldX) {
direction = "left"
} else if (dragged == true && e.pageX > oldX) {
direction = "right"
}
divOverlay.innerHTML = direction;
})
#div {
width: 100px;
height: 100px;
background: red;
}
<div id="div"></div>

You should add oldX to the position checking. You also need to initialize it.
var divOverlay = document.getElementById("div");
var dragged = false
var oldX = 0;
window.addEventListener('mousedown', function (e) {
dragged = false;
// set start position of drag
oldX = e.pageX;
});
document.addEventListener('mousemove', function () {
dragged = true
});
window.addEventListener('mouseup', function (e) {
// compare drag end position with start position
if (dragged == true && e.pageX <= oldX) {
direction = "left"
} else if (dragged == true && e.pageX > oldX) {
direction = "right"
}
divOverlay.innerHTML = direction;
});
#div {
width: 100px;
height: 100px;
background: red;
}
<div id="div"></div>

Having a separate function to handle the relative direction of the mouse on the X axis is more likely something suitable for what you would like to do. Instead of trying to update variables, remove the eventListener once the mouse is released.
See below as an example:
const body = document.body;
const container = document.getElementById('container');
const directionTag = document.getElementById("direction");
const moveFunc = function (e) {
let direction;
if (e.movementX < 0) {
container.style.backgroundColor = "red";
direction = "left";
} else if (e.movementX > 0) {
container.style.backgroundColor = "blue";
direction = "right";
}
if (typeof direction !== "undefined") {
directionTag.innerHTML = direction;
}
};
body.onmousedown = function () {
body.addEventListener("mousemove", moveFunc);
};
body.onmouseup = function () {
body.removeEventListener("mousemove", moveFunc);
};
#container {
height: 150px;
margin: 20px;
padding: 20px;
width: 300px;
border: 1px solid #888;
}
<body>
<div id="container">This will change when you hold down</div>
<p id="direction">Unset</p>
</body>

Related

What causes the glitch in getBoundingClientRect()?

I am struggling with a glitch caused by getBoundingClientRect() method. (see fiddle below). The goal is to make a bar following a cursor inside a container. The aforementioned method does not return valid results. I am a beginner in terms of JS - there is most probably an obvious reason behind this. I just can't find the anwswer.
https://jsfiddle.net/aveoL210/3/
var div_moving = document.getElementById('div_moving');
var parent_div = 'parent_div';
var movingDiv = {
getCoords: function(e) {
var rect = e.target.getBoundingClientRect();
var y_pos = rect.top;
console.log(y_pos);
var y = e.pageY;
y = y - y_pos;
return (y);
}
};
document.getElementById(parent_div).addEventListener('mousemove', function(e){
result = movingDiv.getCoords(e);
div_moving.style.top = result +'px';
div_moving.style.display = "initial";
});
document.getElementById(parent_div).addEventListener('mouseleave', function(){
div_moving.style.display = "none";
});
The target when moving the mouse down will alternate between the moving and parent div
while adding
if(e.target.id !== 'parent_div') return;
guard to the getCoords function fixes it a little, it's still glitchy
The better solution is to get the rect of the parent_div, regardless of the e.target
So .. try this
var div_moving = document.getElementById('div_moving');
var parent_div = document.getElementById('parent_div')
var movingDiv = {
getCoords: function(e) {
var rect = parent_div.getBoundingClientRect();
var y_pos = rect.top;
var y = e.pageY;
y = y - y_pos;
return (y);
}
};
parent_div.addEventListener('mousemove', function(e) {
result = movingDiv.getCoords(e);
div_moving.style.top = result + 'px';
div_moving.style.display = "initial";
});
parent_div.addEventListener('mouseleave', function() {
div_moving.style.display = "none";
});
#parent_div {
position: relative;
width: 50%;
height: 300px;
margin: 1em auto;
border: 1px solid #333;
background: #f0f0f0;
}
#div_moving {
display: none;
position: absolute;
width: 100%;
height: 5px;
margin: 0;
background: blue;
}
<div id="parent_div">
<div id="div_moving"></div>
</div>
In getCoords you want to get parent_div all the time, so you have to use e.currentTarget instead of e.target otherwise, when div_moving is under the cursor e.target return div_moving and all calculation goes wrong, it will move to top and when you move your mouse again all wrong things repeat.
var div_moving = document.getElementById('div_moving');
var parent_div = 'parent_div';
var movingDiv = {
getCoords: function(e) {
console.log(e.target.id)
var rect = e.currentTarget.getBoundingClientRect();
var y_pos = rect.top;
console.log(y_pos);
var y = e.pageY;
y = y - y_pos;
return (y);
}
};
document.getElementById(parent_div).addEventListener('mousemove', function(e){
result = movingDiv.getCoords(e);
div_moving.style.top = result +'px';
div_moving.style.display = "initial";
});
document.getElementById(parent_div).addEventListener('mouseleave', function(){
div_moving.style.display = "none";
});
The, what you called "glitch", is actually what is expected to happen. Let me tell you why:
The line e.target.getBoundingClientRect() is the source of the issue, precisely e.target.
The, so called glitch, happens when the mouse is on top of the blue line element and at this point e.target no longer points to your #parent_div and instead e.target is #div_moving.
A quick fix would be by caching #parent_div in a variable so can use it later on, and on the getCoords() method we simply replace e.target by that new variable.
Here's a live demo:
var parent_div = document.getElementById('parent_div');
var div_moving = document.getElementById('div_moving');
var movingDiv = {
getCoords: function(e) {
var rect = parent_div.getBoundingClientRect();
var y_pos = rect.top;
//console.log(y_pos);
var y = e.pageY;
y = y - y_pos;
return (y);
}
};
parent_div.addEventListener('mousemove', function(e) {
result = movingDiv.getCoords(e);
div_moving.style.top = result + 'px';
div_moving.style.display = "initial";
});
parent_div.addEventListener('mouseleave', function() {
div_moving.style.display = "none";
});
#parent_div {
position: relative;
width: 50%;
height: 300px;
margin: 1em auto;
border: 1px solid #333;
background: #f0f0f0;
}
#div_moving {
display: none;
position: absolute;
width: 100%;
height: 5px;
margin: 0;
background: blue;
}
<div id="parent_div">
<div id="div_moving"></div>
</div>
DISCLAIMAIR: The above demo did not change a lot from the OP's original code which, by the way, can be hugely improved which is out of the scope of my answer.

Wrong swipe direction js

Please tell me, if you start touchmove from the middle of the page and move to the right, then console.log() goes right, but if you move to the left and cross the middle of the screen, console.log() will show the opposite direction, although the swipe goes in the same direction. As I understand it, this is due to the fact that touchstart coordinates are fixed from where touchmove started. How to fix this error. (see video).
https://youtu.be/_pB49S2BR2o
var initialX = null;
var initialY = null;
$("#content, .menu").on("touchstart", function(e) {
initialX = e.touches[0].clientX;
initialY = e.touches[0].clientY;
});
$("#content, .menu").on("touchmove", function(e) {
var diffX = initialX - e.touches[0].clientX;
var diffY = initialY - e.touches[0].clientY;
if (Math.abs(diffX) > Math.abs(diffY)) {
if (diffX > 0) {
console.log("Right");
} else {
console.log("Left");
}
} else {
return;
}
var width = parseInt($("body").css("width"));
clientX = e.touches[0].clientX;
width = width - clientX;
if (width >= 0 || width <= 375) {
$(".menu").css("left", "-" + width + "px");
} else {}
})
body {
margin: 0px;
overflow: hidden;
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
}
.menu {
background: blue;
width: 100%;
height: 100%;
z-index: 1;
position: absolute;
left: -100%;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
#content {
width: 100%;
height: 100vh;
color: #000;
display: flex;
align-items: center;
justify-content: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
<body>
<div class="menu" style="transition-duration: 0ms;">Menu slide</div>
<div id="content">
Content slide
</div>
</body>
const text = document.querySelector('#direction');
const cursorEl = document.querySelector('#cursorDir');
let lastX = null;
let active = false;
$("#content, .menu").on("touchstart", function(evt) {
active = true;
});
$("#content, .menu").on("touchend", function(evt) {
active = false;
});
$("#content, .menu").on("touchmove", function(evt) {
if (!active) return; // Run only on mousedown
if (lastX === null) {
// Set first value
lastX = evt.clientX;
return;
}
const diff = lastX - evt.clientX;
if (diff === 0) return; // Break if there's 0 pixels difference
console.log("direction: " + (diff > 0 ? "Left" : "Right"));
lastX = evt.clientX; // Set next value to check the difference
});
Is this what you are looking for?
Note: For the purpose of running this in a browser, touchstart and touchmove are replaced by mousedown and mousemove
const text = document.querySelector('#direction');
const cursorEl = document.querySelector('#cursorDir');
let lastX = null;
let active = false;
cursorEl.addEventListener('mousedown', () => { active = true; });
document.documentElement.addEventListener('mouseup', () => { active = false; });
cursorEl.addEventListener('mousemove', (evt) => {
if (!active) return; // Run only on mousedown
if (lastX === null) {
// Set first value
lastX = evt.clientX;
return;
}
const diff = lastX - evt.clientX;
if (diff === 0) return; // Break if there's 0 pixels difference
text.innerHTML = "direction: " + (diff > 0 ? "Left" : "Right");
lastX = evt.clientX; // Set next value to check the difference
});
#cursorDir {
display: block;
width: 150px;
height: 150px;
}
#cursorDir {
background: blue;
}
Click and hold to activate
<div id="cursorDir"></div>
<div id="direction"><div>

Change opacity then hide on swipe up (CSS / JS)

I'm looking to change opacity and then completely hide div on swipe up on certain threshold, like in the video below or in Photoswipe:
https://www.loom.com/share/29741bdadc7846bfbc747d3870815340
Unfortunately most off libraries only allow to register actual event start end, but not the amount of swiped pixels. How would I get the actual swiped distance and connect it to the swipe event?
Note: You can apply the animations used in this example on other elements like an overlay instead. The technique is the same.
Here is some code to move up an element, fade it out and remove it from display. Note that I only implemented the PointerEvent-api. You should also implement a fallback.
A summary about what is going on:
Detect a pointerdown on the element and allow the pointer to be used outside the element with setPointerCapture().
Detect a pointermove on the element. If the mouse/touch is moved up, also move up the element. ( I also restricted movement to the left, right, bottom, but you don't have to)
Detect a pointerup. After releasePointerCapture() the pointer will once more only be available in the default element and not outside it. Depending on the amount the element has moved up, the element is returned to its original position or animated out.
class SwipeOutBehaviour {
constructor(element) {
this.element = element;
this.dy = null;
this.initial_y = null;
this.animation_frame_state = 'completed';
if( window.PointerEvent ) {
this.element.addEventListener('pointerdown', this.start_drag.bind(this), true);
this.element.addEventListener('pointermove', this.drag.bind(this), true);
this.element.addEventListener('pointerup', this.drag_end.bind(this), true);
} else {
//should use composition instead if you re serious, for this example I only implemented PointerEvent some browsers will use Tpuchevent and MouseEvent
}
}
start_drag( event ){
event.preventDefault();
// only respond to a single touch
if( event.touches && event.touches.length > 1 ) return;
// allow pointerevents outside the target
event.target.setPointerCapture(event.pointerId);
// set initial pos
this.initial_y = ( event.targetTouches ) ? event.targetTouches[0].clientY : event.clientY;
}
drag( event ){
event.preventDefault();
if( this.initial_y === null ) return;
if( this.animation_frame_state === 'pending' ) return;
this.dy = ( event.targetTouches ) ? Math.floor( event.targetTouches[0].clientY - this.initial_y ) : Math.floor( event.clientY - this.initial_y );
if( this.dy > 0 ) return;
this.animation_frame_state = 'pending'
window.requestAnimationFrame( () => {
this.element.style.transform = `translateY(${this.dy}px)`
this.animation_frame_state = 'completed';
});
}
drag_end(event) {
event.preventDefault();
if(event.touches && event.touches.length > 0) return;
event.target.releasePointerCapture(event.pointerId);
if( this.dy < -100 ) {
window.requestAnimationFrame( () => {
this.element.style.transition = 'opacity 500ms, translateY 200ms';
this.element.style.transform = `translateY(-175px)`;
this.element.style.opacity = `0`;
this.animation_frame_state = 'completed';
window.setTimeout( () => {
// set display to none, you could remove it from the DOM instead
this.element.style.display = 'none';
}, 500)
});
} else {
window.requestAnimationFrame( () => {
this.element.style.transform = `translateY(0px)`
this.animation_frame_state = 'completed';
});
}
this.initial_y = null;
}
}
let element = document.getElementById('container');
new SwipeOutBehaviour( element );
#container {
margin: auto;
width: 150px;
height: 150px;
border: 1px solid black;
}
#box-of-doom {
margin: auto;
width: 200px;
height: 200px;
border: 1px solid red;
background: orange;
}
p {
text-align: center;
}
<p>Drag the item in the box of doom<p>
<div id='box-of-doom'>
<p>The box of doom<p>
</div>
<div id='container'>
<img alt='a placeholder' src='https://via.placeholder.com/150' />
</div>
Note: This answer is inspired by this documentation/article from Google about touch events, so you may want to read more there.
With a lot of event listeners and computed properties; I made a quick code pen using W3's draggable function, but added the opacity change myself:
// Make the DIV element draggable:
dragElement(document.getElementById("mydiv"));
function dragElement(elmnt) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if (document.getElementById(elmnt.id + "header")) {
// if present, the header is where you move the DIV from:
document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
} else {
// otherwise, move the DIV from anywhere inside the DIV:
elmnt.onmousedown = dragMouseDown;
}
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
//change background opacity:
const background = document.getElementById("background");
const bgHeight = background.offsetHeight;
const elmntHeight = elmnt.offsetHeight;
const adjustedBottom = bgHeight - elmntHeight;
const percentage = 1 - elmnt.offsetTop / adjustedBottom;
console.log(percentage)
background.style.opacity = percentage;
}
function closeDragElement() {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
}
}
body {
margin: 0;
}
#background {
background: black;
width: 100vw;
height: 100vh;
position: absolute;
}
#mydiv {
position: absolute;
z-index: 9;
background-color: #f1f1f1;
border: 1px solid #d3d3d3;
text-align: center;
}
#mydivheader {
padding: 10px;
cursor: move;
z-index: 10;
background-color: #2196F3;
color: #fff;
}
<div id="background"></div>
<!-- Draggable DIV -->
<div id="mydiv">
<!-- Include a header DIV with the same name as the draggable DIV, followed by "header" -->
<div id="mydivheader">Click here to move</div>
<p>Move</p>
<p>this</p>
<p>DIV</p>
</div>
</div>
Far from perfect, but hopefully demonstrates an idea to expand on.

Dragging linked images (without firing the link)

I need to just drag/move around some images. Each image is linked to some other page. When I use drag functions I can drag the image but when I release it the link of the image fires up. I would need to drag around, release, and then click the image if I want to open the link. What can I do?
The JSFiddle: http://jsfiddle.net/grundum/wp0zhjbn/21/
I found How to HTML5-drag an element that has a link in it but is not clear and doesn't have a concrete answer. Any ideas?
Thanks in advance.
HTML
<div class="dragme" draggable="true">
<a draggable="false" href="https://placeholder.com/"><img class="dragme" draggable="false" src="https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/microformat.svg"></a>
</div>
CSS
.dragme {
position: relative;
width: 270px;
height: 203px;
cursor: move;
}
#draggable {
background-color: #ccc;
border: 1px solid #000;
}
JS
function startDrag(e) {
// determine event object
if (!e) {
var e = window.event;
}
// IE uses srcElement, others use target
var targ = e.target ? e.target : e.srcElement;
if (targ.className != 'dragme') {
return
};
// calculate event X, Y coordinates
offsetX = e.clientX;
offsetY = e.clientY;
// assign default values for top and left properties
if (!targ.style.left) {
targ.style.left = '0px'
};
if (!targ.style.top) {
targ.style.top = '0px'
};
// calculate integer values for top and left
// properties
coordX = parseInt(targ.style.left);
coordY = parseInt(targ.style.top);
drag = true;
// move div element
document.onmousemove = dragDiv;
}
function dragDiv(e) {
if (!drag) {
return
};
if (!e) {
var e = window.event
};
var targ = e.target ? e.target : e.srcElement;
// move div element
targ.style.left = coordX + e.clientX - offsetX + 'px';
targ.style.top = coordY + e.clientY - offsetY + 'px';
return false;
}
function stopDrag() {
drag = false;
}
window.onload = function() {
document.onmousedown = startDrag;
document.onmouseup = stopDrag;
}
When I use drag functions I can drag the image but when I release it
the link of the image fires up. I would need to drag around, release,
and then click the image if I want to open the link. What can I do?
Then how can you recognize if element is being dragged or clicked? Time interval between onmousedown, onmouseup calls? Mouse movement?
One thing for sure you have to get rid of anchor tag or call in its click event handler preventDefault() as its getting in the way.
Ugly example of how that might work.
let state = {
startDrag: false,
drag: false,
clicked: false,
offsetX: -1,
offsetY: -1,
target: null,
anchor: null
}
let href = "https://placeholder.com/";
window.addEventListener("load", (event) => {
state.anchor = document.querySelector("#anchor");
state.anchor.addEventListener('click', (event) => {
if(!state.clicked) {
event.preventDefault()
}
});
document.addEventListener('mousedown', startDrag);
document.addEventListener('mouseup', stopDrag);
})
function startDrag(event) {
const target = event.target ? event.target : event.srcElement;
state.target = target;
const { clientX, clientY } = event;
state.offsetX = clientX;
state.offsetY = clientY;
state.startDrag = true;
if (!target.style.left) {
target.style.left = '0px'
};
if (!target.style.top) {
target.style.top = '0px'
};
state.coordX = parseInt(target.style.left);
state.coordY = parseInt(target.style.top);
document.onmousemove = dragDiv;
}
function dragDiv({clientX, clientY}) {
if(!state.startDrag) {
return;
}
const { target, coordX, coordY, offsetX, offsetY } = state;
state.drag = state.startDrag;
target.style.left = coordX + clientX - offsetX + 'px';
target.style.top = coordY + clientY - offsetY + 'px';
return false;
}
function stopDrag() {
document.onmousemove = null;
if(state.startDrag && state.drag) {
// handle stop dragging;
}
else {
// handle click;
state.clicked = true;
state.anchor.click();
// or location.href = href;
}
state.clicked = false;
state.startDrag = false;
state.drag = false;
}
.dragme {
position: relative;
width: 270px;
height: 203px;
cursor: move;
}
#draggable {
background-color: #ccc;
border: 1px solid #000;
}
<div class="dragme" draggable="true">
<a id="anchor" draggable="false" href="https://placeholder.com/"><img class="dragme" draggable="false" src="https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/microformat.svg"></a>
</div>

Get percentage clicked of elements width along the x-axis

I have a page structure that looks like in the image below. When clicking anywhere in the #progress element I want to calculate how many percentages of it's full width that was clicked.
How can this be done?
< div id="progress" onMouseUp={ e => this._seekTo(e) }></div>
...
_seekTo(event) {
var progress = document.getElementById('progress');
console.log((event.clientX - progress.offsetLeft) / progress.offsetWidth * 100)
}
You can get the percentage click position of an elements width along the x-axis like:
document.getElementById('progress').addEventListener('click', function(e) {
var bcr = this.getBoundingClientRect();
console.log('You clicked to ', (e.clientX - bcr.left) / bcr.width);
});
e.clientX provides the horizontal coordinate within the application's client area at which the event occurred. source
Example progress bar:
function mouseSliderPosition(element, e) {
var bcr = element.getBoundingClientRect();
return {
x: Math.min(Math.max(0, (e.clientX - bcr.left) / bcr.width), 1),
y: Math.min(Math.max(0, (e.clientY - bcr.top) / bcr.height), 1)
}
};
function activateSlider(e) {
if (e.touches && e.touches.length > 1) {
return;
}
e.preventDefault();
window.activeSlider = this;
handleSliderMove(e);
}
function handleSliderMove(e) {
if (e.touches && e.touches.length > 1) {
return;
}
if (window.activeSlider) {
var progressBar = window.activeSlider.getElementsByClassName('progress-bar')[0];
var progressFill = window.activeSlider.getElementsByClassName('progress-fill')[0];
var value = mouseSliderPosition(progressBar, e.touches && e.touches[0] || e).x;
progressFill.style.transform = 'scaleX(' + value + ')';
}
}
function deactivateSlider(e) {
if (e.touches && e.touches.length > 0) {
return;
}
this.activeSlider = null;
}
document.querySelector('.progress-slider').addEventListener('mousedown', activateSlider)
document.querySelector('.progress-slider').addEventListener('touchstart', activateSlider)
window.addEventListener('mousemove', handleSliderMove);
window.addEventListener('mouseup', deactivateSlider);
window.addEventListener('touchmove', handleSliderMove);
window.addEventListener('touchend', deactivateSlider);
window.activeSlider = null;
.progress-slider {
padding: 10px;
cursor: pointer;
}
.progress-bar {
height: 2px;
background: rgba(100,100,100,0.5);
}
.progress-slider:hover .progress-bar {
height: 3px;
}
.progress-fill {
height: 100%;
background: rgba(255, 0, 0, 0.7);
transform-origin: 0 50%;
transform: scaleX(0);
}
<div class="progress-slider">
<div class="progress-bar"><div class="progress-fill"></div></div>
</div>

Categories

Resources