Shape Drop Game- Drag and Drop Events
Open the project folder
This lesson continues from the previous lessons starter file.
The draggable attribute
As you would expect from just placing in regular HTML elements, when we try to drag our shapes, nothing happens just yet.
To make these shapes draggable, the first step is to add a draggable attribute to the div elements in the drag section:
<div class="drag-section">
  <div class="rectangle" draggable="true" ></div>
  <div class="square" draggable="true" ></div>
  <div class="pill" draggable="true" ></div>
  <div class="square2" draggable="true" ></div>
  <div class="oval" draggable="true" ></div>
  <div class="oval2" draggable="true" ></div>
  <div class="pill2" draggable="true" ></div>
  <div class="circle" draggable="true" ></div>
  <div class="rectangle2" draggable="true" ></div>
  <div class="rectangle3" draggable="true" ></div>
</div>
You will now be able to click on and drag these shapes in the browser. Although we can move them, we can’t really do much with them yet. This is because we need to set up what we want to do at each stage.
Listening for events
The HTML Drag and Drop API has events we can listen for. Let's look at these in the script.js file:
// sctipt.js
// get a list of all shapes with class of drop
const dropzones = document.querySelectorAll('.drop');
// listen for element drop
function handleDrop(e) {
  console.log('dropped');
}
// loop over all dropzones and call the handleDrop function when the drop event occurs
dropzones.forEach(function (element) {
  element.addEventListener('drop', handleDrop);
});
// listen for drag start
function handleDragStart(e) {
  console.log('drag start');
}
document.addEventListener('dragstart', handleDragStart);
// listen for end of drag event
function handleDragEnd(e) {
  console.log('drag end');
}
document.addEventListener('dragend', handleDragEnd);
If we try this out, you will notice in the console only the drag start and drag end events work. The reason why we cannot drop is because the default behaviour is to not allow things to be dropped onto a web page. Meaning we need to prevent this default on the drop zones.
The dragover event
We can do this by listening out for an event called dragover. This means we are dragging an element into a certain zone where we want it to be dropped:
// 2. Add a new function to prevent the default behaviour:
function allowDrop(e) {
    e.preventDefault();
}
dropzones.forEach(function (element) {
    element.addEventListener("drop", handleDrop);
  // 1. Inside the forEach loop add a second event listener:
    element.addEventListener("dragover", allowDrop);
});
The dropped console log will now show when dropping shapes into a drop zone. Now we know the events are working, we can begin to make things happen.
Checking shapes match
What we want to do is only allow a shape to be dropped into the correct hole. When the shape is dropped, we need a way of checking they are the same.
In the index.html, one way of doing this is with the classes we have:
<div class="drop-section">
  <div class="drop square"></div>
  <div class="drop rectangle"></div>
  <div class="drop circle"></div>
  <div class="drop square2"></div>
  <div class="drop oval2"></div>
  <div class="drop rectangle2"></div>
  <div class="drop rectangle3"></div>
  <div class="drop pill"></div>
  <div class="drop oval"></div>
  <div class="drop pill2"></div>
</div>
<div class="drag-section">
  <div class="rectangle" draggable="true"></div>
  <div class="square" draggable="true"></div>
  <div class="pill" draggable="true"></div>
  <div class="square2" draggable="true"></div>
  <div class="oval" draggable="true"></div>
  <div class="oval2" draggable="true"></div>
  <div class="pill2" draggable="true"></div>
  <div class="circle" draggable="true"></div>
  <div class="rectangle2" draggable="true"></div>
  <div class="rectangle3" draggable="true"></div>
</div>
Both of the drag and drop sections have shapes with the same class names. If they match, we allow the drop. This means in the drop function, we need to have access to both the dragged element, and the drop zone element to be able to compare.
We can get classes from the drop zone element from the event data:
function handleDrop(e) {
  console.log(e.target.classList);
}
But how do we access the element we are dragging? A way of doing this is to store it in a variable at the top of the page:
// script.js
let selected;
Then we can store the dragged element in this variable when we start to drag it. Modify the handleDragStart function to do this:
function handleDragStart(e) {
  selected = e.target;
}
Back to the handleDrop function, we now have access to both elements:
function handleDrop(e) {
  // the selected element only has 1 class name, so we can use the className property:
  console.log(selected.className);
  console.log(e.target.classList);
}
This will log the classes of both elements. Now we need to check if there is a matching class. And we can do this with the contains method. Modify the handleDrop function as follows:
function handleDrop(e) {
  if (e.target.classList.contains(selected.className)) {
    console.log('correct');
    return;
  }
  console.log('incorrect');
}
Try this out to see the console messages.
Handling a match
Next, we can also remove the dragged element if the user got it correct. Replace the console log with the remove() method:
function handleDrop(e) {
  if (e.target.classList.contains(selected.className)) {
    // remove method removed the element from the DOM
    selected.remove();
    return;
  }
  console.log('incorrect');
}
This removes the shape from the bottom section since we want it to appear to be placed in the top section. It would also be nice when the correct shape has been dropped, to also give the illusion that the shape has stayed inside the hole.
Looking at our shapes at the top and at the bottom sections, the only difference is a class of drop which the top ones have.
Looking at the CSS file:
.drop {
  background: none;
}
This class removes the background. To make it look like the shape has been dropped into place, we can remove this class. And classList has a remove method to help with this:
function handleDrop(e) {
    if (e.target.classList.contains(selected.className)) {
        e.target.classList.remove("drop"); // remove drop class if correct
        selected.remove();
        return;
    }
    console.log("incorrect");
}
Now when a shape is dropped into the correct top section, the solid colour will remain.
Fading dragged elements
Another nice touch is to fade the element when we are dragging. When we click on a shape to drag, the shape which is left in place in the bottom section is the same colour. To create a better visual effect, and make it clear which shape is being dragged, we can fade the color with the CSS opacity property.
We can set the CSS opacity for this is the handleDragStart function:
function handleDragStart(e) {
  selected = e.target;
  e.target.style.opacity = 0.5;
}
And in the handleDragEnd function, return it back to normal if the drag ends:
function handleDragEnd(e) {
  e.target.style.opacity = 1;
}
This is the drag and drop functionality now in place, and next, we will improve this game by adding the users score, handling the end of game, and restarting the game.