This commit is contained in:
Ilya Kantor 2017-03-02 16:58:09 +03:00
parent 4c08f02317
commit a2fef883c2
52 changed files with 225 additions and 301 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -0,0 +1,51 @@
First we need to choose a method of positioning the ball.
We can't use `position:fixed` for it, because scrolling the page would move the ball from the field.
So we should use `position:absolute` and, to make the positioning really solid, make `field` itself positioned.
Then the ball will be positioned relatively to the field:
```css
#field {
width: 200px;
height: 150px;
position: relative;
}
#ball {
position: absolute;
left: 0; /* relative to the closest positioned ancestor (field) */
top: 0;
transition: 1s all; /* CSS animation for left/top makes the ball fly */
}
```
Next we need to assign the correct `ball.style.position.left/top`. They contain field-relative coordinates now.
Here's the picture:
![](move-ball-coords.png)
We have `event.clientX/clientY` -- window-relative coordinates of the click.
To get field-relative `left` coordinate of the click, we can substract the field left edge and the border width:
```js
let left = event.clientX - fieldInnerCoords.left - field.clientLeft;
```
Normally, `ball.style.position.left` means the "left edge of the element" (the ball). So if we assign that `left`, then the ball edge would be under the mouse cursor.
We need to move the ball half-width left and half-height up to make it center.
So the final `left` would be:
```js
let left = event.clientX - fieldInnerCoords.left - field.clientLeft - ball.offsetWidth/2;
```
The vertical coordinate is calculated using the same logic.
Please note that the ball width/height must be known at the time we access `ball.offsetWidth`. Should be specified in HTML or CSS.

View file

@ -0,0 +1,75 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#field {
width: 200px;
height: 150px;
border: 10px solid black;
background-color: #00FF00;
position: relative;
overflow: hidden;
cursor: pointer;
}
#ball {
position: absolute;
left: 0;
top: 0;
width: 40px;
height: 40px;
transition: all 1s;
}
</style>
</head>
<body style="height:2000px">
Click on a field to move the ball there.
<br>
<div id="field">
<img src="https://en.js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
<script>
field.onclick = function(event) {
// window-relative field coordinates
let fieldCoords = this.getBoundingClientRect();
// the ball has position:absolute, the field: position:relative
// so ball coordinates are relative to the field inner left-upper corner
let ballCoords = {
top: event.clientY - fieldInnerCoords.top - field.clientTop - ball.clientHeight / 2,
left: event.clientX - fieldInnerCoords.left - field.clientLeft - ball.clientWidth / 2
};
// prevent crossing the top field boundary
if (ballCoords.top < 0) ballCoords.top = 0;
// prevent crossing the left field boundary
if (ballCoords.left < 0) ballCoords.left = 0;
// // prevent crossing the right field boundary
if (ballCoords.left + ball.clientWidth > field.clientWidth) {
ballCoords.left = field.clientWidth - ball.clientWidth;
}
// prevent crossing the bottom field boundary
if (ballCoords.top + ball.clientHeight > field.clientHeight) {
ballCoords.top = field.clientHeight - ball.clientHeight;
}
ball.style.left = ballCoords.left + 'px';
ball.style.top = ballCoords.top + 'px';
}
</script>
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#field {
width: 200px;
height: 150px;
border: 10px solid black;
background-color: #00FF00;
overflow: hidden;
}
</style>
</head>
<body style="height:2000px">
Click on a field to move the ball there.
<br> The ball should never leave the field.
<div id="field">
<img src="https://en.js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
</body>
</html>

View file

@ -0,0 +1,21 @@
importance: 5
---
# Move the ball across the field
Move the ball across the field to a click. Like this:
[iframe src="solution" height="260" link]
Requirements:
- The ball center should come exactly under the pointer on click (if possible without crossing the field edge).
- CSS-animation is welcome.
- The ball must not cross field boundaries.
- When the page is scrolled, nothing should break.
Notes:
- The code should also work with different ball and field sizes, not be bound to any fixed values.
- Use properties `event.clientX/event.clientY` for click coordinates.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View file

@ -325,6 +325,50 @@ Try the code below. In most browsers only the second handler works, not the firs
```
````
## Event object
To properly handle an event we'd want to know more about what's happened. Not just a "click" or a "keypress", but what were the pointer coordinates? Which key was pressed? And so on.
When an event happens, the browser creates an *event object*, puts details into it and passes it as an argument to the handler.
Here's an example of getting mouse coordinates from the event object:
```html run
<input type="button" value="Click me" id="elem">
<script>
elem.onclick = function(*!*event*/!*) {
// show event type, element and coordinates of the click
alert(event.type + " на " + event.currentTarget);
alert(event.clientX + ":" + event.clientY);
};
</script>
```
Some properties of `event` object:
`event.type`
: Event type, here it's `"click"`.
`event.currentTarget`
: Element that handled the event. That's exactly the same as `this`, unless you bind `this` to something else, and then `event.currentTarget` becomes useful.
`event.clientX / event.clientY`
: Window-relative coordinates of the cursor, for mouse events.
There are more properties. They depend on the event type, so we'll study them later when come to different events in details.
````smart header="The event object is also accessible from HTML"
If we assign a handler in HTML, we can also use the `event` object, like this:
```html autorun height=60
<input type="button" onclick="*!*alert(event.type)*/!*" value="Event type">
```
That's possible because when the browser reads the attribute, it creates a handler like this: `function(event) { alert(event.type) }`. That is: its first argument is called `"event"`, and the body is taken from the attribute.
````
## Summary
There are 3 ways to assign event handlers:
@ -339,4 +383,6 @@ DOM properties are ok to use, but we can't assign more than one handler of the p
The last way is the most flexible, but it is also the longest to write. There are few events that only work with it, for instance `transtionend` and `DOMContentLoaded` (to be covered).
When a handler is called, it gets an event objects as the first argument. It contains details about what's happened. We'll see more of them later.
As of now we're just starting to work with events. More details in the next chapters.

View file

@ -1,36 +0,0 @@
# Мяч под курсор мыши
Основная сложность первого этапа -- сдвинуть мяч под курсор, т.к. координаты клика `e.clientX/Y` -- относительно окна, а мяч позиционирован абсолютно внутри поля, его координаты `left/top` нужно ставить относительно левого-верхнего внутреннего (внутри рамки!) угла поля.
Чтобы правильно вычислить координаты мяча, нужно получить координаты угла поля и вычесть их из `clientX/Y`:
```js
var field = document.getElementById('field');
var ball = document.getElementById('ball');
field.onclick = function(e) {
*!*
var fieldCoords = field.getBoundingClientRect();
var fieldInnerCoords = {
top: fieldCoords.top + field.clientTop,
left: fieldCoords.left + field.clientLeft
};
ball.style.left = e.clientX - fieldInnerCoords.left + 'px';
ball.style.top = e.clientY - fieldInnerCoords.top + 'px';
*/!*
};
```
Далее мяч нужно сдвинуть на половину его ширины и высоты `ball.clientWidth/clientHeight`, чтобы он оказался центром под курсором.
Здесь есть важный "подводный камень" -- размеры мяча в исходном документе не прописаны. Там просто стоит `<img>`. Но на момент выполнения JavaScript картинка, возможно, ещё не загрузилась, так что высота и ширина мяча будут неизвестны (а они необходимы для центрирования).
Нужно добавить `width/height` в тег `<img>` или задать размеры в CSS, тогда на момент выполнения JavaScript будет знать их и передвинет мяч правильно.
Код, который полностью центрирует мяч, вы найдете в полном решении:
[iframe border="1" src="solution" height="260" link edit]

View file

@ -1,92 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#field {
width: 200px;
height: 150px;
border: 10px groove black;
background-color: #00FF00;
position: relative;
overflow: hidden;
cursor: pointer;
}
#ball {
position: absolute;
left: 0;
top: 0;
width: 40px;
height: 40px;
-webkit-transition: all 1s;
-moz-transition: all 1s;
-o-transition: all 1s;
-ms-transition: all 1s;
transition: all 1s;
}
</style>
</head>
<body style="height:2000px">
Кликните на любое место поля, чтобы мяч перелетел туда.
<br>
<div id="field">
<img src="https://js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
<script>
var field = document.getElementById('field');
var ball = document.getElementById('ball');
field.onclick = function(event) {
// координаты поля относительно окна
var fieldCoords = this.getBoundingClientRect();
// координаты левого-верхнего внутреннего угла поля
var fieldInnerCoords = {
top: fieldCoords.top + field.clientTop,
left: fieldCoords.left + field.clientLeft
};
// разместить по клику,
// но сдвинув относительно поля (т.к. position:relative)
// и сдвинув на половину ширины/высоты
// (!) используются координаты относительно окна clientX/Y, как и в fieldCoords
var ballCoords = {
top: event.clientY - fieldInnerCoords.top - ball.clientHeight / 2,
left: event.clientX - fieldInnerCoords.left - ball.clientWidth / 2
};
// вылезает за верхнюю границу - разместить по ней
if (ballCoords.top < 0) ballCoords.top = 0;
// вылезает за левую границу - разместить по ней
if (ballCoords.left < 0) ballCoords.left = 0;
// вылезает за правую границу - разместить по ней
if (ballCoords.left + ball.clientWidth > field.clientWidth) {
ballCoords.left = field.clientWidth - ball.clientWidth;
}
// вылезает за нижнюю границу - разместить по ней
if (ballCoords.top + ball.clientHeight > field.clientHeight) {
ballCoords.top = field.clientHeight - ball.clientHeight;
}
ball.style.left = ballCoords.left + 'px';
ball.style.top = ballCoords.top + 'px';
}
</script>
</body>
</html>

View file

@ -1,40 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#field {
width: 200px;
height: 150px;
border: 10px groove black;
background-color: #00FF00;
position: relative;
overflow: hidden;
}
#ball {
position: absolute;
top: 50%;
left: 50%;
margin-left: -20px;
margin-top: -20px;
}
</style>
</head>
<body style="height:2000px">
Кликните на любое место поля, чтобы мяч перелетел туда.
<br> Мяч никогда не вылетит за границы поля.
<div id="field">
<img src="https://js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
</body>
</html>

View file

@ -1,22 +0,0 @@
importance: 5
---
# Передвигать мяч по полю
Сделайте так, что при клике по полю мяч перемещался на место клика.
[iframe src="solution" height="260" link]
Требования:
- Мяч после перелёта должен становиться центром ровно под курсор мыши, если это возможно без вылета за край поля.
- CSS-анимация не обязательна, но желательна.
- Мяч должен останавливаться у границ поля, ни в коем случае не вылетать за них.
- При прокрутке страницы с полем ничего не должно ломаться.
Замечания:
- Код не должен зависеть от конкретных размеров мяча и поля.
- Вам пригодятся свойства `event.clientX/event.clientY`

View file

@ -1,108 +0,0 @@
# Event object
To handle an event we'd like to know more about what's happened. For a mouse event we may need to know pointer coordinates, for a keyboard event -- which key was pressed and so on.
Each kind of event has special properties that browser writes into
Детали произошедшего браузер записывает в "объект события", который передаётся первым аргументом в обработчик.
[cut]
## Свойства объекта события
Пример ниже демонстрирует использование объекта события:
```html run
<input type="button" value="Нажми меня" id="elem">
<script>
elem.onclick = function(*!*event*/!*) {
// вывести тип события, элемент и координаты клика
alert(event.type + " на " + event.currentTarget);
alert(event.clientX + ":" + event.clientY);
}
</script>
```
Свойства объекта `event`:
`event.type`
: Тип события, в данном случае `click`
`event.currentTarget`
: Элемент, на котором сработал обработчик. Значение -- в точности такое же, как и у `this`, но бывают ситуации, когда обработчик является методом объекта и его `this` при помощи `bind` привязан к этому объекту, тогда мы можем использовать `event.currentTarget`.
`event.clientX / event.clientY`
: Координаты курсора в момент клика (относительно окна)
Есть также и ряд других свойств, в зависимости от событий, которые мы разберём в дальнейших главах, когда будем подробно знакомиться с событиями мыши, клавиатуры и так далее.
````smart header="Объект события доступен и в HTML"
При назначении обработчика в HTML, тоже можно использовать переменную `event`, это будет работать кросс-браузерно:
```html autorun height=60
<input type="button" onclick="*!*alert(event.type)*/!*" value="Тип события">
```
Это возможно потому, что когда браузер из атрибута создаёт функцию-обработчик, то она выглядит так: `function(event) { alert(event.type) }`. То есть, её первый аргумент называется `"event"`.
````
## Особенности IE8-
IE8- вместо передачи параметра обработчику создаёт глобальный объект `window.event`. Обработчик может обратиться к нему.
Работает это так:
```js
elem.onclick = function() {
// window.event - объект события
alert( window.event.clientX );
};
```
### Кроссбраузерное решение
Универсальное решение для получения объекта события:
```js
element.onclick = function(event) {
event = event || window.event; // (*)
// Теперь event - объект события во всех браузерах.
};
```
Строка `(*)`, в случае, если функция не получила `event` (IE8-), использует `window.event`.-событие `event`.
Можно написать и иначе, если мы сами не используем переменную `event` в замыкании:
```js
element.onclick = function(e) {
e = e || event;
// Теперь e - объект события во всех браузерах.
};
```
## Итого
- Объект события содержит ценную информацию о деталях события.
- Он передается первым аргументом `event` в обработчик для всех браузеров, кроме IE8-, в которых используется глобальная переменная `window.event`.
Кросс-браузерно для JavaScript-обработчика получаем объект события так:
```js
element.onclick = function(event) {
event = event || window.event;
// Теперь event - объект события во всех браузерах.
};
```
Еще вариант:
```js
element.onclick = function(e) {
e = e || event; // если нет другой внешней переменной event
...
};
```

View file

@ -1,6 +1,6 @@
# Всплытие и перехват
# Bubbling and capturing
Давайте сразу начнём с примера.
Let's start with an example.
Этот обработчик для `<div>` сработает, если вы кликните по вложенному тегу `<em>` или `<code>`:
@ -239,4 +239,3 @@ for (var i = 0; i < elems.length; i++) {
2. Разумная -- когда происходит событие, то разумно дать возможность первому сработать обработчику на самом элементе, поскольку он наиболее конкретен. Код, который поставил обработчик именно на этот элемент, знает максимум деталей о том, что это за элемент, чем он занимается.
Далее имеет смысл передать обработку события родителю -- он тоже понимает, что происходит, но уже менее детально, далее -- выше, и так далее, до самого объекта `document`, обработчик на котором реализовывает самую общую функциональность уровня документа.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

Binary file not shown.