Draggable & Resizable Window on your Website
Markus Wedler
Posted on August 8, 2022
Create a draggable and resizable window that looks and feels like those in your desktop OS.
The source code is located at the end of the post.
❔How it works
Now, let's just briefly look at HTML and then I'll explain the JS part.
HTML
<div class="window"> <div class="resizer corner tl"></div> <div class="resizer corner tr"></div> <div class="resizer corner bl"></div> <div class="resizer corner br"></div> <div class="resizer t"></div> <div class="resizer b"></div> <div class="resizer l"></div> <div class="resizer r"></div> <div class="body"> <div class="topbar"> <div class="btns"> <div></div> <div></div> <div></div> </div> </div> <!-- your content here --> </div> </div>
Here we have 4 resizers at the corners and 4 at the sides of the window. We'll be able to drag the window holding its topbar.
JS
Let's declare variables-selectors of the window and topbar.
const xwindow = document.querySelector(".window") const topbar = document.querySelector(".topbar")
Dragging
We'll be able to drag the window while holding the mouse button and moving it. The
mousemove
listener will only be added whenmousedown
is triggered. So we also needmouseup
to removemousemove
when releasing the mouse button.
Let's now addmousedown
listener to the topbar and create amousedown()
function.//topbar can be whatever you want to hold while dragging topbar.addEventListener("mousedown", mousedown) function mousedown(){ window.addEventListener("mousemove", mousemove) window.addEventListener("mouseup", mouseup) function mousemove(){} function mouseup(){ window.removeEventListener("mousemove", mousemove) window.removeEventListener("mouseup", mouseup) } }
Ok, so now we want JS to track out cursor's position and move the windows as we move the cursor. To do so we need
e
(orevent
) which besides other stuff stores information about cursor position. Let's pass it as a function argument.function mousedown(e){ ... function mousemove(e){} ... }
To change the position of the window we can add the difference of the new and previous position of the cursor and add this difference to the left and top values of the window (our window is absolutely positioned). Let's get previous values of the cursor's coordinates. Before we move the cursor, these values are just the current values.
function mousedown(e){ ... let prevX = e.clientX let prevY = e.clientY function mousemove(e){} ... }
Now let's find the difference between new and previous positions.
... let prevX = e.clientX let prevY = e.clientY function mousemove(e){ let newX = e.clientX - prevX let newY = e.clientY - prevY } ...
JavaScript method
getBoundingClientRect()
returns a DOMRect object providing information about the size of an element and its position relative to the viewport. With it we can get the values of top and left properties and set new values.function mousemove(e){ let newX = e.clientX - prevX let newY = e.clientY - prevY const rect = xwindow.getBoundingClientRect() xwindow.style.left = rect.left + newX + "px" xwindow.style.top = rect.top + newY + "px" prevX = e.clientX prevY = e.clientY }
Congrats! Now we can drag our window.
Resizing
Let's get all the resizers.
const resizers = document.querySelectorAll(".resizer")
As with the topbar, we want to resize the window only when holding the resizer. So let's do the similar thing but in
for
loop to apply for every resizer.for (let resizer of resizers){ resizer.addEventListener("mousedown", mousedown) function mousedown(e){ window.addEventListener("mousemove", mousemove) window.addEventListener("mouseup", mouseup) let prevX = e.clientX let prevY = e.clientY function mousemove(e){ const rect = xwindow.getBoundingClientRect() prevX = e.clientX prevY = e.clientY } function mouseup(){ window.removeEventListener("mousemove", mousemove) window.removeEventListener("mouseup", mouseup) } } }
Now we need to tell JS which resizer we're holding. We can easily check it by their class. For example,
tl
means "top-left". To check if current resizer has such class at first we need to declarecurrentResiser
and assign it to e.target (the element we're holding).... let prevY = e.clientY let currentResizer = e.target ...
To check if there's some class in classes list of the resizer, we use
currentResizer.classList.contains("tl")
inif
statement. If yes, we need to calculate new size to the windows. Also if it's top, left or top-left resizer, we also have to changetop
andleft
.... function mousemove(e){ const rect = xwindow.getBoundingClientRect() if(currentResizer.classList.contains("br")){ xwindow.style.width = rect.width + (e.clientX - prevX) + "px" xwindow.style.height = rect.height + (e.clientY - prevY) + "px" } else if(currentResizer.classList.contains("bl")){ xwindow.style.width = rect.width + (prevX - e.clientX) + "px" xwindow.style.height = rect.height + (e.clientY - prevY) + "px" xwindow.style.left = rect.left + (e.clientX - prevX) + "px" } else if(currentResizer.classList.contains("tr")){ xwindow.style.width = rect.width + (e.clientX - prevX) + "px" xwindow.style.height = rect.height + (prevY - e.clientY) + "px" xwindow.style.top = rect.top + (e.clientY - prevY) + "px" } else if(currentResizer.classList.contains("tl")){ xwindow.style.width = rect.width + (prevX - e.clientX) + "px" xwindow.style.height = rect.height + (prevY - e.clientY) + "px" xwindow.style.top = rect.top + (e.clientY - prevY) + "px" xwindow.style.left = rect.left + (e.clientX - prevX) + "px" } else if(currentResizer.classList.contains("t")){ xwindow.style.height = rect.height + (prevY - e.clientY) + "px" xwindow.style.top = rect.top + (e.clientY - prevY) + "px" } else if(currentResizer.classList.contains("b")){ xwindow.style.height = rect.height + (e.clientY - prevY) + "px" } else if(currentResizer.classList.contains("l")){ xwindow.style.width = rect.width + (prevX - e.clientX) + "px" xwindow.style.left = rect.left + (e.clientX - prevX) + "px" } else if(currentResizer.classList.contains("r")){ xwindow.style.width = rect.width + (e.clientX - prevX) + "px" } prevX = e.clientX prevY = e.clientY } ...
Voila! Now we also can resize it.
The Full Code
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
<script defer src="index.js"></script>
</head>
<body>
<div class="window">
<div class="resizer corner tl"></div>
<div class="resizer corner tr"></div>
<div class="resizer corner bl"></div>
<div class="resizer corner br"></div>
<div class="resizer t"></div>
<div class="resizer b"></div>
<div class="resizer l"></div>
<div class="resizer r"></div>
<div class="body">
<div class="topbar">
<div class="btns">
<div></div>
<div></div>
<div></div>
</div>
</div>
<!-- your content here -->
</div>
</div>
</body>
</html>
SCSS
*{
margin: 0;
box-sizing: border-box;
}
.window{
width: 600px;
height: 400px;
min-width: 300px;
min-height: 200px;
position: absolute;
.resizer{
position: absolute;
z-index: 1;
width: 22px;
height: 22px;
background: #41a94c80; //delete
&.corner{
z-index: 2;
&.tl{ cursor: nw-resize; top: -5px; left: -5px; }
&.tr{ cursor: ne-resize; top: -5px; right: -5px; }
&.bl{ cursor: sw-resize; bottom: -5px; left: -5px; }
&.br{ cursor: se-resize; bottom: -5px; right: -5px; }
}
&.t, &.b{
width: 100%;
height: 14px;
}
&.l, &.r{
width: 14px;
height: 100%;
}
&.t{ cursor: n-resize; top: -5px; }
&.b{ cursor: s-resize; bottom: -5px; }
&.l{ cursor: w-resize; left: -5px; }
&.r{ cursor: e-resize; right: -5px; }
}
.body{
border-radius: 15px;
overflow: hidden;
height: 100%;
background: #f4f4f4;
.topbar{
width: 100%;
height: 60px;
background: #8c8c8c;
display: flex;
align-items: center;
padding: 0 30px;
.btns{
display: flex;
gap: 9px;
div{
height: 14px;
width: 14px;
border-radius: 50%;
&:nth-child(1){ background:#FF5F58; }
&:nth-child(2){ background:#FFBE2F; }
&:nth-child(3){ background:#2AC940; }
}
}
}
}
}
JS
const xwindow = document.querySelector(".window")
const topbar = document.querySelector(".topbar")
topbar.addEventListener("mousedown", mousedown)
function mousedown(e){
window.addEventListener("mousemove", mousemove)
window.addEventListener("mouseup", mouseup)
let prevX = e.clientX
let prevY = e.clientY
function mousemove(e){
let newX = e.clientX - prevX
let newY = e.clientY - prevY
const rect = xwindow.getBoundingClientRect()
xwindow.style.left = rect.left + newX + "px"
xwindow.style.top = rect.top + newY + "px"
prevX = e.clientX
prevY = e.clientY
}
function mouseup(){
window.removeEventListener("mousemove", mousemove)
window.removeEventListener("mouseup", mouseup)
}
}
const resizers = document.querySelectorAll(".resizer")
for (let resizer of resizers){
resizer.addEventListener("mousedown", mousedown)
function mousedown(e){
let currentResizer = e.target
let prevX = e.clientX
let prevY = e.clientY
window.addEventListener("mousemove", mousemove)
window.addEventListener("mouseup", mouseup)
function mousemove(e){
const rect = xwindow.getBoundingClientRect()
if(currentResizer.classList.contains("br")){
xwindow.style.width = rect.width + (e.clientX - prevX) + "px"
xwindow.style.height = rect.height + (e.clientY - prevY) + "px"
}
else if(currentResizer.classList.contains("bl")){
xwindow.style.width = rect.width + (prevX - e.clientX) + "px"
xwindow.style.height = rect.height + (e.clientY - prevY) + "px"
xwindow.style.left = rect.left + (e.clientX - prevX) + "px"
}
else if(currentResizer.classList.contains("tr")){
xwindow.style.width = rect.width + (e.clientX - prevX) + "px"
xwindow.style.height = rect.height + (prevY - e.clientY) + "px"
xwindow.style.top = rect.top + (e.clientY - prevY) + "px"
}
else if(currentResizer.classList.contains("tl")){
xwindow.style.width = rect.width + (prevX - e.clientX) + "px"
xwindow.style.height = rect.height + (prevY - e.clientY) + "px"
xwindow.style.top = rect.top + (e.clientY - prevY) + "px"
xwindow.style.left = rect.left + (e.clientX - prevX) + "px"
}
else if(currentResizer.classList.contains("t")){
xwindow.style.height = rect.height + (prevY - e.clientY) + "px"
xwindow.style.top = rect.top + (e.clientY - prevY) + "px"
}
else if(currentResizer.classList.contains("b")){
xwindow.style.height = rect.height + (e.clientY - prevY) + "px"
}
else if(currentResizer.classList.contains("l")){
xwindow.style.width = rect.width + (prevX - e.clientX) + "px"
xwindow.style.left = rect.left + (e.clientX - prevX) + "px"
}
else if(currentResizer.classList.contains("r")){
xwindow.style.width = rect.width + (e.clientX - prevX) + "px"
}
prevX = e.clientX
prevY = e.clientY
}
function mouseup(){
window.removeEventListener("mousemove", mousemove)
window.removeEventListener("mouseup", mouseup)
}
}
}
Posted on August 8, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.