Hi! Let's add some visual feedback to the drag and drop operations.
So, in this example, you can see that when we drag elements, the look and feel is green with a dashed border around the dragged element.
And when I enter the drop zone, or drag over the drop zone, you can see different things: first, the drop zone is affected: it becomes green with a dashed border and also you get a "+" sign that appears, you see this? These are things you can configure.
The way we do that, is that we added to the drop zone a ‘dragenter’ event listener, a ‘dragleave’ event listener, a ‘dragover’ event listener. "enter" and "leave" are straightforward, and the ‘dragover’ listener is for stopping the propagation of the event.
I explained in the previous video that it's good for performance reasons.
On the ‘dragstart’ handler, when we start to drag the element, we will copy the data we need to get back once we dropped the elements, but we will also change some of the style (the CSS style) of the dragged element.
And to remove this style, we also listen to the ‘dragend’ event.
Let's have a look at the CSS.
In the CSS, we defined two classes: one is called "dragged" and the other "draggedOver".
I just discovered that they are the same… so there is certainly a way to simplify this example...
So, the "dragged" and the "draggedOver" classes just add a border (2 pixels dashed black) and change the background color to green.
Let's have a look at the JavaScript: let's look at the ‘dragenter’, the ‘dropleave’ and the ‘dropenter’ handlers.
What do we do when we enter the drop zone? When we enter the drop zone, we use the classList interface, that was also an HTML5 addition, and with this classList interface you can remove CSS classes or add CSS classes.
When we enter the drop zone, we add, to the element we entered, the "draggedOver" CSS class, that corresponds to a border and a background color.
So this is how, when I enter the drop zone, the div becomes green.
And when I leave (this is a ‘dragleave’ handler), that will remove the class.
And the same with the the dragstart handler.
When I start to drag an element, I add the class here.
And I exaggerate the opacity, the transparency, by setting a higher value for the opacity.
Because when you drag and drop elements, they are a bit lighter than normal, and here we accentuate this effect.
We can go further by using some properties called the "dropEffect" and the "allowEffect" properties.
So in that case, when we start moving here, we can change the cursor and the "+" sign that you see here, can be also customized by setting the dropEffect property in the ‘dropenter listener’.
Let's look at how we can do that: in the ‘dragstart’ listener, here... in the dragStartHandler, we create an image, we set it to a source, we give it a width, and we use the dataTransfert.setDragImage(dragIcon).
This is the name of the image I created (it's the HTML5 logo).
We can also proposes an offset relative to the cursor, the mouse cursor.
When I enter (in the dragEnterHandler), by setting the dropEffect property of the dataTransfer object, it produces a "+" sign.
You can use different values for the allowEffect and dropEffect, that I explained in the course for producing small icons similar to the ones you've got on Windows when making shortcuts or just moving a file from one folder to another.
Bye bye!
We can associate some CSS styling with the lifecycle of a drag and drop. This is easy to do as the drag and drop API provides many events we can listen to, and can be used on the draggable elements as well as in the drop zones:
dragstart: this event, which we discussed in a previous section, is used on draggable elements. We used it to get a value from the element that was dragged, and copied it onto the clipboard. It's a good time to add some visual feedback - for example, by adding a CSS class to the draggable object.
dragend: this event is launched when the drag has ended (on a drop or if the user releases the mouse button outside a drop zone). In both cases, it is a best practice to reset the style of the draggable object to default.
The next screenshot shows the use of CSS styles (green background + dashed border) triggered by the start of a drag operation. As soon as the drag ends and the element is dropped, we reset the style of the dragged object to its default. The full runnable online example is a bit further down the page (it includes, in addition, visual feedback on the drop zone):
Source code extract:
...
<style>
.dragged {
border: 2px dashed #000;
background-color: green;
}
</style>
<script>
function dragStartHandler(event) {
// Change CSS class for visual feedback
event.target.style.opacity = '0.4';
event.target.classList.add('dragged');
console.log('dragstart event, target: ' + event.target);
// Copy to the drag'n'drop clipboard the value of the data* attribute of the target,
// with a type "Fruits".
event.dataTransfer.setData("Fruit", event.target.dataset.value);
}
function dragEndHandler(event) {
console.log("drag end");
// Set draggable object to default style
event.target.style.opacity = '1';
event.target.classList.remove('dragged');
}
</script>
...
<ol ondragstart="dragStartHandler(event)" ondragend="dragEndHandler(event)" >
<li draggable="true" data-value="fruit-apple">Apples</li>
<li draggable="true" data-value="fruit-orange">Oranges</li>
<li draggable="true" data-value="fruit-pear">Pears</li>
</ol>
Notice at lines 12 and 24 the use of the classlist property that has been introduced with HTML5 in order to allow CSS class manipulation from JavaScript.
Other events can also be handled:
dragenter: usually we bind this event to the drop zone. The event occurs when a dragged object enters a drop zone. So, we could change the look of the drop zone.
dragleave: this event is also used in relation to the drop zone. When a dragged element leaves the drop zone (maybe the user changed his mind?), we must set the look of the drop zone back to normal.
dragover: this event is also generally bound to elements that correspond to a drop zone. A best practice here is to prevent the propagation of the event, and also to prevent the default behavior of the browser (i.e. if we drop an image, the default behavior is to display its full size in a new page, etc.)
drop: also on the drop zone. This is when we actually process the drop (get the value from the clipboard, etc). It's also necessary to reset the look of the drop zone to default.
The following example shows how to use these events in a droppable zone.
Try it in your browser below or directly at CodePen:
Complete source code (for clarity's sake, we put the CSS and JavaScript into a single HTML page):
<!DOCTYPE html>
<html>
<head>
<style>
div {
height: 150px;
width: 150px;
float: left;
border: 2px solid #666666;
background-color: #ccc;
margin-right: 5px;
border-radius: 10px;
box-shadow: inset 0 0 3px #000;
text-align: center;
cursor: move;
}
.dragged {
border: 2px dashed #000;
background-color: green;
}
.draggedOver {
border: 2px dashed #000;
background-color: green;
}
</style>
<script>
function dragStartHandler(event) {
// Change css class for visual feedback
event.target.style.opacity = '0.4';
event.target.classList.add('dragged');
console.log('dragstart event, target: ' + event.target.innerHTML);
// Copy in the drag'n'drop clipboard the value of the data* attribute of the target,
// with a type "Fruits".
event.dataTransfer.setData("Fruit", event.target.dataset.value);
}
function dragEndHandler(event) {
console.log("drag end");
event.target.style.opacity = '1';
event.target.classList.remove('dragged');
}
function dragLeaveHandler(event) {
console.log("drag leave");
event.target.classList.remove('draggedOver');
}
function dragEnterHandler(event) {
console.log("Drag enter");
event.target.classList.add('draggedOver');
}
function dragOverHandler(event) {
//console.log("Drag over a droppable zone");
event.preventDefault(); // Necessary. Allows us to drop.
}
function dropHandler(event) {
console.log('drop event, target: ' + event.target);
// reset the visual look of the drop zone to default
event.target.classList.remove('draggedOver');
var li = document.createElement('li');
// get the data from the drag'n'drop clipboard, with a type="Fruit"
var data = event.dataTransfer.getData("Fruit");
if (data == 'fruit-apple') {
li.textContent = 'Apples';
} else if (data == 'fruit-orange') {
li.textContent = 'Oranges';
} else if (data == 'fruit-pear') {
li.textContent = 'Pears';
} else {
li.textContent = 'Unknown Fruit';
}
// add the dropped data as a child of the list.
document.querySelector("#droppedFruits").appendChild(li);
}
</script>
</head>
<body>
<p>What fruits do you like? Try to drag an element!</p>
<ol ondragstart="dragStartHandler(event)" ondragend="dragEndHandler(event)" >
<li draggable="true" data-value="fruit-apple">Apples</li>
<li draggable="true" data-value="fruit-orange">Oranges</li>
<li draggable="true" data-value="fruit-pear">Pears</li>
</ol>
<div id="droppableZone" ondragenter="dragEnterHandler(event)" ondrop="dropHandler(event)"
ondragover="dragOverHandler(event)" ondragleave="dragLeaveHandler(event)">
Drop your favorite fruits below:
<ol id="droppedFruits"></ol>
</div>
<body>
<html>