0:00
Nearly all websites need a little bit of drag and drop functionality, so in this video I'm going to show you how to build a really simple, sortable drag and drop system using just HTML, CSS, and vanilla JavaScript
0:14
So let's get started now. But before we get started, I want to tell you how you can get an entire year of hosting for free by using today's video sponsor Atlantic.net Hosting
0:29
Atlantic.net is giving you an entire year of free hosting on their servers, and these are powerful servers that are more powerful than the servers I host my own website on
0:39
So if you go to Atlantic.net, linked in the description below, and use the code KYLE when you sign up, you're going to get an entire year of free hosting, as well as an additional $50 of credit to use however you see fit
0:52
So make sure you check that out because this is a great deal, and you're not going to find this much free hosting anywhere else
0:58
Welcome back to WebDev Simplified. My name's Kyle, and my job is to simplify the web for you so you can start building your dream projects sooner
1:07
So if that sounds interesting, make sure you subscribe to the channel for more videos just like this
1:12
Now on the right, I have the final version of the product we're going to be building
1:16
As you can see, it's very simply styled, but we can select any element in this list, and we can drag it around, and you can see we get this nice little ghost image appearing on our cursor
1:26
as well as an indicator of where the item is going to be placed, and then when we release the item, it's going to be put exactly where we want it
1:33
and we can drag items between any of these containers back up to the container, and it's really flexible, and the amount of code to create this is actually pretty small
1:41
So in order to get started, I just have an empty project open up in Visual Studio Code
1:46
We're going to create an index file called index.html. We're going to have a stylesheet called styles.css, just for these really basic styles
1:54
And then we're also going to have a script.js file for all of our different JavaScript
1:59
And now in our index.html, we just hit exclamation point, hit tab, or enter
2:04
It's going to fill out all of this boolean plate for us, and we can come in here and link our stylesheet, which is called styles.css
2:12
We can also link our JavaScript, which is just going to be called script.js, and we're going to make sure we give this the defer attribute so it loads after our HTML is done
2:22
Now inside of our HTML, we're going to build up an initial container that looks like this, where we have one, two in the top, and three, four in the bottom
2:30
So we're going to have a div with the class of container, which is going to be this black box, so we're going to have two of those divs
2:36
And then inside of these divs, we're going to have a bunch of different p tags, and these p tags are going to be one, two, three, or four
2:43
So we're going to give them a class of draggable so that we know that these are draggable elements
2:48
And then in order to make something draggable using JavaScript, there's an attribute called draggable, and we just need to set this to true
2:56
So if we set this draggable attribute to true, we're already partway there into making elements on our page draggable
3:02
Now let's just put the text of one in here, copy this down and have another one for two
3:07
And then we're going to copy these into our bottom container so that we have a three and a four, and we can save that
3:14
And if we just right click this and open with live server, which is an extension for Visual Studio code you can download, you can see we have one, two, three and four
3:22
So now in order to style these properly, let's just open up our style sheet and do a few really minor styles
3:28
So the first thing I want to do is select our body and remove the margin so that we can get our black container pushed right up against the side of our screen
3:36
And then we're going to style our container to give it that black style look
3:40
So we're going to say a background color here, so background color
3:44
We're going to make this background color 333. It's just going to give us this nice grayish color as you can see over here
3:51
And then we want to give it a little bit of padding, so we'll say 1rem, and we'll give it a little margin on the top of 1rem just to space these black containers out from one another
4:00
And now we can select our different draggable elements. And inside of our draggable elements, we're going to give them a little padding on their own, a background color, which is going to be white
4:11
And if we save, you can already see that starting to look good. Let's just give them a little bit of a border
4:16
We'll say 1px solid black just to give them a little bit of appearance like that
4:21
And then lastly, to get to this nice little movable cursor, what we can do is just change our cursor over here to be move
4:28
And now when we hover over any of these, we get that nice little movable looking cursor
4:32
And as you can see already, we can drag these elements by just clicking on them, and that's because of that draggable property
4:37
But it doesn't matter where we drag them, nothing's going to happen. They're not actually going to move, so we're going to need JavaScript to set that up
4:44
So now inside of our script.js, let's actually just select the different elements we're going to need
4:49
The first thing that we're going to need is to get the different elements we can drag, all of these different p tags
4:54
So we can just get an element called draggables, which is going to be equal to document.querySelector of all
5:01
And we know that these all have a class of draggable, so we can just use that draggable class
5:06
Next, we're going to need to get our different containers, because the containers are where we're able to drop our different elements
5:12
So when you're doing drag and drop with HTML and JavaScript, you need to know what your draggable elements are, in our case, these different p tags
5:20
And you also need to know where you can drop these elements, which are these black rectangles in our scenario
5:25
So we need to get those containers. So we can just do another document.querySelector all
5:31
And this one we're going to get for that .container class, which we already know is going to be these black containers
5:37
So now once we have those, we can set up all of our event listeners
5:41
So let's do our draggable event listeners first, we can say draggables.forEach, so that we can loop through each of our draggables
5:50
And when we loop through each one of these, we want to add some event listeners. So we can say draggable.addEventListener
5:56
And the first event listener that we want to listen to is what happens as soon as we start dragging our element
6:02
And this is going to be the drag start event. And this gets fired as soon as we start dragging an element
6:07
And I can prove that by just saying console.log drag start. And now if we just inspect this real quick, drag this over so we can see our console
6:18
And if I start to drag this, you're going to see we get that drag start printed out every time we start to drag any of our different p tags
6:25
So this happens only when we first initially click and start our dragging
6:29
And it won't fire again at all until we start a new drag. So that's really useful
6:34
So with that drag start, what we can do is just apply a class to tell us that we are currently dragging this element
6:40
That way we can get this nice little opaque style where we have this element. It's a little bit different color than the rest of the elements
6:46
So in order to do that, let's just say our draggable, all we want to do is we want to come in here, get our class list
6:53
We want to add a class and we want to add a class called dragging
6:57
And then in our styles, we can select any draggable that has that dragging class
7:02
And we can just change the opacity down to 0.5. And now when we start our drag, you can see that that opacity changes to 0.5
7:10
But when we release our drag, it still stays at 0.5. So we need to set up another event listener on our draggable
7:17
And this event listener is going to be when our drag stops. So we can just come in here and say drag end, which is the same thing as drag start
7:24
But it calls as soon as we let go of the element. And inside of here, we want to just remove that class list
7:30
So we can say class list dot remove that dragging class. And now when we hover this, you see it gets that nice opaque class
7:37
And when we release it, you can see that class goes away. And that's because these drag start and drag end events
7:43
So now we have all of that nice stuff for dragging our element around, changing the class
7:48
So it changes its opacity. But what we need to do next is actually be able to move this element around inside the container
7:54
as well as drop it in other containers. So we need to loop through our containers in order to determine how our dragging works for when we're dropping our element
8:02
as well as how when we're moving our element around. So we can just say containers dot for each container
8:09
Whoops. There we go. And inside of here, we need to listen to an event called drag over
8:14
So we can say container dot add event listener. And this one is drag over
8:21
And then we can just run this function. And just to show you what's happening, let's just console dot log inside of here, drag over
8:28
And again, we inspect our page, bring over that console, we can see that as soon as we start dragging an element over our container
8:35
it's printing out drag over all the time, just repeatedly printing that over
8:39
And whenever we go outside of our container, you see that drag over stops. But anytime we're in a container, we get that drag over function being ran
8:46
So this function is going to be really useful since we can actually determine which element we're over top of
8:51
as well as if we're not over an element at all. And it's going to constantly run every single time we move our mouse
8:57
So in order to actually take advantage of that, what we need to do in here is as you can see over here
9:02
we're determining what position our element is inside of the container based on our mouse
9:06
And we're also determining which element is inside of. So if it's in the second container, or this first container
9:12
so we need to do all of that logic inside of this drag over function
9:16
The easy way to do this for now is to ignore all of this sorting, and just determine if we're inside container one or inside container two
9:24
So in order to do that, what we want to do in here is get our dragging element
9:28
essentially get the element we're currently dragging. And that's pretty easy to do. We can just say const draggable, which is going to be our current draggable
9:35
is equal to document.querySelector. And we know that only one element will have the dragging class at a given time
9:43
since it's the element we're currently dragging. So that'll be this draggable element here, whichever one we're currently dragging
9:49
And then all we want to do is append it to whatever container we're inside of
9:53
So we can say container.appendChild, and we can just pass in our draggable
9:58
Now, when we hover this, you see if we're inside of container one, it's going to add it to the bottom of container one
10:03
And if we go down to container two, it's going to add it to the bottom of container two
10:07
And when we release, you can see it stays inside that container. Same thing with up here and down here
10:12
But one interesting thing you notice is that our cursor has the do not allow symbol
10:16
You can see that kind of circle with the line through it. In order to remove that, what we need to do is inside of our event
10:22
we actually need to take our event object, and we need to just call preventDefault
10:26
Because by default, dropping inside of an element is disabled. So in order to enable dropping, we need to say e.preventDefault
10:34
And this is going to change our cursor to have a cursor that says we are able to drop inside of here
10:39
As you can see, when we're outside, we get the do not allow cursor. And when we're inside, we get this drop is allowed cursor
10:45
So now with that taken care of, our next step is to determine the order of our elements
10:49
based on where our mouse position is. Instead of just determining what container we're in
10:54
we need to figure out which element we're actually placing this in between, whether it's in between three and four, above three, below four, inside this container, and so on
11:02
So let's just create a simple function. And inside of this function, we're going to call it getDragAfterElement
11:09
And this is going to take in our container as well as the Y position of our mouse
11:14
So what this function is going to do, it's going to determine our mouse position when we're dragging our element
11:19
and it's going to return whichever element our mouse position is directly after
11:23
So in this case, when we're dragging our one, you can see our mouse is right above number four
11:27
So this getDragAfter would return this fourth element. So we know to insert above number four
11:33
If our mouse was up here, it would return number three, since we're closest to number three and right above it
11:38
And if we are for some reason below all of our elements, it would return nothing
11:42
because it just knows we need to append to the end of our container
11:46
So let's actually write out this function. And the first thing we need to do is determine all of the elements inside of the container
11:52
that we are currently hovering over, which is this container we pass in
11:56
So we can just come in here and say container.querySelectorAll, and we want to get all of our draggable elements so we can determine where we're going to place this in our list
12:05
But one problem with this is when we're hovering this one over top of this
12:09
you can see we have elements three and four, but also the element we're dragging, number one
12:13
is also in this container. So we want to ignore everything that we're currently dragging
12:18
So to do that, we can just use the notSelector and just say .dragging
12:23
which is the current element we're dragging. So this will get every draggable that we're not currently dragging
12:28
So essentially, if we're dragging element one, it'll only get element three and four
12:32
and not the one that we're dragging. And now that we have this information, we want to convert this to an array
12:38
so that we can actually do array operations on it because querySelectorAll doesn't return an array
12:43
So in order to do that, we can just use the spread operator to spread this out into a new array
12:49
And with this, we can just set this to a variable, which we're just going to call draggableElements
12:55
just like that. So this is all of our draggable elements inside of our container
12:59
And now we want to call a function on draggable elements called reduce
13:03
And what reduce is going to do is it's essentially going to loop through this element list of draggable elements
13:08
and we're going to determine which single element is the one that is directly after our mouse cursor
13:14
based on this y position that we pass in. And we can get this y position from our event
13:19
So actually, let's just say const after element up here, which is going to be the return of this getDragAfterElement
13:26
We're going to set that equal to calling that function, passing in our current container
13:30
and then the e.client y. This is just going to be the y position of our mouse on the screen
13:37
Now this reduce function, if you're not familiar with it, I have a video covering it
13:41
which you can find in the cards in the description down below. But essentially, this is going to take a function, and then it is also going to take a second parameter
13:49
which is essentially the initial value of our reduce. So this function is going to take in two parameters
13:55
The first parameter is going to be the value we're reducing down to
13:59
So we're going to call this closest because this is going to be the element that we're closest to
14:03
that is directly after our mouse cursor. And then the next parameter is going to be each one of these draggable elements
14:10
We'll just call this child because each one of them are a child of the container we're inside of
14:14
So for each one of our draggable elements, this function is going to be called
14:18
and whatever this function returns is what is this closest section inside of this function
14:23
And then this second parameter of reduce is just the initial value of closest
14:27
So what we want to figure out inside of this function is which element is directly after our mouse cursor
14:32
So to keep track of that, we need to figure out the offset between our cursor
14:36
and the element that comes directly after it, as well as the element that's directly after it
14:41
So initially, we just want to have a value which has an offset
14:45
which is going to be a really, really large number. We want it to be essentially infinitely far away from our mouse cursor
14:50
So we'll say number.infinite, and we can do positive infinity here. So number.positiveinfinity, essentially it's going to be the largest number possible
14:59
so every single number is going to be smaller than that. And this is just so that initially, we have a number that is way too large
15:06
and every single other element in the list is going to be closer than this number.positiveinfinity
15:12
Now inside of this function, we need to determine the actual position of the elements on our screen
15:17
in relation to our mouse. So we can get what's called the bounding box by just calling here
15:23
child.getBoundingClientRect, which is a function, and this is going to give us a rectangle for our box
15:32
And let's just log this out real quick, so we can say console.logBox
15:36
just to see exactly what's going on inside of this function. And if we come over here, inspect our page, go to the console
15:43
and we drag one of these elements, you'll notice nothing's actually being logged out
15:47
which means we probably have something wrong in our code. So if we go over here and check our code, we can just see we got draggable elements
15:53
getting that from our query selector, and you'll notice I forgot to put a period here
15:57
for our class selector for draggable. So when I add that in, once again, inspect our page, go over to our console
16:03
and if we drag this, you now see we're getting a bunch of things printed out. These are the rectangles of our children
16:08
We have the x position, the y position, as well as width, height, and then the top, right, bottom, and left-hand position
16:14
So right and left are going to be x, and top and bottom are going to be in y
16:18
So for our use case, we essentially want to measure from the middle of our box
16:22
So to do that, we can just take the top, and we can subtract out half of the height of our box
16:27
and that's going to give us the dead center of our box. So let's start doing that now
16:31
So we have our box. The next thing we want to do is get the offset
16:35
This is essentially going to be the distance between the center of our box and our actual mouse cursor
16:40
So we can say that our offset here is going to be equal to our y position
16:44
which is our mouse y position, and we want to subtract our box.top
16:48
So this is the top of our box. And then since we want the middle of our box, we again need to subtract out our box.height
16:55
and we need to divide that by 2. So half the height of our box will put us in the dead center of each one of our boxes
17:01
And now if we print out this offset, and then just come over here
17:05
we'll inspect our page, make sure that we go to our console
17:09
and then inside of here, if we drag number 2, you can see that when we're above our elements, we get negative numbers
17:15
When we're below an element, we get a positive number, which is why we have one positive and one negative number
17:20
And when we're below all of our elements, you can see every number is positive
17:24
So essentially, when we're below an element, our numbers are positive. When we're above an element, our numbers are negative
17:29
So we only care about offsets that are negative because that means that we're currently hovering above that element
17:35
So let's close out of this, and we know for a fact we only want to be concerned with offsets that are less than 0
17:41
Essentially, we're above the element that we're hovering over. Because if the offset is positive, then that means that we're no longer
17:47
hovering over that element, and we are now below that element. So now we know we only want offsets that are less than 0
17:54
but we also want the offset that is the closest to 0. Because essentially, if we're as close as possible to 0
18:00
then that means we are barely having our cursor above that element. So if we were, for example, hovering our cursor right here
18:06
we're barely above element 3, so we know that this is going to be close to 0. And since we're actually basing this on half right in the dead center here
18:12
we know if we hover here right above the center of element 3, we know that this is going to be nearly 0
18:18
So we always want to find the offset that is less than 0, but as close to 0 as possible
18:23
So in order to determine which element is our new closest element, we can say if our offset is less than 0
18:30
and our offset is greater than our closest.offset, which in our case down here is infinity to start with
18:38
so if our offset is closer than our closest offset, essentially it's closer to 0 than whatever offset we currently have
18:45
then we know that this is going to be our new closest element. So we can just come in here and we can return an object
18:51
which is going to have an offset, which is going to be our current offset
18:55
and it's also going to have an element, which is going to be our child
19:00
which is just the current element that we're iterating through. And one thing to know here is since we always are checking for our offset
19:06
being greater than our closest offset, and our initial closest offset is positive infinity
19:11
we need this to be negative infinity, because we always want to make sure that this first initial offset
19:17
is always going to be the smallest number, so that way any offset is always initially greater than our default offset
19:24
So now we can have an else statement instead of here. So essentially else, if our offset is either greater than 0
19:29
or it's not larger than the closest offset that we previously had
19:34
we just want to return our closest that we have for now. We can close off that if just like that
19:40
and let's make sure that we return this here. And now we can just come up here
19:44
do a console.log of our after element, and make sure we return whatever we're reducing
19:51
so essentially return this entire reduce function, and we should see this after element being whatever our mouse cursor is directly above
19:58
But to make sure we're only getting the element here, and not the offset and the element, as you can see here
20:03
we're just going to return the element that we get from our reduce function down here
20:08
So now if we save this, inspect our elements over here, and go to our console
20:13
you can see that when we hover this element, for example, when we're above our 2
20:18
you can see that we're getting our draggable being returned for 2, as you can see over here
20:22
And you're also seeing this offset being printed. We just need to remove this log for that offset
20:27
Now we can go back and inspect this, go to our console, and let's do another drag here
20:32
We can see if we drag our 3, when it's above 4, we get 4 being printed as our number
20:37
As you can see, we have 4 right here. And if we drag this up here
20:41
above our 1, we get 1 being printed. Above 2, we get 2 being printed
20:45
And if we drag it above nothing, we get undefined being printed. So that way we can determine whether we're above something
20:51
for example, 1 or 2, or if we're above nothing, and we know we need to append to the end of our list
20:56
So now what we can do is we can use that inside of this function here
21:01
to determine where we're going to put our element. So we can say if our after element equals null
21:07
so for example, we're not above anything, then we can just do what we did before
21:11
We can append our child to the end of the list, and we can just move our draggable up to here
21:16
So now this will currently work if our after element is not anything
21:20
So if we drag 1 and we're not above anything, it'll come to the bottom of our list
21:24
And as you can see here, once we go below everything, it goes to the bottom of our list
21:28
So that is working. The next thing we have to do is to actually determine which element we're inserting before
21:33
So to do that, we can just put in an else here, and we can say container.insertBefore
21:40
And here we're going to pass in the new child, which in our case is our draggable
21:44
and then whichever element we're inserting before, which is our after element
21:48
And now we can save. And if we drag 1 below 2, it'll go below 2
21:53
Above 2, it'll go above 2. Come down here and above 3, it'll be above 3
21:57
Same thing with 4. Same thing if it's below. And when we let go, it'll stay in that position
22:02
So now we can move our elements around freely and put them wherever we want inside of our containers
22:07
And that's all there is to creating this simple drag-and-drop sortable list
22:11
If you enjoyed this video, make sure to check out my other videos linked over here
22:15
and subscribe to the channel for more videos just like this one. Thank you very much for watching and have a good day