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.
|
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:
|
Let's use a symbol key for it:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let user = { name: "John" };
|
let user = { // belongs to another code
|
||||||
|
name: "John"
|
||||||
|
};
|
||||||
|
|
||||||
let id = Symbol("id");
|
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
|
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
|
```js run
|
||||||
let user = { name: "John" };
|
let user = { name: "John" };
|
||||||
|
|
||||||
// our script uses "id" property
|
// Our script uses "id" property
|
||||||
user.id = "ID Value";
|
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"
|
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
|
### 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.
|
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
|
## Drag'n'Drop algorithm
|
||||||
|
|
||||||
The basic Drag'n'Drop algorithm looks like this:
|
The basic Drag'n'Drop algorithm looks like this:
|
||||||
|
|
||||||
1. Catch `mousedown` on a draggable element.
|
1. On `mousedown` - prepare the element for moving, if needed (maybe create a copy of it).
|
||||||
2. Prepare the element for moving (maybe create a copy of it or whatever).
|
2. Then on `mousemove` move it by changing `left/top` and `position:absolute`.
|
||||||
3. 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.
|
||||||
4. On `mouseup` (button release) -- 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:
|
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
|
// move it out of any current parents directly into body
|
||||||
// to make it positioned relative to the body
|
// to make it positioned relative to the body
|
||||||
document.body.append(ball);
|
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);
|
moveAt(event.pageX, event.pageY);
|
||||||
|
|
||||||
|
@ -65,7 +64,7 @@ Here's an example in action:
|
||||||
|
|
||||||
[iframe src="ball" height=230]
|
[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.
|
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.
|
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.
|
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';
|
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.
|
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:
|
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
|
```js
|
||||||
// onmousemove
|
// onmousemove
|
||||||
// ball has position:absoute
|
// у мяча ball стоит position:absoute
|
||||||
ball.style.left = event.pageX - *!*shiftX*/!* + 'px';
|
ball.style.left = event.pageX - *!*shiftX*/!* + 'px';
|
||||||
ball.style.top = event.pageY - *!*shiftY*/!* + 'px';
|
ball.style.top = event.pageY - *!*shiftY*/!* + 'px';
|
||||||
```
|
```
|
||||||
|
@ -177,25 +178,27 @@ In action (inside `<iframe>`):
|
||||||
[iframe src="ball3" height=230]
|
[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)
|
## 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.
|
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.
|
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.
|
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
|
```html run autorun height=60
|
||||||
<style>
|
<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).
|
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
|
```js
|
||||||
// in a mouse event handler
|
// 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);
|
let elemBelow = document.elementFromPoint(event.clientX, event.clientY);
|
||||||
ball.hidden = false;
|
|
||||||
// elemBelow is the element below the ball, may be droppable
|
// 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.
|
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:
|
An extended code of `onMouseMove` to find "droppable" elements:
|
||||||
|
|
||||||
```js
|
```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) {
|
function onMouseMove(event) {
|
||||||
moveAt(event.pageX, event.pageY);
|
moveAt(event.pageX, event.pageY);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue