The first three styles (position, transform, and pointerEvents) can be written directly with CSS. If you can write something in CSS, you should, because it reduces JavaScript complexity.
In this case, let’s say we set a custom attribute, data-dragging, to true when we’re dragging element. We can write the CSS like this:
When the user releases their mouse, we want to reset these three lines of JavaScript.
function up (event) {
// ...
target.style.position = 'static'
target.style.transform = ''
target.style.pointerEvents = 'auto'
}
But since we changed these properties in CSS, we can set data-dragging to false to reset these properties.
function up (event) {
// ...
// Remove these
target.style.position = 'static'
target.style.transform = ''
target.style.pointerEvents = 'auto'
// Add this
target.dataset.dragging = 'false'
}
Removing a redundant line
We used document.body.append(target) directly after target.remove(). In this case, remove is redundant because appendremoves the original element anyway.
If you drag the element outside the screen, you’ll get some errors.
Why? elementFromPoint returns null if the specified point is outside the document. null is a primitive and it does not have a closest method. You’ll get an error if you try to call null.closest().
This is easy to fix. We’ll return nothing if hitTest is null.
const getDropzone = element => {
const { top, left } = element.getBoundingClientRect()
const hitTest = document.elementFromPoint(left, top)
if (!hitTest) return
return hitTest.closest('[data-dropzone]')
}
Merging previewExists and previewPos
In move, we used find to check whether the preview element exists in the dropzone. Later we used findIndex to find the index of the preview element.
function move (event) {
const previewExists = [...dropzone.children].find(element => {
return element === preview
})
if (!previewExists) {
dropzone.append(preview)
}
// ...
const previewPos = [...dropzone.children].findIndex(element => {
return element === preview
})
}
find and findIndex do similar things. We can use findIndex to handle them both.
First, we use findIndex to check whether the preview element exists in the dropzone. If the preview element does not exist, findIndex will return -1.
function move (event) {
const previewPos = [...dropzone.children].findIndex(element => {
return element === preview
})
if (previewPos === -1) {
// preview element does not exist
}
}
If the preview element does not exist, we append it into the dropzone.
function move (event) {
// ...
const previewPos = [...dropzone.children].findIndex(element => {
return element === preview
})
if (previewPos === -1) {
dropzone.append(preview)
}
// ...
}
And since we append the preview element in the dropzone, we know it’s going to be in the last position. We can assign this position back into previewPos.
function move (event) {
// ...
let previewPos = [...dropzone.children].findIndex(element => {
return element === preview
})
if (previewPos === -1) {
dropzone.append(preview)
previewPos = dropzone.children.length - 1
}
// ...
}
We can further simplify the findIndex part with a function. Let’s call it getCurrentPreviewPosition.
function getCurrentPreviewPosition (dropzone, preview) {
return [...dropzone.children].findIndex(element => {
return element === preview
})
}
Using: getCurrentPreviewPosition
function move (event) {
// ...
let previewPos = getCurrentPreviewPosition(dropzone, preview)
if (previewPos === -1) {
dropzone.append(preview)
previewPos = dropzone.children.length - 1
}
// ...
}
Getting the desired preview position
We did two things to get the desired position of the preview element:
We get possible preview positions
We check if the dragged element falls into any of these preview positions
function move (event) {
// ...
// Getting possible preview positions
const positions = [...dropzone.children].map(element => {
return element.getBoundingClientRect()
})
// Finding the desired preview's position
const position = positions.findIndex(pos => {
return (pos.left < left && left < pos.right) &&
(pos.top < top && top < pos.bottom)
})
// ...
}
We can put these lines of code into a function. Let’s call it getDesiredPreviewPosition.
function getDesiredPreviewPosition () {
// ...
})
We’ll copy-paste the code we have:
function getDesiredPreviewPosition () {
const positions = [...dropzone.children]
.map(element => element.getBoundingClientRect())
const position = positions.findIndex(pos => {
return (pos.left < left && left < pos.right) &&
(pos.top < top && top < pos.bottom)
})
}
And we return the position.
function getDesiredPreviewPosition () {
const positions = [...dropzone.children]
.map(element => element.getBoundingClientRect())
// Return the position
return positions.findIndex(pos => {
return (pos.left < left && left < pos.right) &&
(pos.top < top && top < pos.bottom)
})
}
getDesiredPreviewPosition needs three variables to work:
The dropzone.
The left of the target’s bounding box.
The top of the target’s bounding box.
We can pass the dropzone directly into getDesiredPreviewPosition.
function getDesiredPreviewPosition (dropzone) {
// ...
}
Next, we can get left and top by passing in the dragged element. Once we have the element, we can use element.getBoundingClientRect to get the left and top position.
function getDesiredPreviewPosition (dropzone, element) {
const { left, top } = element.getBoundingClientRect()
const positions = [...dropzone.children]
.map(element => element.getBoundingClientRect())
return positions.findIndex(pos => {
return (pos.left < left && left < pos.right) &&
(pos.top < top && top < pos.bottom)
})
}
Using getDesiredPreviewPosition:
function move (event) {
// ...
const position = getDesiredPreviewPosition(dropzone, target)
// ...
}