minor
This commit is contained in:
parent
cdf6156088
commit
28cc5c82c0
2 changed files with 42 additions and 32 deletions
|
@ -74,15 +74,19 @@ alert(id.description); // id
|
|||
|
||||
Symbols allow us to create "hidden" properties of an object, that no other part of code can occasionally access or overwrite.
|
||||
|
||||
For instance, if we're working with `user` objects, that belong to a third-party code and don't have any `id` field. We'd like to add identifiers to them.
|
||||
For instance, if we're working with `user` objects, that belong to a third-party code. We'd like to add identifiers to them.
|
||||
|
||||
Let's use a symbol key for it:
|
||||
|
||||
```js run
|
||||
let user = { name: "John" };
|
||||
let user = { // belongs to another code
|
||||
name: "John"
|
||||
};
|
||||
|
||||
let id = Symbol("id");
|
||||
|
||||
user[id] = "ID Value";
|
||||
user[id] = 1;
|
||||
|
||||
alert( user[id] ); // we can access the data using the symbol as the key
|
||||
```
|
||||
|
||||
|
@ -108,13 +112,13 @@ There will be no conflict between our and their identifiers, because symbols are
|
|||
```js run
|
||||
let user = { name: "John" };
|
||||
|
||||
// our script uses "id" property
|
||||
user.id = "ID Value";
|
||||
// Our script uses "id" property
|
||||
user.id = "Our id value";
|
||||
|
||||
// ...if later another script the uses "id" for its purposes...
|
||||
// ...Another script also wants "id" for its purposes...
|
||||
|
||||
user.id = "Their id value"
|
||||
// boom! overwritten! it did not mean to harm the colleague, but did it!
|
||||
// Boom! overwritten by another script!
|
||||
```
|
||||
|
||||
### Symbols in a literal
|
||||
|
|
|
@ -6,20 +6,19 @@ In the modern HTML standard there's a [section about Drag and Drop](https://html
|
|||
|
||||
They are interesting because they allow to solve simple tasks easily, and also allow to handle drag'n'drop of "external" files into the browser. So we can take a file in the OS file-manager and drop it into the browser window. Then JavaScript gains access to its contents.
|
||||
|
||||
But native Drag Events also have limitations. For instance, we can't limit dragging by a certain area. Also we can't make it "horizontal" or "vertical" only. There are other drag'n'drop tasks that can't be implemented using that API.
|
||||
But native Drag Events also have limitations. For instance, we can't limit dragging by a certain area. Also we can't make it "horizontal" or "vertical" only. There are other drag'n'drop tasks that can't be done using that API.
|
||||
|
||||
So here we'll see how to implement Drag'n'Drop using mouse events. Not that hard either.
|
||||
Here we'll see how to implement Drag'n'Drop using mouse events.
|
||||
|
||||
## Drag'n'Drop algorithm
|
||||
|
||||
The basic Drag'n'Drop algorithm looks like this:
|
||||
|
||||
1. Catch `mousedown` on a draggable element.
|
||||
2. Prepare the element for moving (maybe create a copy of it or whatever).
|
||||
3. Then on `mousemove` move it by changing `left/top` and `position:absolute`.
|
||||
4. On `mouseup` (button release) -- perform all actions related to a finished Drag'n'Drop.
|
||||
1. On `mousedown` - prepare the element for moving, if needed (maybe create a copy of it).
|
||||
2. Then on `mousemove` move it by changing `left/top` and `position:absolute`.
|
||||
3. On `mouseup` - perform all actions related to a finished Drag'n'Drop.
|
||||
|
||||
These are the basics. We can extend it, for instance, by highlighting droppable (available for the drop) elements when hovering over them.
|
||||
These are the basics. Later we can extend it, for instance, by highlighting droppable (available for the drop) elements when hovering over them.
|
||||
|
||||
Here's the algorithm for drag'n'drop of a ball:
|
||||
|
||||
|
@ -32,7 +31,7 @@ ball.onmousedown = function(event) { // (1) start the process
|
|||
// move it out of any current parents directly into body
|
||||
// to make it positioned relative to the body
|
||||
document.body.append(ball);
|
||||
// ...and put that absolutely positioned ball under the cursor
|
||||
// ...and put that absolutely positioned ball under the pointer
|
||||
|
||||
moveAt(event.pageX, event.pageY);
|
||||
|
||||
|
@ -65,7 +64,7 @@ Here's an example in action:
|
|||
|
||||
[iframe src="ball" height=230]
|
||||
|
||||
Try to drag'n'drop the mouse and you'll see the strange behavior.
|
||||
Try to drag'n'drop the mouse and you'll see such behavior.
|
||||
```
|
||||
|
||||
That's because the browser has its own Drag'n'Drop for images and some other elements that runs automatically and conflicts with ours.
|
||||
|
@ -88,7 +87,7 @@ In action:
|
|||
|
||||
Another important aspect -- we track `mousemove` on `document`, not on `ball`. From the first sight it may seem that the mouse is always over the ball, and we can put `mousemove` on it.
|
||||
|
||||
But as we remember, `mousemove` triggers often, but not for every pixel. So after swift move the cursor can jump from the ball somewhere in the middle of document (or even outside of the window).
|
||||
But as we remember, `mousemove` triggers often, but not for every pixel. So after swift move the pointer can jump from the ball somewhere in the middle of document (or even outside of the window).
|
||||
|
||||
So we should listen on `document` to catch it.
|
||||
|
||||
|
@ -101,15 +100,17 @@ ball.style.left = pageX - ball.offsetWidth / 2 + 'px';
|
|||
ball.style.top = pageY - ball.offsetHeight / 2 + 'px';
|
||||
```
|
||||
|
||||
Not bad, but there's a side-effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if do it at the edge, then the ball suddenly "jumps" to become centered.
|
||||
Not bad, but there's a side-effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer.
|
||||
|
||||
It would be better if we keep the initial shift of the element relative to the pointer.
|
||||
|
||||
For instance, if we start dragging by the edge of the ball, then the cursor should remain over the edge while dragging.
|
||||
For instance, if we start dragging by the edge of the ball, then the pointer should remain over the edge while dragging.
|
||||
|
||||

|
||||
|
||||
1. When a visitor presses the button (`mousedown`) -- we can remember the distance from the cursor to the left-upper corner of the ball in variables `shiftX/shiftY`. We should keep that distance while dragging.
|
||||
Let's update our algorithm:
|
||||
|
||||
1. When a visitor presses the button (`mousedown`) - remember the distance from the pointer to the left-upper corner of the ball in variables `shiftX/shiftY`. We'll keep that distance while dragging.
|
||||
|
||||
To get these shifts we can substract the coordinates:
|
||||
|
||||
|
@ -123,7 +124,7 @@ For instance, if we start dragging by the edge of the ball, then the cursor shou
|
|||
|
||||
```js
|
||||
// onmousemove
|
||||
// ball has position:absoute
|
||||
// у мяча ball стоит position:absoute
|
||||
ball.style.left = event.pageX - *!*shiftX*/!* + 'px';
|
||||
ball.style.top = event.pageY - *!*shiftY*/!* + 'px';
|
||||
```
|
||||
|
@ -177,25 +178,27 @@ In action (inside `<iframe>`):
|
|||
[iframe src="ball3" height=230]
|
||||
```
|
||||
|
||||
The difference is especially noticeable if we drag the ball by its right-bottom corner. In the previous example the ball "jumps" under the pointer. Now it fluently follows the cursor from the current position.
|
||||
The difference is especially noticeable if we drag the ball by its right-bottom corner. In the previous example the ball "jumps" under the pointer. Now it fluently follows the pointer from the current position.
|
||||
|
||||
## Potential drop targets (droppables)
|
||||
|
||||
In previous examples the ball could be dropped just "anywhere" to stay. In real-life we usually take one element and drop it onto another. For instance, a file into a folder, or a user into a trash can or whatever.
|
||||
In previous examples the ball could be dropped just "anywhere" to stay. In real-life we usually take one element and drop it onto another. For instance, a "file" into a "folder" or something else.
|
||||
|
||||
In other words, we take a "draggable" element and drop it onto "droppable" element.
|
||||
Speaking abstract, we take a "draggable" element and drop it onto "droppable" element.
|
||||
|
||||
We need to know where the element was dropped at the end of Drag'n'Drop -- to do the corresponding action, and, preferably, know the droppable we're dragging over, to highlight it.
|
||||
We need to know:
|
||||
- where the element was dropped at the end of Drag'n'Drop -- to do the corresponding action,
|
||||
- and, preferably, know the droppable we're dragging over, to highlight it.
|
||||
|
||||
The solution is kind-of interesting and just a little bit tricky, so let's cover it here.
|
||||
|
||||
What may be the first idea? Probably to set `mouseover/mouseup` handlers on potential droppables and detect when the mouse pointer appears over them. And then we know that we are dragging over/dropping on that element.
|
||||
What may be the first idea? Probably to set `mouseover/mouseup` handlers on potential droppables?
|
||||
|
||||
But that doesn't work.
|
||||
|
||||
The problem is that, while we're dragging, the draggable element is always above other elements. And mouse events only happen on the top element, not on those below it.
|
||||
|
||||
For instance, below are two `<div>` elements, red on top of blue. There's no way to catch an event on the blue one, because the red is on top:
|
||||
For instance, below are two `<div>` elements, red one on top of the blue one (fully covers). There's no way to catch an event on the blue one, because the red is on top:
|
||||
|
||||
```html run autorun height=60
|
||||
<style>
|
||||
|
@ -218,24 +221,27 @@ So, what to do?
|
|||
|
||||
There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window).
|
||||
|
||||
So in any of our mouse event handlers we can detect the potential droppable under the pointer like this:
|
||||
We can use it in any of our mouse event handlers to detect the potential droppable under the pointer, like this:
|
||||
|
||||
```js
|
||||
// in a mouse event handler
|
||||
ball.hidden = true; // (*)
|
||||
ball.hidden = true; // (*) hide the element that we drag
|
||||
|
||||
let elemBelow = document.elementFromPoint(event.clientX, event.clientY);
|
||||
ball.hidden = false;
|
||||
// elemBelow is the element below the ball, may be droppable
|
||||
|
||||
ball.hidden = false;
|
||||
```
|
||||
|
||||
Please note: we need to hide the ball before the call `(*)`. Otherwise we'll usually have a ball on these coordinates, as it's the top element under the pointer: `elemBelow=ball`.
|
||||
Please note: we need to hide the ball before the call `(*)`. Otherwise we'll usually have a ball on these coordinates, as it's the top element under the pointer: `elemBelow=ball`. So we hide it and immediately show again.
|
||||
|
||||
We can use that code to check what element we're "flying over" at any time. And handle the drop when it happens.
|
||||
|
||||
An extended code of `onMouseMove` to find "droppable" elements:
|
||||
|
||||
```js
|
||||
let currentDroppable = null; // potential droppable that we're flying over right now
|
||||
// potential droppable that we're flying over right now
|
||||
let currentDroppable = null;
|
||||
|
||||
function onMouseMove(event) {
|
||||
moveAt(event.pageX, event.pageY);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue