I'm new to Javascript, but getting close to finishing the vanilla JS for a mock desktop UI project: opening aside
s as "windows" that can be reordered, selected, and moved independently anywhere on the page. (I'm building a blog that mimics a desktop.)
I'm having trouble on that last part: getting my drag & drop functions to target one aside
at a time. I have three aside
s that I want to drag, drop, then fix to the page while other aside
s are moved. The first drag & drop often works, but subsequent moves will capture and move all of the other open "windows" together.
I think the issue might be in how I've ordered or layered my functions; I'm brand new to JS, but am trying to learn the ropes by reinventing wheels.
I have set a function to add the class ".active" (which adds position: absolute
and transform: translateZ(10)
to each aside
onclick) and remove it from the others.
And set event listeners, so if a window is clicked again, it will allow drag & drop. Attempting to prevent my collective moving issue, I separated each drag & drop function by window, but it didn't work. This makes me think the issue could be in the drag & drop functions themselves.
const buttonOne = document.querySelector("#toggle1");
const buttonTwo = document.querySelector("#toggle2");
const buttonThree = document.querySelector("#toggle3");
const windowOne = document.querySelector("#window1");
const windowTwo = document.querySelector("#window2");
const windowThree = document.querySelector("#window3");
function toggleOne() {
if (windowOne.style.display === "none") {
windowOne.style.display = "block";
} else {
windowOne.style.display = "none";
}
}
function toggleTwo() {
if (windowTwo.style.display === "none") {
windowTwo.style.display = "block";
} else {
windowTwo.style.display = "none";
}
}
function toggleThree() {
if (windowThree.style.display === "none") {
windowThree.style.display = "block";
} else {
windowThree.style.display = "none";
}
}
function bringForward(elem) {
var windows = document.querySelectorAll("aside");
for (i = 0; i < windows.length; i++) {
windows[i].classList.remove('active');
windows[i].style.draggable === "false"
windows[i].style.position === "fixed";
}
elem.classList.add('active');
elem.style.draggable === "true";
elem.style.position === "absolute";
}
windowOne.addEventListener('click', function() {
if (windowOne.classList.contains('active')) {
function dragStart(e) {
var style = window.getComputedStyle(e.target, null);
e.dataTransfer.setData("text/plain",
(parseInt(style.getPropertyValue("left"), 10) - e.clientX) + ',' + (parseInt(style.getPropertyValue("top"), 10) - e.clientY));
}
function dragOver(e) {
e.preventDefault();
return false;
}
function dropDown(e) {
var offset = e.dataTransfer.getData("text/plain").split(',');
var dm = document.getElementById('window1');
dm.style.left = (e.clientX + parseInt(offset[0], 10)) + 'px';
dm.style.top = (e.clientY + parseInt(offset[1], 10)) + 'px';
e.preventDefault();
return false;
}
var dm = document.getElementById('window1');
dm.addEventListener('dragstart', dragStart, false);
document.body.addEventListener('dragover', dragOver, false);
document.body.addEventListener('drop', dropDown, false);
} else {
windowOne.classList.add('active');
}
})
windowTwo.addEventListener('click', function() {
if (windowTwo.classList.contains('active')) {
function dragStart2(e) {
var style = window.getComputedStyle(e.target, null);
e.dataTransfer.setData("text/plain",
(parseInt(style.getPropertyValue("left"), 10) - e.clientX) + ',' + (parseInt(style.getPropertyValue("top"), 10) - e.clientY));
}
function dragOver2(e) {
e.preventDefault();
return false;
}
function dropDown2(e) {
var offset2 = e.dataTransfer.getData("text/plain").split(',');
var dm2 = document.getElementById('window2');
dm2.style.left = (e.clientX + parseInt(offset2[0], 10)) + 'px';
dm2.style.top = (e.clientY + parseInt(offset2[1], 10)) + 'px';
e.preventDefault();
return false;
}
var dm2 = document.getElementById('window2');
dm2.addEventListener('dragstart', dragStart2, false);
document.body.addEventListener('dragover', dragOver2, false);
document.body.addEventListener('drop', dropDown2, false);
} else {
windowTwo.classList.add('active');
}
})
windowThree.addEventListener('click', function() {
if (windowThree.classList.contains('active')) {
function dragStart3(e) {
var style = window.getComputedStyle(e.target, null);
e.dataTransfer.setData("text/plain",
(parseInt(style.getPropertyValue("left"), 10) - e.clientX) + ',' + (parseInt(style.getPropertyValue("top"), 10) - e.clientY));
}
function dragOver3(e) {
e.preventDefault();
return false;
}
function dropDown3(e) {
var offset3 = e.dataTransfer.getData("text/plain").split(',');
var dm3 = document.getElementById('window3');
dm3.style.left = (e.clientX + parseInt(offset3[0], 10)) + 'px';
dm3.style.top = (e.clientY + parseInt(offset3[1], 10)) + 'px';
e.preventDefault();
return false;
}
var dm3 = document.getElementById('window3');
dm3.addEventListener('dragstart', dragStart3, false);
document.body.addEventListener('dragover', dragOver3, false);
document.body.addEventListener('drop', dropDown3, false);
} else {
windowThree.classList.add('active');
}
})
#frame {
width: 99%;
height: 600px;
border-width: 3px;
border-color: black;
border-style: solid;
border-radius: 1%;
background-color: beige;
}
.active {
position: absolute;
background-color: #cf5c3f;
transform: translateZ(10);
}
#window1 {
display: none;
left: 4em;
top: 4em;
width: 300px;
height: 200px;
text-align: center;
border-width: 2px;
border-color: black;
border-style: solid;
border-radius: 1%;
color: black;
font-size: 2em;
font-family: Verdana, Geneva, Tahoma, sans-serif;
}
#window2 {
display: none;
left: 6em;
top: 6em;
width: 300px;
height: 200px;
text-align: center;
border-width: 2px;
border-color: black;
border-style: solid;
border-radius: 1%;
color: black;
font-size: 2em;
font-family: Verdana, Geneva, Tahoma, sans-serif;
}
#window3 {
display: none;
left: 8em;
top: 8em;
width: 300px;
height: 200px;
text-align: center;
border-width: 2px;
border-color: black;
border-style: solid;
border-radius: 1%;
color: black;
font-size: 2em;
font-family: Verdana, Geneva, Tahoma, sans-serif;
}
#toggle1,
#toggle2,
#toggle3 {
float: right;
margin-top: 20px;
margin-right: 30px;
width: 100px;
height: 70px;
}
aside {
position: fixed;
}
<div id="frame">
<button id="toggle1" onclick="toggleOne()">toggle</button>
<button id="toggle2" onclick="toggleTwo()">toggle</button>
<button id="toggle3" onclick="toggleThree()">toggle</button>
<aside draggable="true" id="window1" onclick="bringForward(this)" style="display: none">
I'm on the move!
</aside>
<aside draggable="true" id="window2" onclick="bringForward(this)" style="display: none">
Here we go!
</aside>
<aside draggable="true" id="window3" onclick="bringForward(this)" style="display: none">
All together now!
</aside>
</div>
Any advice is appreciated - I really hope I can make this work.
You are more or less right about the position of the elements. I find it easier to calculate if the parent element (here, the frame) is positioned relative and then each window is positioned absolute -- so, that becomes absolute within the frame.
I can see that you are a beginner in programming. Don't repeat functions for each element. That doesn't scale and it becomes impossible to maintain. A function can have parameter. In the code that I have in the example, that is the event (e
), and the event is different for each button, clicked or dragged window. The function is reusable in that sense.
And for the dragging: You need the initial offset of the mouse in relation to the window on dragstart
, so that the right position can bee calculated on the drop.
const buttons = document.querySelector("#buttons");
const frame = document.querySelector("#frame");
buttons.addEventListener('click', toggleWindow);
frame.addEventListener('click', bringForward);
frame.addEventListener('dragstart', dragStart);
frame.addEventListener('dragover', dragOver);
frame.addEventListener('drop', dropEnd);
function toggleWindow(e) {
if (e.target.value) {
let win = document.getElementById(`window${e.target.value}`);
win.classList.toggle('show');
}
}
function bringForward(e) {
let win = e.target.closest('.window');
let frame = e.target.closest('#frame');
if (win && frame) {
let windows = frame.querySelectorAll("aside");
windows.forEach(win => win.draggable = false);
win.draggable = true;
}
}
function dragStart(e) {
let data = JSON.stringify({
id: e.target.id,
x: e.offsetX,
y: e.offsetY
});
e.dataTransfer.setData('text/plain', data);
}
function dragOver(e) {
e.preventDefault();
}
function dropEnd(e) {
e.preventDefault();
let data = JSON.parse(e.dataTransfer.getData("text/plain"));
let win = document.getElementById(data.id);
let frame = e.target.closest('#frame');
if (win) {
win.style.left = `${e.clientX - frame.offsetLeft - data.x}px`;
win.style.top = `${e.clientY - frame.offsetTop - data.y}px`;
}
}
#frame {
width: 99%;
height: 600px;
border-width: 3px;
border-color: black;
border-style: solid;
border-radius: 1%;
background-color: beige;
position: relative;
}
aside[draggable="true"] {
background-color: #cf5c3f;
z-index: 100;
}
.window {
display: none;
width: 300px;
height: 200px;
text-align: center;
border-width: 2px;
border-color: black;
border-style: solid;
border-radius: 1%;
color: black;
font-size: 2em;
font-family: Verdana, Geneva, Tahoma, sans-serif;
display: none;
position: absolute;
}
.show {
display: block;
}
#buttons button {
float: right;
margin-top: 20px;
margin-right: 30px;
width: 100px;
height: 70px;
}
aside {
position: fixed;
}
<div id="frame">
<div id="buttons">
<button value="1">toggle 1</button>
<button value="2">toggle 2</button>
<button value="3">toggle 3</button>
</div>
<aside id="window1" class="window" style="left: 40px; top: 40px;">
I'm on the move!
</aside>
<aside id="window2" class="window" style="left: 60px; top: 60px">
Here we go!
</aside>
<aside id="window3" class="window" style="left: 80px; top: 80px">
All together now!
</aside>
</div>