Multiple Draggable divs "tangle" - javascript

I'm trying to develop a maths game for kids that does not need the keyboard.
Round divs are draggable into blue boxes (also divs).
Dragging down quickly on any round div over the other round divs causes them to wander! (touch or slow mouse both work fine).
sample reference removed 21.2.2022 all relevant information is in the useful answer thanks to Oleg
My hunch is a 3D transform at the end of the script is to blame but I don't really understand it.
Any help Greatly appreciated

If I understood correctly, then in your example, you'd better use Pointer Events and Pointer capture. Your example with PointerEvents below:
const box1 = document.querySelector("#box1");
const box2 = document.querySelector("#box2");
const box3 = document.querySelector("#box3");
const box4 = document.querySelector("#box4");
const box5 = document.querySelector("#box5");
const item1 = document.querySelector("#item1");
const item2 = document.querySelector("#item2");
const item3 = document.querySelector("#item3");
const item4 = document.querySelector("#item4");
const item5 = document.querySelector("#item5");
let active = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
//////////////////////////////////////////
// Moved from dragend
//////////////////////////////////////////
const box1Size = box1.getBoundingClientRect(); //the size of box1
const box2Size = box2.getBoundingClientRect(); //the size of box2
const box3Size = box3.getBoundingClientRect(); //the size of box2
const box4Size = box4.getBoundingClientRect(); //the size of box2
const box5Size = box5.getBoundingClientRect(); //the size of box2
//Add Event Listeners for Touchscreens
//container.addEventListener("touchstart", dragStart, false);
//container.addEventListener("touchend", dragEnd, false);
//container.addEventListener("touchmove", drag, false);
item1.addEventListener('pointerdown', dragStart);
item1.addEventListener('pointerup', dragEnd);
item2.addEventListener('pointerdown', dragStart);
item2.addEventListener('pointerup', dragEnd);
item3.addEventListener('pointerdown', dragStart);
item3.addEventListener('pointerup', dragEnd);
item4.addEventListener('pointerdown', dragStart);
item4.addEventListener('pointerup', dragEnd);
item5.addEventListener('pointerdown', dragStart);
item5.addEventListener('pointerup', dragEnd);
function dragStart(e) { //when the drag starts
this.addEventListener('pointermove', drag);
this.setPointerCapture(e.pointerId);
if (e.type === "touchstart") { //if its a touchscreen
initialX = e.touches[0].clientX - xOffset; //set initial x-cordinate to where it was before drag started
initialY = e.touches[0].clientY - yOffset; //set initial y-cordinate to where it was before drag started
} else { //if its not a touchscreen (mouse)
initialX = e.clientX - xOffset; //set initial x-cordinate to where it was before drag started
initialY = e.clientY - yOffset; //set initial y-cordinate to where it was before drag started
}
}
function dragEnd(e) { //when the drag ends
this.removeEventListener('pointermove', drag);
this.releasePointerCapture(e.pointerId);
const elementSize = e.target.getBoundingClientRect(); //the size of the circle
if (elementSize.left >= box1Size.left && elementSize.right <= box1Size.right && elementSize.top >= box1Size.top && elementSize.bottom <= box1Size.bottom) {
//if the circle is in box1
initialX = currentX; //set the initial x-cordinate to where it is now
initialY = currentY; //set the initial y-cordinate to where it is now
box1.innerHTML = box1.innerHTML + " " + e.target.innerHTML;
} else if (elementSize.left >= box2Size.left && elementSize.right <= box2Size.right && elementSize.top >= box2Size.top && elementSize.bottom <= box2Size.bottom) {
//if the circle is in box2
initialX = currentX; //set the initial x-cordinate to where it is now
initialY = currentY; //set the initial y-cordinate to where it is now
box2.innerHTML = box2.innerHTML + " " + e.target.innerHTML;
} else if (elementSize.left >= box3Size.left && elementSize.right <= box3Size.right && elementSize.top >= box3Size.top && elementSize.bottom <= box3Size.bottom) {
//if the circle is in box3
initialX = currentX; //set the initial x-cordinate to where it is now
initialY = currentY; //set the initial y-cordinate to where it is now
box3.innerHTML = box3.innerHTML + " " + e.target.innerHTML;
} else if (elementSize.left >= box4Size.left && elementSize.right <= box4Size.right && elementSize.top >= box4Size.top && elementSize.bottom <= box4Size.bottom) {
//if the circle is in box4
initialX = currentX; //set the initial x-cordinate to where it is now
initialY = currentY; //set the initial y-cordinate to where it is now
box4.innerHTML = box4.innerHTML + " " + e.target.innerHTML;
} else if (elementSize.left >= box5Size.left && elementSize.right <= box5Size.right && elementSize.top >= box5Size.top && elementSize.bottom <= box5Size.bottom) {
//if the circle is in box5
initialX = currentX; //set the initial x-cordinate to where it is now
initialY = currentY; //set the initial y-cordinate to where it is now
box5.innerHTML = box5.innerHTML + " " + e.target.innerHTML;
} else { //if the circle is in neither box1 nor box2
/*currentX = 0;
currentY = 0;
initialX = 0;
initialY = 0;
xOffset = 0;
yOffset = 0;
setTranslate(0, 0, e.target);*/
}
currentX = 0;
currentY = 0;
initialX = 0;
initialY = 0;
xOffset = 0;
yOffset = 0;
setTranslate(0, 0, e.target);
active = false; //the drag is no longer active
}
function drag(e) { //when the circle is being dragged
if (e.type === "touchmove") { //if its a touchscreen
currentX = e.touches[0].clientX - initialX; //set current x-cordinate to where it is now
currentY = e.touches[0].clientY - initialY; //set current y-cordinate to where it is now
} else { //if its not a touchscreen (mouse)
currentX = e.clientX - initialX; //set current x-cordinate to where it is now
currentY = e.clientY - initialY; //set current y-cordinate to where it is now
}
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, e.target);
}
function setTranslate(xPos, yPos, e) {
e.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
}
.flex-container {
display: flex;
}
[id^=i] {
width: 80px;
height: 80px;
margin: auto;
background-color: ivory;
border: 1px solid DarkRed;
border-radius: 50%;
touch-action: none;
user-select: none;
/*position: absolute;*/
font-size: 40px;
text-align: center;
}
[id^=b] {
/* this applies to all elements with ids beginning with "b" */
font-size: 40px;
border: 2px solid LightGrey;
width: 100px;
height: 100px;
background-color: blue;
color: ivory;
text-align: center;
}
<h2>Trial</h2>
<div>
<table>
<tr>
<td>
<div id="box1" class="box" ;></div>
</td>
<td>
<div id="item1" draggable="false">A</div>
</td>
</tr>
<tr>
<td>
<div id="box2" class="box"></div>
</td>
<td>
<div id="item2">B</div>
</td>
</tr>
<tr>
<td>
<div id="box3" class="box"></div>
</td>
<td>
<div id="item3">C</div>
</td>
</tr>
<tr>
<td>
<div id="box4" class="box"></div>
</td>
<td>
<div id="item4">D</div>
</td>
</tr>
<tr>
<td>
<div id="box5" class="box"></div>
</td>
<td>
<div id="item5">E</div>
</td>
</tr>
</table>
</div>

Related

CSS animated element animates on wrong place after touch-move transform

I am using CSS to define a ball animation with certain class. This works fine when adding the class on click, the ball just jumps on one place.
However, when I add the class after moving the ball using touchmove/mousedrag and transform, the ball jumps on its original position, instead its new position I have moved it to.
Is there any way I can make the ball jump on the new position but still keep the jump animation nicely in the CSS?
Thanks if you're reading this.
fiddle here: https://jsfiddle.net/9bvucd3q/
code here:
<html>
<head>
<!--<meta name="viewport"
content="width=device-width,
initial-scale=1.0,
user-scalable=no" />
<title>Drag/Drop/Bounce</title>-->
<style>
#item {
width: 100px;
height: 100px;
background-color: rgb(245, 230, 99);
border: 10px solid rgba(136, 136, 136, .5);
border-radius: 50%;
touch-action: none;
user-select: none;
position: absolute;
}
.bugout{animation: card-out 0.6s cubic-bezier(.8,.2,.1,0.8);}
#keyframes card-out {
0% { z-index: 20; transform: translateY(0%) rotate(0deg); }
5% { z-index: 20; transform: translateY(-5%) rotate(-4deg); }
49% { z-index:20;transform: translateY(-120%) ; }
100% { z-index:5;transform: translateY(-120%) ; }
}
#box1 {
width: 200px;
height: 200px;
background-color: red;
}
</style>
</head>
<body>
<h1>Drag and Drop</h1>
<div id="item"></div>
<div id="box1">
</div>
<script>
var dragItem = document.querySelector("#item");
var box1 = document.querySelector("#box1");
var container = dragItem;
//Declare Variables
var active = false;
var currentX;
var currentY;
var initialX;
var initialY;
var xOffset = 0;
var yOffset = 0;
//Add Event Listeners for Touchscreens
container.addEventListener("touchstart", dragStart, false);
container.addEventListener("touchend", dragEnd, false);
container.addEventListener("touchmove", drag, false);
//Add Event Listeners for Mice
container.addEventListener("mousedown", dragStart, false);
container.addEventListener("mouseup", dragEnd, false);
container.addEventListener("mousemove", drag, false);
function dragStart(e) { //when the drag starts
if (e.type === "touchstart") { //if its a touchscreen
initialX = e.touches[0].clientX - xOffset; //set initial x-cordinate to where it was before drag started
initialY = e.touches[0].clientY - yOffset; //set initial y-cordinate to where it was before drag started
} else { //if its not a touchscreen (mouse)
initialX = e.clientX - xOffset; //set initial x-cordinate to where it was before drag started
initialY = e.clientY - yOffset; //set initial y-cordinate to where it was before drag started
}
if (e.target === dragItem) { //if user is dragging circle
active = true; //the drag is active
dragItem.classList.remove('bugout');
}
}
function dragEnd(e) { //when the drag ends
const box1Size = box1.getBoundingClientRect(); //the size of box1
const elementSize = dragItem.getBoundingClientRect(); //the size of the circle
if (elementSize.left >= box1Size.left && elementSize.right <= box1Size.right && elementSize.top >= box1Size.top && elementSize.bottom <= box1Size.bottom) { //if the circle is in box1
initialX = currentX; //set the initial x-cordinate to where it is now
initialY = currentY; //set the initial y-cordinate to where it is now
dragItem.classList.add('bugout');
}
else { //if the circle is in neither box1 nor box2
currentX = 0;
currentY = 0;
initialX = 0;
initialY = 0;
xOffset = 0;
yOffset = 0;
setTranslate(0, 0, dragItem);
}
active = false; //the drag is no longer active
}
function drag(e) { //when the circle is being dragged
if (active) { //if the drag is active
e.preventDefault(); //the user cant do anything else but drag
if (e.type === "touchmove") { //if its a touchscreen
currentX = e.touches[0].clientX - initialX; //set current x-cordinate to where it is now
currentY = e.touches[0].clientY - initialY; //set current y-cordinate to where it is now
} else { //if its not a touchscreen (mouse)
currentX = e.clientX - initialX; //set current x-cordinate to where it is now
currentY = e.clientY - initialY; //set current y-cordinate to where it is now
}
//Im not sure what this does but it dosnt work without it
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, dragItem);
}
}
function setTranslate(xPos, yPos, el) { //Im not sure what this does but it dosnt work without it
el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
}
</script>
</body>
</html>```
So in the end I have decided to use GSAP, a really fast animation lib which also supports dragging via its Draggable plugin. It's just simpler if I have to code the animations in JS as opposed as to having them nicely in CSS.

Detect mouseMove of X pixels

I'm trying to trigger an event when the cursor position moves X amount of pixels, say 100px. So, for every 100px the cursor moves in either X or Y direction, I trigger an event. This should continue for every 100px movement.
I've successfully detected the 'current' X and Y position of the cursor, and have set a pixel threshold, but am struggling with the maths on the rest. Can anyone help?
$(window).on('mousemove', function(e){
// Vars
var cursorX = e.clientX;
var cursorY = e.clientY;
var cursorThreshold = 100;
... detect every 100px movement here...
});
You need to keep track of the old cursor positions. Then you can calculate the distance using the Pythagorean theorem:
totalDistance += Math.sqrt(Math.pow(oldCursorY - cursorY, 2) + Math.pow(oldCursorX - cursorX, 2))
This works in any direction.
Example:
Note: Unlike #wayneOS's approach (+1 from me) I do not keep track of the direction.
It's a rather minimalistic implementation.
var totalDistance = 0;
var oldCursorX, oldCursorY;
$(window).on("mousemove", function(e){
var cursorThreshold = 100;
if (oldCursorX) totalDistance += Math.sqrt(Math.pow(oldCursorY - e.clientY, 2) + Math.pow(oldCursorX - e.clientX, 2));
if (totalDistance >= cursorThreshold){
console.log("Mouse moved 100px!");
totalDistance = 0;
}
oldCursorX = e.clientX;
oldCursorY = e.clientY;
});
.d {
width: 0;
height: 0;
border-style: solid;
border-width: 100px 100px 0 0;
border-color: #e54646 transparent transparent transparent;
}
.s { display: flex; }
.p1 { margin-left: 100px; }
.p2 { margin-right: 20px; padding-top: 20px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p class="p1">100px X</p>
<div class="s">
<p class="p2">100px Y</p>
<div class="d"></div>
</div>
To track mouse-movement for defined steps, you just need to save the last position and than check if the cursor has moved more than the threshold in one direction. Here is an example:
// Vars
var lastCursorX = null;
var lastCursorY = null;
var cursorThreshold = 100;
$(window).on('mousemove', function(e){
//set start-points
if (lastCursorX === null)
lastCursorX = e.clientX;
if (lastCursorY === null)
lastCursorY = e.clientY;
//check for move left
if (e.clientX <= lastCursorX - cursorThreshold) {
lastCursorX = e.clientX;
console.log (cursorThreshold + 'px moved left');
}
//check for move right
if (e.clientX >= lastCursorX + cursorThreshold) {
lastCursorX = e.clientX;
console.log (cursorThreshold + 'px moved right');
}
//check for move up
if (e.clientY <= lastCursorY - cursorThreshold) {
lastCursorY = e.clientY;
console.log (cursorThreshold + 'px moved up');
}
//check for move down
if (e.clientY >= lastCursorY + cursorThreshold) {
lastCursorY = e.clientY;
console.log (cursorThreshold + 'px moved down');
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Draw a bounding box over an image present inside a modal

I am JavaScript Beginner and I am working on a small code where I have an image displayed inside a Modal and I am trying to draw a bounding box over the image.
I have tried the following code to draw the bounding box over the image.
Html: ( the code for the modal that shows the image)
<div id="myModal" class="modal">
<!-- The Close Button -->
<span class="close">×</span>
<!-- Modal Content (The Image) -->
<div id="imagearea" class="imagearea">
<img class="modal-content" id="img01">
</div>
</div>
Javascript: ( the logic that I tried to draw a bounding box over the image)
initDraw(document.getElementById('imgarea'));
function initDraw(imgarea) {
var mouse = {
x: 0,
y: 0,
startX: 0,
startY: 0
};
function setMousePosition(e) {
var ev = e || window.event; //Moz || IE
if (ev.pageX) { //Moz
mouse.x = ev.pageX + window.pageXOffset;
mouse.y = ev.pageY + window.pageYOffset;
} else if (ev.clientX) { //IE
mouse.x = ev.clientX + document.body.scrollLeft;
mouse.y = ev.clientY + document.body.scrollTop;
}
};
var element = null;
imgarea.onmousemove = function (e) {
setMousePosition(e);
if (element !== null) {
element.style.width = Math.abs(mouse.x - mouse.startX) + 'px';
element.style.height = Math.abs(mouse.y - mouse.startY) + 'px';
element.style.left = (mouse.x - mouse.startX < 0) ? mouse.x + 'px' : mouse.startX + 'px';
element.style.top = (mouse.y - mouse.startY < 0) ? mouse.y + 'px' : mouse.startY + 'px';
}
}
imagearea.onmouseup = function (e) {
if (element !== null) {
element = null;
imagearea.style.cursor = "default";
console.log("finsihed.");
} }
imagearea.onmousedown = function (e) {
if(element==null){
console.log("begun.");
mouse.startX = mouse.x;
mouse.startY = mouse.y;
element = document.createElement('div');
element.className = 'rectangle'
element.style.left = mouse.x + 'px';
element.style.top = mouse.y + 'px';
imagearea.appendChild(element)
imagearea.style.cursor = "crosshair";
e.preventDefault();
}
}
}
css: (for the bounding box)
.rectangle {
border: 10px solid red;
position: absolute;
}
.imagearea {
display: flex;
flex-direction: column;
float: left;
width: 35%;
max-width: 700px;
position: relative;
}
#img01{
position: absolute;
}
The above code displays the image in modal and when I try to draw the bounding box on the image, the cursor changes to crosshair as expected , but I could not see the bounding box when i draw it over the image. Just the cursor changes but the rectangle(bounding box) which I draw over the image is not visible.
Can some one help me with this. Thank you
There are two problems.
You can't append children to <img>.
Your calculations are based on the viewport, while border's CSS is relative to the image. There are multiple ways to fix that, the quickest one would be to subtract image's position from your mouse position, like this:
function setMousePosition(e) {
// your mouse calculations
const boundaries = e.currentTarget.getBoundingClientRect();
mouse.x -= boundaries.left;
mouse.y -= boundaries.top;
}
Also, make sure you use setMousePosition() in your mousedown handler.

Move a div within another div

I am trying to develop a simple app.
When we drag the small box inside the bigger box, the smaller box should move inside the bigger box.
However, it can't go outside the bigger box. I know how to move the smaller box, but I don't know how to keep it inside the bigger box. Can somebody help me, please?
As I mentioned, my code moves the small box properly but does not keep it inside the bigger box.
var guy=document.getElementById("guy");
var cont=document.getElementById("container");
var lastX,lastY; // Tracks the last observed mouse X and Y position
guy.addEventListener("mousedown", function(event) {
if (event.which == 1) {
lastX = event.pageX;
lastY = event.pageY;
addEventListener("mousemove", moved);
event.preventDefault(); // Prevent selection
}
});
function buttonPressed(event) {
if (event.buttons == null)
return event.which != 0;
else
return event.buttons != 0;
}
function moved(event) {
if (!buttonPressed(event)) {
removeEventListener("mousemove", moved);
} else {
var distX = event.pageX - lastX;
var distY = event.pageY - lastY;
guy.style.left =guy.offsetLeft + distX + "px";
guy.style.top = guy.offsetTop + distY + "px";
lastX = event.pageX;
lastY = event.pageY;
}
}
#container {
height:400px;
width:600px;
outline: 1px solid black;
position:absolute;
left:50px;
top: 0px;
background-color:green;
}
#guy {
position:absolute;
height:50px;
width:50px;
outline: 1px solid black;
background-color:red;
left: 200px;
top: 200px;
}
<div id="container" draggable="true" ></div>
<div id="guy"></div>
You need to restrict guy's position to the container's bounds. In other words, guy's x position can at minimum be the container's x position, at maximum the container's x position plus the container's width minus guy's witdh. The same goes for the y axis, but with height instead of width.
var guy=document.getElementById("guy");
var cont=document.getElementById("container");
var lastX,lastY; // Tracks the last observed mouse X and Y position
var minX = cont.offsetLeft;
var maxX = minX + cont.offsetWidth - guy.offsetWidth;
var minY = cont.offsetTop;
var maxY = minY + cont.offsetHeight - guy.offsetHeight;
guy.addEventListener("mousedown", function(event) {
if (event.which == 1) {
lastX = event.pageX;
lastY = event.pageY;
addEventListener("mousemove", moved);
event.preventDefault(); // Prevent selection
}
});
function buttonPressed(event) {
if (event.buttons == null)
return event.which != 0;
else
return event.buttons != 0;
}
function moved(event) {
if (!buttonPressed(event)) {
removeEventListener("mousemove", moved);
} else {
var distX = event.pageX - lastX;
var distY = event.pageY - lastY;
var targetX = guy.offsetLeft + distX;
var targetY = guy.offsetTop + distY;
guy.style.left = Math.min(maxX, Math.max(minX, targetX)) + "px";
guy.style.top = Math.min(maxY, Math.max(minY, targetY)) + "px";
lastX = event.pageX;
lastY = event.pageY;
}
}
#container {
height:200px;
width:300px;
outline: 1px solid black;
position:absolute;
left:50px;
top: 0px;
background-color:green;
}
#guy {
position:absolute;
height:50px;
width:50px;
outline: 1px solid black;
background-color:red;
left: 100px;
top: 100px;
}
<div id="container" draggable="true" ></div>
<div id="guy"></div>

Drag and drop position relative

Why when the ball is placed relative positioning when you press the mouse it bounces? Absolute positioning is when this doesn't happen.
var ball = document.querySelector('.ball');
ball.onmousedown = function(event) {
var shiftX = event.pageX - getCoords(this).left,
shiftY = event.pageY - getCoords(this).top;
this.style.position = 'relative';
this.zIndex = 10000;
function move(event) {
this.style.left = event.pageX - shiftX + 'px';
this.style.top = event.pageY - shiftY + 'px';
}
move.call(this, event);
document.onmousemove = function(event) {
move.call(ball, event);
};
this.onmouseup = function(event) {
document.onmousemove = this.onmouseup = null;
};
return false;
};
ball.ondragstart = function() {
return false;
};
function getCoords(elem) {
var box = elem.getBoundingClientRect();
return {
top: box.top + window.pageYOffset,
left: box.left + window.pageXOffset
};
}
body {
margin: 50px;
}
img {
cursor: pointer;
}
<img class="ball" src="https://js.cx/clipart/ball.svg" alt="" />
I guess it's because of the padding of the body element. Please explain.
it seems i found answer
Implementing drag and drop on relatively positioned elements in JavaScript
and as it says, with relative position, your element contain offset of all objects in his parent-tag
and parent's margin and padding too
so when you try to get elemtn's position, you should count it's real offset in parent, see in my code example work with count index
<html>
<body>
<div id=obj1 style="width:100px; height:100px; background:#000; position:relative; " ></div>
<div id=obj2 style="width:100px; height:100px; background:#000; position:relative; " ></div>
<div id=obj3 style="width:100px; height:100px; background:#000; position:relative; " ></div>
<script>
var dragObj, count=0;
function set_drag_drop(obj)
{
if(count>0){
// count margins and divs offset
// body{ margin:10px; }
// height:100px;
obj.adx = 10;
obj.ady = 10 + (count*100)
}else{
obj.adx = 0;
obj.ady = 0;
}
count++;
obj.onmousedown = function(e)
{
var rect = obj.getBoundingClientRect();
obj.dx = rect.left - e.clientX;
obj.dy = rect.top - e.clientY;
obj.isDown = true;
dragObj = this;
}
obj.onmouseup = function(e)
{
obj.isDown = false;
}
document.onmousemove = function(e)
{
if(dragObj && dragObj.isDown)
{
dragObj.style.left = e.pageX -dragObj.adx+ dragObj.dx +"px";
dragObj.style.top = e.pageY -dragObj.ady+ dragObj.dy + "px";
}
}
}
set_drag_drop(document.getElementById("obj1"));
set_drag_drop(document.getElementById("obj2"));
set_drag_drop(document.getElementById("obj3"));
</script>
</html>

Categories