This commit is contained in:
Ilya Kantor 2017-03-12 12:54:13 +03:00
parent fc84391bd2
commit 8360ebbe90
60 changed files with 920 additions and 1672 deletions

View file

@ -1,5 +1,5 @@
В решении этой задачи для переноса мы используем координаты относительно окна и `position:fixed`. Так проще.
To drag the element we can use `position:fixed`, it makes coordinates easier to manage. At the end we should switch it back to `position:absolute`.
А по окончании -- прибавляем прокрутку и делаем `position:absolute`, чтобы элемент был привязан к определённому месту в документе, а не в окне. Можно было и сразу `position:absolute` и оперировать в абсолютных координатах, но код был бы немного длиннее.
Then, when coordinates are at window top/bottom, we use `window.scrollTo` to scroll it.
Детали решения расписаны в комментариях в исходном коде.
More details in the code, in comments.

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="22cm" height="14cm" viewBox="0 0 1150 720" stroke="white" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<!--图纸区-->
<rect x="0" y="0" width="1150" height="720" fill="green" />
<!--球场框和中线-->
<path d="M 575,20 L 50,20 50,700 1100,700 1100,20 575,20 575,700 z" stroke="white" stroke-width="2" fill="green" />
<!--中线-->
<circle cx="575" cy="360" r="91.5" stroke="white" stroke-width="2" fill-opacity="0" />
<circle cx="575" cy="360" r="2" stroke="white" fill="white" />
<!--罚球点-->
<circle cx="160" cy="360" r="2" stroke="white" fill="white" />
<circle cx="990" cy="360" r="2" stroke="white" fill="white" />
<!--球门-->
<path d="M 50,324.4 L 40,324.4 40, 396.6 50 396.6 z" stroke="white" stroke-width="2" fill-opacity="0" />
<path d="M 1100,324.4 L 1110,324.4 1110,396.6 1100,396.6 z" stroke="white" stroke-width="2" fill-opacity="0" />
<!--球门区-->
<path d="M 50,269.4 L 105,269.4 105,451.6 50 451.6 z" stroke="white" stroke-width="2" fill-opacity="0" />
<path d="M 1100,269.4 L 1045,269.4 1045,451.6 1100,451.6 z" stroke="white" stroke-width="2" fill-opacity="0" />
<!--禁区 -->
<path d="M 50,159.4 L 215,159.4 215,561.6 50 561.6 z" stroke="white" stroke-width="2" fill-opacity="0" />
<path d="M 1100,159.4 L 935,159.4 935,561.6 1100,561.6 z" stroke="white" stroke-width="2" fill-opacity="0" />
<path d="M 215,286.875 A 91.5,91.5 0 0,1 215,433.125 z" stroke="white" stroke-width="2" fill="green" />
<path d="M 935,286.875 A 91.5,91.5 0 0,0 935,433.125 z" stroke="white" stroke-width="2" fill="green" />
<!--角球弧-->
<path d="M 50,30 A 10,10 0 0,0 60,20 L 50,20 z" stroke="white" stroke-width="2" fill-opacity="0" />
<path d="M 60,700 A 10,10 0 0,0 50,690 L 50,700 z" stroke="white" stroke-width="2" fill-opacity="0" />
<path d="M 1100,690 A 10,10 0 0,0 1090,700 L 1100,700 z" stroke="white" stroke-width="2" fill-opacity="0" />
<path d="M 1090,20 A 10,10 0 0,0 1100,30 L 1100,20 z" stroke="white" stroke-width="2" fill-opacity="0" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -8,13 +8,17 @@
<body>
<h2>Расставьте супергероев по полю.</h2>
<h2>Place superheroes around the soccer field.</h2>
<p>Супергерои и мяч -- это элементы с классом "draggable". Сделайте так, чтобы их можно было переносить.</p>
<p>Superheroes and the ball are elements with the class "draggable". Make them really draggable.</p>
<p>Важно: если супергероя подносят к низу или верху страницы, она должна автоматически прокручиваться. Если страница помещается на вашем экране целиком и не имеет вертикальной прокрутки -- сделайте окно браузера меньше, чтобы протестировать эту возможность.</p>
<p>Important: limit dragging by the window. If a draggable reaches window top or bottom, then the page should scroll to let us drag it further.</p>
<p>Да, и ещё: супергерои ни при каких условиях не должны попасть за край экрана.</p>
<p>If your screen is big enough to fit the whole document -- make the window smaller to get vertical scrolling, so that you could test it.</p>
<p>In this task it's enough to handle vertical scrolling. There's no horizontal scrolling usually, and it's handled the similar way if needed.</p>
<p>And one more thing: heroes may never leave the page. If they reach the edge of the document, no dragging outside of it.</p>
<div id="field">
@ -27,7 +31,7 @@
<div class="hero draggable" id="hero5"></div>
<div class="hero draggable" id="hero6"></div>
<img src="https://en.js.cx/drag-heroes/ball.png" class="draggable">
<img src="https://en.js.cx/clipart/ball.svg" class="draggable">
<div style="clear:both"></div>

View file

@ -1,16 +1,16 @@
html,
body {
html, body {
margin: 0;
padding: 0;
}
#field {
background: url(https://js.cx/drag-heroes/field.png);
background: url(field.svg);
width: 800px;
height: 600px;
height: 500px;
float: left;
}
/* герои и мяч (переносимые элементы) */
/* heroes and the ball (dragables) */
.hero {
background: url(https://js.cx/drag-heroes/heroes.png);

View file

@ -8,13 +8,17 @@
<body>
<h2>Расставьте супергероев по полю.</h2>
<h2>Place superheroes around the soccer field.</h2>
<p>Супергерои и мяч -- это элементы с классом "draggable". Сделайте так, чтобы их можно было переносить.</p>
<p>Superheroes and the ball are elements with the class "draggable". Make them really draggable.</p>
<p>Важно: если супергероя подносят к низу или верху страницы, она должна автоматически прокручиваться. Если страница помещается на вашем экране целиком и не имеет вертикальной прокрутки -- сделайте окно браузера меньше, чтобы протестировать эту возможность.</p>
<p>Important: limit dragging by the window. If a draggable reaches window top or bottom, then the page should scroll to let us drag it further.</p>
<p>Да, и ещё: супергерои ни при каких условиях не должны попасть за край экрана.</p>
<p>If your screen is big enough to fit the whole document -- make the window smaller to get vertical scrolling, so that you could test it.</p>
<p>In this task it's enough to handle vertical scrolling. There's no horizontal scrolling usually, and it's handled the similar way if needed.</p>
<p>And one more thing: heroes may never leave the page. If they reach the edge of the document, no dragging outside of it.</p>
<div id="field">
@ -27,7 +31,7 @@
<div class="hero draggable" id="hero5"></div>
<div class="hero draggable" id="hero6"></div>
<img src="https://js.cx/drag-heroes/ball.png" class="draggable">
<img src="https://en.js.cx/clipart/ball.svg" class="draggable">
<div style="clear:both"></div>

View file

@ -1,16 +1,16 @@
html,
body {
html, body {
margin: 0;
padding: 0;
}
#field {
background: url(https://js.cx/drag-heroes/field.png);
background: url(field.svg);
width: 800px;
height: 600px;
height: 500px;
float: left;
}
/* герои и мяч (переносимые элементы) */
/* heroes and the ball (dragables) */
.hero {
background: url(https://js.cx/drag-heroes/heroes.png);

View file

@ -1 +1 @@
// Ваш код
// Your code

View file

@ -2,20 +2,19 @@ importance: 5
---
# Расставить супергероев по полю
# Drag superheroes around the field
В этой задаче вы можете проверить своё понимание сразу нескольких аспектов Drag'n'Drop.
This task can help you to check understanding of several aspects of Drag'n'Drop and DOM.
Сделайте так, чтобы элементы с классом `draggable` можно было переносить мышкой. По окончании переноса элемент остаётся на том месте в документе, где его положили.
Make all elements with class `draggable` -- draggable. Like a ball in the chapter.
Требования к реализации:
Requirements:
- Должен быть 1 обработчик на `document`, использующий делегирование.
- Если элементы подносят к вертикальным краям окна -- оно должно прокручиваться вниз/вверх.
- Горизонтальной прокрутки в этой задаче не существует.
- Элемент при переносе, даже при резких движениях мышкой, не должен попасть вне окна.
- Use event delegation to track drag start: a single event handler on `document` for `mousedown`.
- If elements are dragged to top/bottom window edges -- the page scrolls up/down to allow further dragging.
- There is no horizontal scroll.
- Draggable elements should never leave the window, even after swift mouse moves.
Футбольное поле в этой задаче слишком большое, чтобы показывать его здесь, поэтому откройте его, кликнув по ссылке ниже. Там же и подробное описание задачи (осторожно, винни-пух и супергерои!).
The demo is too big to fit it here, so here's the link.
[demo src="solution"]

View file

@ -0,0 +1,6 @@
We should use two handlers: `document.onkeydown` and `document.onkeyup`.
The set `pressed` should keep currently pressed keys.
The first handler adds to it, while the second one removes from it. Every time on `keydown` we check if we have enough keys pressed, and run the function if it is so.

View file

@ -0,0 +1,47 @@
<!DOCTYPE HTML>
<html>
<body>
<p>Press "Q" and "W" together (can be in any language).</p>
<script>
function runOnKeys(func, ...codes) {
let pressed = new Set();
document.addEventListener('keydown', function(event) {
pressed.add(event.code);
for (let code of codes) { // are all keys in the set?
if (!pressed.has(code)) {
return;
}
}
// yes, they are
// during the alert, if the visitor releases the keys,
// JavaScript does not get the "keyup" event
// and pressed set will keep assuming that the key is pressed
// so, to evade "sticky" keys, we reset the status
// if the user wants to run the hotkey again - let him press all keys again
pressed.clear();
func();
});
document.addEventListener('keyup', function(event) {
pressed.delete(event.code);
});
}
runOnKeys(
() => alert("Hello!"),
"KeyQ",
"KeyW"
);
</script>
</body>
</html>

View file

@ -0,0 +1,19 @@
importance: 5
---
# Extended hotkeys
Create a function `runOnKeys(func, code1, code2, ... code_n)` that runs `func` on simultaneous pressing of keys with codes `code1`, `code2`, ..., `code_n`.
For instance, the code below shows `alert` when `"Q"` and `"W"` are pressed together (in any language, with or without CapsLock)
```js no-beautify
runOnKeys(
() => alert("Hello!"),
"KeyQ",
"KeyW"
);
```
[demo src="solution"]

View file

@ -0,0 +1,154 @@
# Keyboard: keydown and keyup
Let's study keyboard events now.
Before we start, please note that on modern devices there are other ways to "input something" then just a keyboard. For instance, people use speech recognition (tap microphone, say something, see it entered) or copy/paste with a mouse.
So if we want to track any input into an `<input>` field, then keyboard events is not enough. There's another event named `input` to handle changes of an `<input>` field, by any means. And it may be a better choice for such task. We'll cover it a bit later [todo link].
Keyboard events should be used when we want to handle keyboard actions (virtual keyboard usually also counts). For instance, to react on arrow keys `key:Up` and `key:Down` or hotkeys (including combinations of keys).
[cut]
## Teststand [#keyboard-test-stand]
```offline
To better understand keyboard events, you can use the [teststand](sandbox:keyboard-dump).
```
```online
To better understand keyboard events, you can use the teststand below.
Try different key combinations in the text field.
[codetabs src="keyboard-dump" height=480]
```
As you read on, if you want to try things out -- return to the stand and press keys.
## Keydown and keyup
The `keydown` events happens when a key is pressed, and then `keyup` -- when it's released.
### event.code and event.key
The `key` property of the event object allows to get the character, while the `code` property of the event object allows to get the "physical key code".
For instance, the same key `key:Z` can be pressed with or without `Shift`. That gives us two different characters: a lowercase and uppercase `z`, right?
The `event.key` is exactly the character, and it will be different. But `event.code` is the same:
| Key | `event.key` | `event.code` |
|--------------|-------------|--------------|
| `key:Z` |`z` (lowercase) |`KeyZ` |
| `key:Shift+Z`|`Z` (uppercase) |`KeyZ` |
```smart header="\"KeyZ\" and other key codes"
Key codes like `"KeyZ"` in the example above are described in the [UI Events code specification](https://www.w3.org/TR/uievents-code/).
For instance:
- Letter keys have codes `"Key<letter>"`
- Special keys are coded by their names: `"Enter"`, `"Backspace"`, `"Tab"` etc.
See [alphanumeric section](https://www.w3.org/TR/uievents-code/#key-alphanumeric-section) for more examples, or just try the [teststand](#keyboard-test-stand) above.
```
```warn header="Case matters: `\"KeyZ\"`, not `\"keyZ\"`"
Seems obvious, but people still make mistakes.
Please evade mistypes: it's `KeyZ`, not `keyZ`. The check like `event.code=="keyZ"` won't work: the first letter of `"Key"` must be uppercase.
```
If a user works with different languages, then switching to another language would make a totally different character instead of `"Z"`. That will become the value of `event.key`, while `event.code` is always the same: `"KeyZ"`.
What is a key does not give any character? For instance, `key:Shift` or `key:Tab` or others. For those keys `event.key` is approximately the same as `event.code`:
| Key | `event.key` | `event.code` |
|--------------|-------------|--------------|
| `key:Tab` |`Tab` |`Tab` |
| `key:F1` |`F1` |`F1` |
| `key:Backspace` |`Backspace` |`Backspace` |
| `key:Shift`|`Shift` |`ShiftRight` or `ShiftLeft` |
Please note that `event.code` specifies exactly which key is pressed. For instance, most keyboards have two `key:Shift` keys: on the left and on the right side. The `event.code` tells us exactly which one was pressed, and `event.key` is responsible for the "meaning" of the key: what it is (a "Shift").
Let's say, we want to handle a hotkey: `key:Ctrl+Z` (or `key:Cmd+Z` for Mac). That's an "undo" action in most text editors. We can set a listener on `keydown` and check which key is pressed -- to detect when we have the hotkey.
How do you think, should we check the value of `event.key` or `event.code`?
Please, pause and answer.
Made up your mind?
If you've got an understanding, then the answer is, of course, `event.code`, and we don't want `event.key` here. The value of `event.key` can change depending on the language or `CapsLock` enabled. The value of `event.code` is strictly bound to the key, so here we go:
```js run
document.addEventListener('keydown', function(event) {
if (event.code == 'KeyZ' && (event.ctrlKey || event.metaKey)) {
alert('Undo!')
}
});
```
## Auto-repeat
If a key is being pressed for a long enough time, it starts to repeat: the `keydown` triggers again and again, and then when it's released we finally get `keyup`. So it's kind of normal to have many `keydown` and a single `keyup`.
For all repeating keys the event object has `event.return=true`.
## Default actions
Default actions vary, as there are many possible things that may be initiated by keyboard:
For instance:
- A character appears on the screen (the most obvious one).
- A character is deleted (`key:Delete` key).
- The page is scrolled (`key:PageDown` key).
- The browser opens the "Save Page" dialog (`key:Ctrl+S`)
- ...and so on.
Preventing the default actions cancels most of them, with the sole exception of OS-based special keys.
For instance, on Windows `key:Alt+F4` closes the current browser window. And there's no way to stop it by preventing the default action in JavaScript.
For instance, we can't use keyboard to enter something into the `<input>` below:
```html run
<input *!*onkeydown="return false"*/!* placeholder="No keyboard input" type="text">
```
If we type-in something, it's just ignored. Please note that special combinations like `Ctrl+V` (paste) also don't work. But using a mouse and right-click + Paste still can add the text. So checking in `keydown` doesn't work as a 100% reliable filter.
## Legacy
In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `which` properties of the event object.
But there were so many incompatibilities between browsers that all of them are deprecated and removed from the standard. So the old code still works (and sometimes works around a bunch of quirks), but there's totally no need to use those any more.
There was time when this chapter included their detailed description. But as of now we can forget about those.
## Summary
All keys yield keyboard events -- be it symbol keys or special keys like `key:Shift` or `key:Ctrl`.
The only exception is `key:Fn` key that is sometimes present on laptop keyboards. There's no keyboard event for it, because it's often implemented on lower-level than OS.
Keyboard events:
- `keydown` -- on press the key (auto-repeats if the key is pressed for long),
- `keyup` -- on releasing the key.
Main keyboard event properties:
- `code` -- the "key code", specific to the physical location of the key on keyboard.
- `key` -- the character, for non-character keys a "code value", usually same as `code`.
In the past, keyboard events were sometimes used to track user input in form fields. That's not the case any more, because we have `input` and `change` events for that (to be covered later). They are better, because they work for all ways of input, including mouse or speech recognition.
We still should use them when we really want keyboard. For example, to react on hotkeys or special keys.

View file

@ -10,36 +10,29 @@
<form id="form" onsubmit="return false">
Предотвратить действие по умолчанию для:
Prevent default for:
<label>
<input type="checkbox" name="keydownStop" value="1"> keydown</label>&nbsp;&nbsp;&nbsp;
<label>
<input type="checkbox" name="keypressStop" value="1"> keypress</label>&nbsp;&nbsp;&nbsp;
<label>
<input type="checkbox" name="keyupStop" value="1"> keyup</label>
<p>
Игнорировать:
Ignore:
<label>
<input type="checkbox" name="keydownIgnore" value="1"> keydown</label>&nbsp;&nbsp;&nbsp;
<label>
<input type="checkbox" name="keypressIgnore" value="1"> keypress</label>&nbsp;&nbsp;&nbsp;
<label>
<input type="checkbox" name="keyupIgnore" value="1"> keyup</label>
</p>
<p>Сфокусируйтесь на поле и нажмите какую-нибудь клавишу.</p>
<p>Focus on the input field and press a key.</p>
<input type="text" placeholder="Клавиши нажимать тут" id="kinput">
<input type="text" placeholder="Press keys here" id="kinput">
<textarea id="area"></textarea>
<input type="button" value="Очистить" onclick="area.value = ''" />
</form>
<input type="button" value="Clear" onclick="area.value = ''" />
</form>
<script src="script.js"></script>
</body>
</html>

View file

@ -6,14 +6,14 @@ function handle(e) {
if (form.elements[e.type + 'Ignore'].checked) return;
var text = e.type +
' keyCode=' + e.keyCode +
' which=' + e.which +
' charCode=' + e.charCode +
' char=' + String.fromCharCode(e.keyCode || e.charCode) +
(e.shiftKey ? ' +shift' : '') +
(e.ctrlKey ? ' +ctrl' : '') +
(e.altKey ? ' +alt' : '') +
(e.metaKey ? ' +meta' : '') + "\n";
' key=' + e.key +
' code=' + e.code +
(e.shiftKey ? ' shiftKey' : '') +
(e.ctrlKey ? ' ctrlKey' : '') +
(e.altKey ? ' altKey' : '') +
(e.metaKey ? ' metaKey' : '') +
(e.repeat ? ' (repeat)' : '') +
"\n";
if (area.value && Date.now() - lastTime > 250) {
area.value += new Array(81).join('-') + '\n';

View file

@ -1,2 +0,0 @@
Решение использует кросс-браузерный код назначения обработчика `onwheel` на элемент и `style.transform`.

View file

@ -1,52 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p id="text" style="margin: 40px; width: 200px; height:60px; border: 1px solid red">
При прокрутке колёсика мыши над этим элементом, он будет масштабироваться.
</p>
<script>
function addOnWheel(elem, handler) {
if (elem.addEventListener) {
if ('onwheel' in document) {
// IE9+, FF17+
elem.addEventListener("wheel", handler);
} else if ('onmousewheel' in document) {
// устаревший вариант события
elem.addEventListener("mousewheel", handler);
} else {
// 3.5 <= Firefox < 17, более старое событие DOMMouseScroll пропустим
elem.addEventListener("MozMousePixelScroll", handler);
}
} else { // IE8-
text.attachEvent("onmousewheel", handler);
}
}
var scale = 1;
addOnWheel(text, function(e) {
var delta = e.deltaY || e.detail || e.wheelDelta;
// отмасштабируем при помощи CSS
if (delta > 0) scale += 0.05;
else scale -= 0.05;
text.style.transform = text.style.WebkitTransform = text.style.MsTransform = 'scale(' + scale + ')';
// отменим прокрутку
e.preventDefault();
});
</script>
</body>
</html>
</html>

View file

@ -1,18 +0,0 @@
importance: 5
---
# Масштабирование колёсиком мыши
Сделайте так, чтобы при прокрутке колёсиком мыши над элементом, он масштабировался.
Масштабирование обеспечивайте при помощи свойства CSS transform:
```js
// увеличение в 1.5 раза
elem.style.transform = elem.style.WebkitTransform = elem.style.MsTransform = 'scale(1.5)';
```
Результат в iframe:
[iframe link border="1" src="solution" height="160"]

View file

@ -1,14 +0,0 @@
document.onwheel = function(e) {
if (e.target.tagName != 'TEXTAREA') return;
var area = e.target;
var delta = e.deltaY || e.detail || e.wheelDelta;
if (delta < 0 && area.scrollTop == 0) {
e.preventDefault();
}
if (delta > 0 && area.scrollHeight - area.clientHeight - area.scrollTop <= 1) {
e.preventDefault();
}
};

View file

@ -1,41 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="fix-textarea-scroll.js"></script>
</head>
<body style="height: 1000px; position: relative">
<header>
<h2>Начало документа</h2>
</header>
<textarea cols="80" rows="10">
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
</textarea>
<footer style="position: absolute; bottom: 0">
Конец документа.
</footer>
</body>
</html>

View file

@ -1,40 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body style="height: 1000px; position: relative">
<header>
<h2>Начало документа</h2>
</header>
<textarea cols="80" rows="10">
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
прокрути меня
</textarea>
<footer style="position: absolute; bottom: 0">
Конец документа.
</footer>
</body>
</html>

View file

@ -1,28 +0,0 @@
importance: 4
---
# Прокрутка без влияния на страницу
В большинстве браузеров если в процессе прокрутки `textarea` колёсиком мышки (или жестами) мы достигаем границы элемента, то прокрутка продолжается уже на уровне страницы (в Firefox при этом будет небольшая задержка перед продолжением прокрутки).
Иными словами, если в примере ниже вы попробуете прокрутить `textarea` вниз, то когда прокрутка дойдёт до конца -- начнёт прокручиваться документ:
[iframe src="source" border="1" height=300]
То же самое происходит при прокрутке вверх.
В интерфейсах редактирования, когда большая `textarea` является основным элементом страницы, такое поведение может быть неудобно.
Для редактирования более оптимально, чтобы при прокрутке до конца `textarea` страница не "улетала" вверх и вниз.
Вот тот же документ, но с желаемым поведением `textarea`:
[iframe src="solution" border="1" height=300]
Задача:
- Создать скрипт, который при подключении к документу исправлял бы поведение всех `textarea`, чтобы при прокрутке они не трогали документ.
- Направление прокрутки -- только вверх или вниз.
- Редактор прокручивает только мышкой или жестами (на мобильных устройствах), прокрутку клавиатурой здесь рассматривать не нужно (хотя это и возможно).

View file

@ -1,78 +0,0 @@
# Мышь: колёсико, событие wheel
Колёсико мыши используется редко. Оно есть даже не у всех мышей. Поэтому существуют пользователи, которые в принципе не могут сгенерировать такое событие.
...Но, тем не менее, его использование может быть оправдано. Например, можно добавить дополнительные удобства для тех, у кого колёсико есть.
[cut]
## Отличия колёсика от прокрутки
Несмотря на то, что колёсико мыши обычно ассоциируется с прокруткой, это совсем разные вещи.
- При прокрутке срабатывает событие [onscroll](/onscroll) -- рассмотрим его в дальнейшем. Оно произойдёт *при любой прокрутке*, в том числе через клавиатуру, но *только на прокручиваемых элементах*. Например, элемент с `overflow:hidden` в принципе не может сгенерировать `onscroll`.
- А событие `wheel` является чисто "мышиным". Оно генерируется *над любым элементом* при передвижении колеса мыши. При этом не важно, прокручиваемый он или нет. В частности, `overflow:hidden` никак не препятствует обработке колеса мыши.
Кроме того, событие `onscroll` происходит *после* прокрутки, а `onwheel` -- *до* прокрутки, поэтому в нём можно отменить саму прокрутку (действие браузера).
## Зоопарк wheel в разных браузерах
Событие `wheel` появилось в [стандарте](http://www.w3.org/TR/DOM-Level-3-Events/#event-type-wheel) не так давно. Оно поддерживается Chrome 31+, IE9+, Firefox 17+.
До него браузеры обрабатывали прокрутку при помощи событий [mousewheel](http://msdn.microsoft.com/en-us/library/ie/ms536951.aspx) (все кроме Firefox) и [DOMMouseScroll](https://developer.mozilla.org/en-US/docs/DOM/DOM_event_reference/DOMMouseScroll), [MozMousePixelScroll](https://developer.mozilla.org/en-US/docs/DOM/DOM_event_reference/MozMousePixelScroll) (только Firefox).
Самые важные свойства современного события и его нестандартных аналогов:
`wheel`
: Свойство `deltaY` -- количество прокрученных пикселей по горизонтали и вертикали. Существуют также свойства `deltaX` и `deltaZ` для других направлений прокрутки.
`MozMousePixelScroll`
: Срабатывает, начиная с Firefox 3.5, только в Firefox. Даёт возможность отменить прокрутку и получить размер в пикселях через свойство `detail`, ось прокрутки в свойстве `axis`.
<dt>`mousewheel`</dd>
: Срабатывает в браузерах, которые ещё не реализовали `wheel`. В свойстве `wheelDelta` -- условный "размер прокрутки", обычно равен `120` для прокрутки вверх и `-120` -- вниз. Он не соответствует какому-либо конкретному количеству пикселей.
Чтобы кросс-браузерно отловить прокрутку и, при необходимости, отменить её, можно использовать все эти события.
Пример, включающий поддержку IE8-:
```js
if (elem.addEventListener) {
if ('onwheel' in document) {
// IE9+, FF17+, Ch31+
elem.addEventListener("wheel", onWheel);
} else if ('onmousewheel' in document) {
// устаревший вариант события
elem.addEventListener("mousewheel", onWheel);
} else {
// Firefox < 17
elem.addEventListener("MozMousePixelScroll", onWheel);
}
} else { // IE8-
elem.attachEvent("onmousewheel", onWheel);
}
function onWheel(e) {
e = e || window.event;
// wheelDelta не дает возможность узнать количество пикселей
var delta = e.deltaY || e.detail || e.wheelDelta;
var info = document.getElementById('delta');
info.innerHTML = +info.innerHTML + delta;
e.preventDefault ? e.preventDefault() : (e.returnValue = false);
}
```
В действии:
[iframe src="wheel" link edit]
```warn header="Ошибка в IE8"
В браузере IE8 (только версия 8) есть ошибка. При наличии обработчика `mousewheel` -- элемент не скроллится. Иначе говоря, действие браузера отменяется по умолчанию.
Это, конечно, не имеет значения, если элемент в принципе не прокручиваемый.
```

View file

@ -1,61 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
#container {
width: 200px;
height: 200px;
border: 1px solid black;
background: #0FF;
overflow: auto;
}
</style>
</head>
<body>
Прокрутка: <span id="delta">0</span>
<div id="container">
Прокрути надо мной.
</div>
<script>
var elem = document.getElementById('container');
if (elem.addEventListener) {
if ('onwheel' in document) {
// IE9+, FF17+
elem.addEventListener("wheel", onWheel);
} else if ('onmousewheel' in document) {
// устаревший вариант события
elem.addEventListener("mousewheel", onWheel);
} else {
// Firefox < 17
elem.addEventListener("MozMousePixelScroll", onWheel);
}
} else { // IE8-
elem.attachEvent("onmousewheel", onWheel);
}
// Это решение предусматривает поддержку IE8-
function onWheel(e) {
e = e || window.event;
// deltaY, detail содержат пиксели
// wheelDelta не дает возможность узнать количество пикселей
// onwheel || MozMousePixelScroll || onmousewheel
var delta = e.deltaY || e.detail || e.wheelDelta;
var info = document.getElementById('delta');
info.innerHTML = +info.innerHTML + delta;
e.preventDefault ? e.preventDefault() : (e.returnValue = false);
}
</script>
</body>
</html>

View file

@ -1,65 +0,0 @@
# Мышь: IE8-, исправление события
Ранее мы говорили о различных несовместимостях при работе с событиями для IE8-. Самая главная -- это, конечно, назначение событий при помощи `attachEvent/detachEvent` вместо `addEventListener/removeEventListener` и отсутствие фазы перехвата. Но и в самом объекте события есть различия.
Что касается событий мыши, различия в свойствах можно легко исправить при помощи функции `fixEvent`, которая описана в этой главе.
[cut]
```warn header="Только IE8-"
Эта глава и описанная далее функция `fixEvent` нужны только для поддержки IE8-.
Если IE8- для Вас неактуален, то пролистывайте дальше, это читать Вам не надо.
```
Функция `fixEvent` предназначена для запуска в начале обработчика, вот так:
```js
elem.onclick = function(event) {
*!*
// если IE8-, то получить объект события window.event и исправить его
event = event || fixEvent.call(this, window.event);
*/!*
...
}
```
Она добавит объекту события в IE8- следующие стандартные свойства:
- `target`
- `currentTarget` -- если обработчик назначен не через `attachEvent`.
- `relatedTarget` -- для `mouseover/mouseout` и `mouseenter/mouseleave`.
- `pageX/pageY`
- `which`
Код функции:
```js
function fixEvent(e) {
e.currentTarget = this;
e.target = e.srcElement;
if (e.type == 'mouseover' || e.type == 'mouseenter') e.relatedTarget = e.fromElement;
if (e.type == 'mouseout' || e.type == 'mouseleave') e.relatedTarget = e.toElement;
if (e.pageX == null && e.clientX != null) {
var html = document.documentElement;
var body = document.body;
e.pageX = e.clientX + (html.scrollLeft || body && body.scrollLeft || 0);
e.pageX -= html.clientLeft || 0;
e.pageY = e.clientY + (html.scrollTop || body && body.scrollTop || 0);
e.pageY -= html.clientTop || 0;
}
if (!e.which && e.button) {
e.which = e.button & 1 ? 1 : (e.button & 2 ? 3 : (e.button & 4 ? 2 : 0));
}
return e;
}
```
Эта функция может быть полезна, если не используются JavaScript-фреймворки, в которых есть свои средства сглаживания кросс-браузерных различий.

View file

@ -1,130 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body,
html {
margin: 0;
padding: 0;
}
.column-left {
float: left;
width: 30%;
background: #aef;
}
.column-right {
margin-left: 30%;
width: 70%;
background: tan;
overflow: auto;
/* расшириться вниз захватить float'ы */
}
.header {
line-height: 60px;
background: yellow;
}
.inner {
margin: 1em;
font-size: 130%;
}
#avatar {
float: left;
margin: 0 1em .5em 0;
border: 1px solid black;
text-align: center;
background: white;
}
.fixed {
position: fixed;
top: 0;
left: 0;
}
</style>
</head>
<body>
<div class="header">Шапка</div>
<div class="column-left">
<div class="inner">
<h3>Персонажи:</h3>
<ul>
<li>Винни-Пух</li>
<li>Ослик Иа</li>
<li>Сова</li>
<li>Кролик</li>
</ul>
</div>
</div>
<div class="column-right">
<div class="inner">
<h3>Винни-Пух</h3>
<div id="avatar">
<img src="https://js.cx/clipart/winnie-mult.jpg" width="200" height="150">
<div>Кадр из мультфильма</div>
</div>
<p>Ви́нни-Пу́х (англ. Winnie-the-Pooh) — плюшевый мишка, персонаж повестей и стихов Алана Александра Милна (цикл не имеет общего названия и обычно тоже называется «Винни-Пух», по первой книге). Один из самых известных героев детской литературы XX века.</p>
<p>В 1960-е—1970-е годы, благодаря пересказу Бориса Заходера «Винни-Пух и все-все-все», а затем и фильмам студии «Союзмультфильм», где мишку озвучивал Евгений Леонов, Винни-Пух стал очень популярен и в Советском Союзе.</p>
<p>Как и многие другие персонажи книги Милна, медвежонок Винни получил имя от одной из реальных игрушек Кристофера Робина (1920—1996), сына писателя. В свою очередь, плюшевый мишка Винни-Пух был назван по имени медведицы по кличке Виннипег (Винни),
содержавшейся в 1920-х в Лондонском зоопарке.</p>
<p>Медведица Виннипег (американский чёрный медведь) попала в Великобританию как живой талисман (маскот) Канадского армейского ветеринарного корпуса из Канады, а именно из окрестностей города Виннипега. Она оказалась в кавалерийском полку «Форт Гарри
Хорс» 24 августа 1914 года ещё будучи медвежонком (её купил у канадского охотника-траппера за двадцать долларов 27-летний полковой ветеринар лейтенант Гарри Колборн, заботившийся о ней и в дальнейшем). Уже в октябре того же года медвежонок был
привезён вместе с войсками в Британию, а так как полк должен был быть в ходе Первой мировой войны переправлен во Францию, то в декабре было принято решение оставить зверя до конца войны в Лондонском зоопарке. Медведица полюбилась лондонцам, и
военные не стали возражать против того, чтобы не забирать её из зоопарка и после войны[1]. До конца дней (она умерла 12 мая 1934 года) медведица находилась на довольствии ветеринарного корпуса, о чём в 1919 году на её клетке сделали соответствующую
надпись.
</p>
<p>«Винни-Пух» представляет собой дилогию, но каждая из двух книг Милна распадается на 10 рассказов (stories) с собственным сюжетом, которые могут читаться, экранизироваться и т. д. независимо друг от друга. В некоторых переводах деление на две части
не сохранено, в других не переведена вторая («Дом на Пуховой опушке»). Иногда первая и вторая книги выполнены разными переводчиками. Такова необычная судьба немецкого Винни-Пуха: первая книга вышла в немецком переводе в 1928 году, а вторая лишь
в 1954; между этими датами — ряд трагических событий германской истории.</p>
<p>Действие книг о Пухе происходит в 500-акровом лесу Эшдаун близ купленной Милнами в 1925 году фермы Кочфорд в графстве Восточный Сассекс, Англия, представленном в книге как Стоакровый лес (англ. The Hundred Acre Wood, в пересказе Заходера — Чудесный
лес). Реальными являются также Шесть сосен и ручеёк, у которого был найден Северный Полюс, а также упоминаемая в тексте растительность, в том числе колючий утёсник (gorse-bush, чертополох у Заходера), в который падает Пух[2]. Маленький Кристофер
Робин любил забираться в дупла деревьев и играть там с Пухом, поэтому многие персонажи книг живут в дуплах, и значительная часть действия происходит в таких жилищах или на ветвях деревьев[2]. Алан Милн, Кристофер Робин и Винни-Пух. Фотография
из Британской национальной портретной галереи</p>
<p>Действие «Винни-Пуха» разворачивается одновременно в трёх планах — это мир игрушек в детской, мир зверей «на своей территории» в Стоакровом лесу и мир персонажей в рассказах отца сыну (это наиболее чётко показано в самом начале)[4]. В дальнейшем
рассказчик исчезает из повествования, и сказочный мир начинает собственное существование, разрастаясь от главы к главе[6]. Отмечалось сходство пространства и мира персонажей «Винни-Пуха» с классическим античным и средневековым эпосом[6]. Многообещающие
эпические начинания персонажей (путешествия, подвиги, охоты, игры) оказываются комически малозначительными, в то время как настоящие события происходят во внутреннем мире героев (помощь в беде, гостеприимство, дружба)[6].</p>
<p>Книги Милна выросли из устных рассказов и игр с Кристофером Робином; устное происхождение характерно и для многих других знаменитых литературных сказок[6]. «Я, собственно, ничего не придумывал, мне оставалось только описывать», как говорил впоследствии
Милн[5]. Реальными игрушками Кристофера Робина были также Пятачок (подарок соседей), Иа-Иа без хвоста (ранний подарок родителей), Кенга с Крошкой Ру в сумке и Тигра (куплены родителями впоследствии специально для развития сюжета вечерних рассказов
сыну). В рассказах они появляются именно в таком порядке[2]. Сову и Кролика Милн придумал сам; на иллюстрациях Шепарда они выглядят не как игрушки, а как настоящие животные, Кролик говорит Сове: «Только у меня и тебя есть мозги. У остальных —
опилки». В процессе игры все эти персонажи получили индивидуальные повадки, привычки и манеру разговора[6]. На созданный Милном мир животных повлияла повесть Кеннета Грэма «Ветер в ивах», которой он восхищался и которую ранее иллюстрировал Шепард[5],
возможна также скрытая полемика с «Книгой джунглей» Киплинга[5]. Текст взят из Википедии.</p>
</div>
</div>
<script>
var avatarElem = document.getElementById('avatar');
var avatarSourceBottom = avatarElem.getBoundingClientRect().bottom + window.pageYOffset;
window.onscroll = function() {
if (avatarElem.classList.contains('fixed') && window.pageYOffset < avatarSourceBottom) {
avatarElem.classList.remove('fixed');
} else if (window.pageYOffset > avatarSourceBottom) {
avatarElem.classList.add('fixed');
}
};
</script>
</body>
</html>

View file

@ -1,111 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body,
html {
margin: 0;
padding: 0;
}
.column-left {
float: left;
width: 30%;
background: #aef;
}
.column-right {
margin-left: 30%;
width: 70%;
background: tan;
overflow: auto;
/* расшириться вниз захватить float'ы */
}
.header {
line-height: 60px;
background: yellow;
}
.inner {
margin: 1em;
font-size: 130%;
}
#avatar {
float: left;
margin: 0 1em .5em 0;
border: 1px solid black;
text-align: center;
background: white;
}
</style>
</head>
<body>
<div class="header">Шапка</div>
<div class="column-left">
<div class="inner">
<h3>Персонажи:</h3>
<ul>
<li>Винни-Пух</li>
<li>Ослик Иа</li>
<li>Сова</li>
<li>Кролик</li>
</ul>
</div>
</div>
<div class="column-right">
<div class="inner">
<h3>Винни-Пух</h3>
<div id="avatar">
<img src="https://js.cx/clipart/winnie-mult.jpg" width="200" height="150">
<div>Кадр из мультфильма</div>
</div>
<p>Ви́нни-Пу́х (англ. Winnie-the-Pooh) — плюшевый мишка, персонаж повестей и стихов Алана Александра Милна (цикл не имеет общего названия и обычно тоже называется «Винни-Пух», по первой книге). Один из самых известных героев детской литературы XX века.</p>
<p>В 1960-е—1970-е годы, благодаря пересказу Бориса Заходера «Винни-Пух и все-все-все», а затем и фильмам студии «Союзмультфильм», где мишку озвучивал Евгений Леонов, Винни-Пух стал очень популярен и в Советском Союзе.</p>
<p>Как и многие другие персонажи книги Милна, медвежонок Винни получил имя от одной из реальных игрушек Кристофера Робина (1920—1996), сына писателя. В свою очередь, плюшевый мишка Винни-Пух был назван по имени медведицы по кличке Виннипег (Винни),
содержавшейся в 1920-х в Лондонском зоопарке.</p>
<p>Медведица Виннипег (американский чёрный медведь) попала в Великобританию как живой талисман (маскот) Канадского армейского ветеринарного корпуса из Канады, а именно из окрестностей города Виннипега. Она оказалась в кавалерийском полку «Форт Гарри
Хорс» 24 августа 1914 года ещё будучи медвежонком (её купил у канадского охотника-траппера за двадцать долларов 27-летний полковой ветеринар лейтенант Гарри Колборн, заботившийся о ней и в дальнейшем). Уже в октябре того же года медвежонок был
привезён вместе с войсками в Британию, а так как полк должен был быть в ходе Первой мировой войны переправлен во Францию, то в декабре было принято решение оставить зверя до конца войны в Лондонском зоопарке. Медведица полюбилась лондонцам, и
военные не стали возражать против того, чтобы не забирать её из зоопарка и после войны[1]. До конца дней (она умерла 12 мая 1934 года) медведица находилась на довольствии ветеринарного корпуса, о чём в 1919 году на её клетке сделали соответствующую
надпись.
</p>
<p>«Винни-Пух» представляет собой дилогию, но каждая из двух книг Милна распадается на 10 рассказов (stories) с собственным сюжетом, которые могут читаться, экранизироваться и т. д. независимо друг от друга. В некоторых переводах деление на две части
не сохранено, в других не переведена вторая («Дом на Пуховой опушке»). Иногда первая и вторая книги выполнены разными переводчиками. Такова необычная судьба немецкого Винни-Пуха: первая книга вышла в немецком переводе в 1928 году, а вторая лишь
в 1954; между этими датами — ряд трагических событий германской истории.</p>
<p>Действие книг о Пухе происходит в 500-акровом лесу Эшдаун близ купленной Милнами в 1925 году фермы Кочфорд в графстве Восточный Сассекс, Англия, представленном в книге как Стоакровый лес (англ. The Hundred Acre Wood, в пересказе Заходера — Чудесный
лес). Реальными являются также Шесть сосен и ручеёк, у которого был найден Северный Полюс, а также упоминаемая в тексте растительность, в том числе колючий утёсник (gorse-bush, чертополох у Заходера), в который падает Пух[2]. Маленький Кристофер
Робин любил забираться в дупла деревьев и играть там с Пухом, поэтому многие персонажи книг живут в дуплах, и значительная часть действия происходит в таких жилищах или на ветвях деревьев[2]. Алан Милн, Кристофер Робин и Винни-Пух. Фотография
из Британской национальной портретной галереи</p>
<p>Действие «Винни-Пуха» разворачивается одновременно в трёх планах — это мир игрушек в детской, мир зверей «на своей территории» в Стоакровом лесу и мир персонажей в рассказах отца сыну (это наиболее чётко показано в самом начале)[4]. В дальнейшем
рассказчик исчезает из повествования, и сказочный мир начинает собственное существование, разрастаясь от главы к главе[6]. Отмечалось сходство пространства и мира персонажей «Винни-Пуха» с классическим античным и средневековым эпосом[6]. Многообещающие
эпические начинания персонажей (путешествия, подвиги, охоты, игры) оказываются комически малозначительными, в то время как настоящие события происходят во внутреннем мире героев (помощь в беде, гостеприимство, дружба)[6].</p>
<p>Книги Милна выросли из устных рассказов и игр с Кристофером Робином; устное происхождение характерно и для многих других знаменитых литературных сказок[6]. «Я, собственно, ничего не придумывал, мне оставалось только описывать», как говорил впоследствии
Милн[5]. Реальными игрушками Кристофера Робина были также Пятачок (подарок соседей), Иа-Иа без хвоста (ранний подарок родителей), Кенга с Крошкой Ру в сумке и Тигра (куплены родителями впоследствии специально для развития сюжета вечерних рассказов
сыну). В рассказах они появляются именно в таком порядке[2]. Сову и Кролика Милн придумал сам; на иллюстрациях Шепарда они выглядят не как игрушки, а как настоящие животные, Кролик говорит Сове: «Только у меня и тебя есть мозги. У остальных —
опилки». В процессе игры все эти персонажи получили индивидуальные повадки, привычки и манеру разговора[6]. На созданный Милном мир животных повлияла повесть Кеннета Грэма «Ветер в ивах», которой он восхищался и которую ранее иллюстрировал Шепард[5],
возможна также скрытая полемика с «Книгой джунглей» Киплинга[5]. Текст взят из Википедии.</p>
</div>
</div>
</body>
</html>

View file

@ -1,14 +0,0 @@
importance: 5
---
# Аватар наверху при прокрутке
Сделайте так, чтобы при прокрутке ниже элемента `#avatar` (картинка с Винни-Пухом) -- он продолжал показываться в левом-верхнем углу.
При прокрутке вверх -- должен возвращаться на обычное место.
Прокрутите вниз, чтобы увидеть:
[iframe src="solution" height=300 link border="1"]

View file

@ -0,0 +1,66 @@
The core of the solution is a function that adds more dates to the page (or loads more stuff in real-life) while we're at the page end.
We can call it immediately and add as a `window.onscroll` handler.
The most important question is: "How do we detect that the page is scrolled to bottom?"
Let's use window-relative coordinates.
The document is represented (and contained) within `<html>` tag, that is `document.documentElement`.
We can get window-relative coordinates of the whole document as `document.documentElement.getBoundingClientRect()`. And the `bottom` property will be window-relative coordinate of the document end.
For instance, if the height of the whole HTML document is 2000px, then:
```js
// When we're on the top of the page
// window-relative top = 0
document.documentElement.getBoundingClientRect().top = 0
// window-relative bottom = 2000
// the document is long, so that is probably far beyound the window bottom
document.documentElement.getBoundingClientRect().bottom = 2000
```
If we scroll `500px` below, then:
```js
// document top is above the window 500px
document.documentElement.getBoundingClientRect().top = -500
// document bottom is 500px closer
document.documentElement.getBoundingClientRect().bottom = 1500
```
When we scroll till the end, assuming that the window height is `600px`:
```js
// document top is above the window 500px
document.documentElement.getBoundingClientRect().top = -1400
// document bottom is 500px closer
document.documentElement.getBoundingClientRect().bottom = 600
```
Please note that the bottom can't be 0, because it never reaches the window top. The lowest limit of the bottom coordinate is the window height, we can't scroll it any more up.
And the window height is `document.documentElement.clientHeight`.
We want the document bottom be no more than `100px` away from it.
So here's the function:
```js
function populate() {
while(true) {
// document bottom
let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom;
// if it's greater than window height + 100px, then we're not at the page back
// (see examples above, big bottom means we need to scroll more)
if (windowRelativeBottom > document.documentElement.clientHeight + 100) break;
// otherwise let's add more data
document.body.insertAdjacentHTML("beforeend", `<p>Date: ${new Date()}</p>`);
}
}
```

View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h1>Scroll me</h1>
<script>
function populate() {
while(true) {
let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom;
if (windowRelativeBottom > document.documentElement.clientHeight + 100) break;
document.body.insertAdjacentHTML("beforeend", `<p>Date: ${new Date()}</p>`);
}
}
window.addEventListener('scroll', populate);
populate(); // init document
</script>
</body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h1>Scroll me</h1>
<script>
// ... your code ...
</script>
</body>
</html>

View file

@ -0,0 +1,20 @@
importance: 5
---
# Endless page
Create an endless page. When a visitor scrolls it to the end, it auto-appends current date-time to the text (so that a visitor can scroll more).
Like this:
[iframe src="solution" height=200]
Please note two important features of the scroll:
1. **The scroll is "elastic".** We can scroll a little beyound the document start or end in some browsers/devices (empty space below is shown, and then the document will automatically "bounces back" to normal).
2. **The scroll is imprecise.** When we scroll to page end, then we may be in fact like 0-50px away from the real document bottom.
So, "scrolling to the end" should mean that the visitor is no more than 100px away from the document end.
P.S. In real life we may want to show "more messages" or "more goods".

View file

@ -1,94 +0,0 @@
Добавим в документ `DIV` с кнопкой:
```html
<div id="updown"></div>
```
Сама кнопка должна иметь `position:fixed`.
```css
#updown {
position: fixed;
top: 30px;
left: 10px;
cursor: pointer;
}
```
Кнопка является CSS-спрайтом, поэтому мы дополнительно добавляем ей размер и два состояния:
```css
#updown {
height: 9px;
width: 14px;
position: fixed;
top: 30px;
left: 10px;
cursor: pointer;
}
#updown.up {
background: url(...updown.gif) left top;
}
#updown.down {
background: url(...updown.gif) left -9px;
}
```
Для решения необходимо аккуратно разобрать все возможные состояния кнопки и указать, что делать при каждом.
Состояние -- это просто класс элемента: `up/down` или пустая строка, если кнопка не видна.
При прокрутке состояния меняются следующим образом:
```js
window.onscroll = function() {
var pageY = window.pageYOffset || document.documentElement.scrollTop;
var innerHeight = document.documentElement.clientHeight;
switch (updownElem.className) {
case '':
if (pageY > innerHeight) {
updownElem.className = 'up';
}
break;
case 'up':
if (pageY < innerHeight) {
updownElem.className = '';
}
break;
case 'down':
if (pageY > innerHeight) {
updownElem.className = 'up';
}
break;
}
}
```
При клике:
```js
var pageYLabel = 0;
updownElem.onclick = function() {
var pageY = window.pageYOffset || document.documentElement.scrollTop;
switch (this.className) {
case 'up':
pageYLabel = pageY;
window.scrollTo(0, 0);
this.className = 'down';
break;
case 'down':
window.scrollTo(0, pageYLabel);
this.className = 'up';
}
}
```

View file

@ -1,6 +1,5 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
body,
@ -18,7 +17,7 @@
text-align: justify;
}
#updown {
#arrowTop {
height: 9px;
width: 14px;
color: green;
@ -28,13 +27,10 @@
cursor: pointer;
}
#updown.up::before {
#arrowTop::before {
content: '▲';
}
#updown.down::before {
content: '▼';
}
</style>
<meta charset="utf-8">
</head>
@ -43,60 +39,23 @@
<div id="matrix">
<script>
for (var i = 0; i < 2000; i++) document.writeln(i)
for (let i = 0; i < 2000; i++) document.writeln(i)
</script>
</div>
<div id="updown"></div>
<div id="arrowTop" hidden></div>
<script>
var updownElem = document.getElementById('updown');
var pageYLabel = 0;
arrowTop.onclick = function() {
window.scrollTo(pageXOffset, 0);
// after scrollTo, there will be a "scroll" event, so the arrow will hide automatically
};
updownElem.onclick = function() {
var pageY = window.pageYOffset || document.documentElement.scrollTop;
switch (this.className) {
case 'up':
pageYLabel = pageY;
window.scrollTo(0, 0);
this.className = 'down';
break;
case 'down':
window.scrollTo(0, pageYLabel);
this.className = 'up';
}
}
window.onscroll = function() {
var pageY = window.pageYOffset || document.documentElement.scrollTop;
var innerHeight = document.documentElement.clientHeight;
switch (updownElem.className) {
case '':
if (pageY > innerHeight) {
updownElem.className = 'up';
}
break;
case 'up':
if (pageY < innerHeight) {
updownElem.className = '';
}
break;
case 'down':
if (pageY > innerHeight) {
updownElem.className = 'up';
}
break;
}
}
window.addEventListener('scroll', function() {
arrowTop.hidden = (pageYOffset < document.documentElement.clientHeight);
});
</script>
</body>
</body>
</html>

View file

@ -1,8 +1,6 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
body,
html {
@ -18,24 +16,38 @@
overflow: auto;
text-align: justify;
}
#arrowTop {
height: 9px;
width: 14px;
color: green;
position: fixed;
top: 10px;
left: 10px;
cursor: pointer;
}
#arrowTop::before {
content: '▲';
}
</style>
<meta charset="utf-8">
</head>
<body>
Символы для стрелок: ▲ ▼
<div id="matrix">
<script>
for (var i = 0; i < 2000; i++) document.writeln(i)
for (let i = 0; i < 2000; i++) document.writeln(i)
</script>
</div>
<script>
// ... ваш код
// при необходимости, в документ можно добавить элементы и стили
</script>
</body>
<div id="arrowTop"></div>
<script>
// ... your code ...
</script>
</body>
</html>

View file

@ -1,20 +1,16 @@
importance: 3
importance: 5
---
# Кнопка вверх-вниз
# Up/down button
Создайте кнопку навигации, которая помогает при прокрутке страницы.
Create a "to the top" button to help with page scrolling.
Работать должна следующим образом:
It should work like this:
- While the page is not scrolled down at least for the window height -- it's invisible.
- When the page is scrolled down more than the window height -- there appears an "upwards" arrow in the left-top corner. If the page is scrolled back, it disappears.
- When the arrow is clicked, the page scrolls to the top.
- Пока страница промотана меньше чем на высоту экрана вниз -- кнопка не видна.
- При промотке страницы вниз больше, чем на высоту экрана, появляется кнопка "стрелка вверх".
- При нажатии на нее страница прыгает вверх, но не только. Дополнительно, кнопка меняется на "стрелка вниз" и при клике возвратит на старое место. Если же в этом состоянии посетитель сам прокрутит вниз больше, чем один экран, то она вновь изменится на "стрелка вверх".
Должен получиться удобный навигационный помощник.
Посмотрите, как оно должно работать, в ифрейме ниже. Прокрутите ифрейм, навигационная стрелка появится слева-сверху.
Like this:
[iframe border="1" height="200" link src="solution"]

View file

@ -1,19 +1,21 @@
Функция должна по текущей прокрутке определять, какие изображения видимы, и загружать их.
The `onscroll` handler should check which images are visible and show them.
Она должна срабатывать не только при прокрутке, но и при загрузке. Вполне достаточно для этого -- указать ее вызов в скрипте под страницей, вот так:
We also may want to run it when the page loads, to detect immediately visible images prior to any scrolling and load them.
If we put it at the `<body>` bottom, then it runs when the page content is loaded.
```js
...страница...
// ...the page content is above...
function isVisible(elem) {
var coords = elem.getBoundingClientRect();
let coords = elem.getBoundingClientRect();
var windowHeight = document.documentElement.clientHeight;
let windowHeight = document.documentElement.clientHeight;
// верхняя граница elem в пределах видимости ИЛИ нижняя граница видима
var topVisible = coords.top > 0 && coords.top < windowHeight;
var bottomVisible = coords.bottom < windowHeight && coords.bottom > 0;
// top elem edge is visible OR bottom elem edge is visible
let topVisible = coords.top > 0 && coords.top < windowHeight;
let bottomVisible = coords.bottom < windowHeight && coords.bottom > 0;
return topVisible || bottomVisible;
}
@ -24,11 +26,6 @@ window.onscroll = showVisible;
*/!*
```
При запуске функция ищет все видимые картинки с `realsrc` и перемещает значение `realsrc` в `src`. Обратите внимание, т.к. атрибут `realsrc` нестандартный, то для доступа к нему мы используем `get/setAttribute`. А `src` -- стандартный, поэтому можно обратиться по DOM-свойству.
**Функция проверки видимости `isVisible(elem)` получает координаты текущей видимой области и сравнивает их с элементом.**
Для видимости достаточно, чтобы координаты верхней(или нижней) границы элемента находились между границами видимой области.
В решении также указан вариант с `isVisible`, который расширяет область видимости на +-1 страницу (высота страницы -- `document.documentElement.clientHeight`).
For visible images we can take `img.dataset.src` and assign it to `img.src` (if not did it yet).
P.S. The solution also has a variant of `isVisible` that "pre-loads" images that are within 1 page above/below (the page height is `document.documentElement.clientHeight`).

View file

@ -1,164 +1,228 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.news-item {
width: 700px;
text-align: justify;
margin-top: 20px;
}
.news-item .title {
font-weight: bold;
margin-bottom: 5px;
}
</style>
</head>
<body>
<p>Тексты и картинки взяты с сайта http://etoday.ru. </p>
<p>Text and pictures are from https://wikipedia.org.</p>
<h3>Все изображения с realsrc загружаются, когда становятся видимыми.</h3>
<h3>All images with <code>data-src</code> load when become visible.</h3>
<h1>Solar system</h1>
<p>The Solar System is the gravitationally bound system comprising the Sun and the objects that
orbit it, either directly or indirectly. Of those objects that orbit the Sun directly,
the largest eight are the planets, with the remainder being significantly smaller objects,
such as dwarf planets and small Solar System bodies.
Of the objects that orbit the Sun indirectly, the moons,
two are larger than the smallest planet, Mercury.</p>
<p>The Solar System formed 4.6 billion years ago from the gravitational collapse of a giant
interstellar molecular cloud. The vast majority of the system's mass is in the Sun, with most
of the remaining mass contained in Jupiter. The four smaller inner planets, Mercury, Venus,
Earth and Mars, are terrestrial planets, being primarily composed of rock and metal.
The four outer planets are giant planets, being substantially more massive than the terrestrials.
The two largest, Jupiter and Saturn, are gas giants, being composed mainly of hydrogen and helium;
the two outermost planets, Uranus and Neptune, are ice giants,
being composed mostly of substances with relatively high melting points compared with hydrogen
and helium, called volatiles, such as water, ammonia and methane.
All planets have almost circular orbits that lie within a nearly flat disc called the ecliptic.</p>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/planets.jpg" width="640" height="360">
</figure>
<h1>Sun</h1>
<p>The Sun is the Solar System's star and by far its most massive component.
Its large mass (332,900 Earth masses) produces temperatures and densities in its core
high enough to sustain nuclear fusion of hydrogen into helium, making it a main-sequence star.
This releases an enormous amount of energy,
mostly radiated into space as electromagnetic radiation peaking in visible light.</p>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/sun.jpg" width="658" height="658">
</figure>
<h1>Mercury</h1>
<p>Mercury (0.4 AU from the Sun) is the closest planet to the Sun and the smallest planet
in the Solar System (0.055 Earth masses).
Mercury has no natural satellites; besides impact craters, its only known geological features
are lobed ridges or rupes that were probably produced by a period of contraction early in
its history.[67] Mercury's very tenuous atmosphere consists of atoms blasted off its
surface by the solar wind.[68] Its relatively large iron core and thin mantle have not yet
been adequately explained. Hypotheses include that its outer layers were stripped off by a
giant impact; or, that it was prevented from fully accreting by the young Sun's energy.</p>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/mercury.jpg" width="390" height="390">
</figure>
<h1>Venus</h1>
<p>Venus (0.7 AU from the Sun) is close in size to Earth (0.815 Earth masses) and, like Earth,
has a thick silicate mantle around an iron core, a substantial atmosphere, and evidence of
internal geological activity. It is much drier than Earth, and its atmosphere is ninety times
as dense. Venus has no natural satellites. It is the hottest planet, with surface temperatures
over 400 °C (752°F), most likely due to the amount of greenhouse gases in the atmosphere.
No definitive evidence of current geological activity has been detected on Venus,
but it has no magnetic field that would prevent depletion of its substantial atmosphere,
which suggests that its atmosphere is being replenished by volcanic eruptions.</p>
<div class="news-item">
<div class="title">Космопорт Америка \ Architecture</div>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/venus.jpg" width="390" height="390">
</figure>
Будущее уже сейчас! Скоро фраза из фантастического фильма "флипнуть до космопорта" станет реальностью. По крайней мере вторую ее часть человечество обеспечило. В октябре состоялась официальная церемония открытия космопорта «Америка», первой в мире коммерческой
площадки для частных космических полетов. Космопорт открылся в пустыне штата Нью-Мексико. Проект был реализован английским бюро Foster and Partners. Космопорт включает в себя зал ожидания и подготовки к полетам, диспетчерский пункт и ангар. Также
обеспечена взлетно-посадочная полоса длиной в три километра.
<h1>Earth</h1>
<div class="illustrations">
<img src="https://js.cx/lazyimg/1.gif" width="200" height="140" realsrc="https://js.cx/lazyimg/1.jpg">
</div>
</div>
<p>Earth (1 AU from the Sun) is the largest and densest of the inner planets,
the only one known to have current geological activity, and the only place where life
is known to exist. Its liquid hydrosphere is unique among the terrestrial planets,
and it is the only planet where plate tectonics has been observed.
Earth's atmosphere is radically different from those of the other planets,
having been altered by the presence of life to contain 21% free oxygen.
It has one natural satellite, the Moon, the only large satellite of a terrestrial planet
in the Solar System.</p>
<div class="news-item">
<div class="title">Рокер и супермодель в Vogue Russia \ Celebrities</div>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/earth.jpg" width="390" height="390">
</figure>
Супермодель Анна Вялицына (Anne Vyalitsyna) и музыкант Адам Ливайн (Adam Levine) снялись в ноябрьском номере Vogue Russia. Снимал их Аликс Малка (Alix Malka). Анна и Адам примерили на себя рок-н-ролльные наряды от Alexander Wang, Louis Vuitton, Alexander
McQueen, Balmain, Yves Saint Laurent, подобранные для них Катериной Мухиной.
<div class="illustrations">
<img src="https://js.cx/lazyimg/1.gif" width="200" height="259" realsrc="https://js.cx/lazyimg/2-1.jpg">
<img src="https://js.cx/lazyimg/1.gif" width="200" height="260" realsrc="https://js.cx/lazyimg/2-2.jpg">
</div>
</div>
<h1>Mars</h1>
<div class="news-item">
<div class="title">Старость - не радость в Vogue Italia \ Fashion Photo</div>
<p>Mars (1.5 AU from the Sun) is smaller than Earth and Venus (0.107 Earth masses).
It has an atmosphere of mostly carbon dioxide with a surface pressure of 6.1 millibars
(roughly 0.6% of that of Earth). Its surface, peppered with vast volcanoes,
such as Olympus Mons, and rift valleys, such as Valles Marineris, shows geological
activity that may have persisted until as recently as 2 million years ago.
Its red colour comes from iron oxide (rust) in its soil.
Mars has two tiny natural satellites (Deimos and Phobos) thought to be captured asteroids.</p>
Стивен Мейзел (Steven Meisel) снял фотосессию для октябрьского Vogue Italia. В съемках приняли участие: Карен Элсон (Karen Elson), Джиневер ван Синус (Guinevere van Seenus), Эмма Балфур (Emma Balfour), Эн Уст (An Oost), Коринна Ингенлеф (Corinna Ingenleuf),
Танга Моро (Tanga Moreau), Кордула Рейер (Cordula Reyer), Гейл о`Нил (Gail O'Neil), Эвелин Кун (Evelyn Kuhn), Каролин де Мэгрэ (Caroline de Maigret), Дэльфин Бафор (Delfine Bafort), Кирстен Оуэн (Kirsten Owen), Гунилла Линдблад (Gunilla Lindblad).
<div class="illustrations">
<img src="https://js.cx/lazyimg/1.gif" width="341" height="474" realsrc="https://js.cx/lazyimg/3-1.jpg">
<img src="https://js.cx/lazyimg/1.gif" width="338" height="474" realsrc="https://js.cx/lazyimg/3-2.jpg">
</div>
</div>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/mars.jpg" width="390" height="390">
</figure>
<div class="news-item">
<div class="title">"Вышитый рентген" Matthew Cox \ Art</div>
<h1>Jupiter</h1>
Художник из Филадельфии Мэтью Кокс (Matthew Cox) создал серию работ, в которых объединены медицинский рентген и вышивка. Художник взял рентгенограммы и вышил их предполагаемое содержание частично со скелетными элементами. Получилось зловеще и интригующе.
Выставка "Вышитый рентген" будет демонстрироваться в галерее Packer/Schopf в Майами, в рамках Базельской Художественной Недели. Эта серия - только треть творческой продукции Кокса. Он также создает традиционные картины и иллюстрации.
<div class="illustrations"><img src="https://js.cx/lazyimg/1.gif" width="680" height="452" realsrc="https://js.cx/lazyimg/4.jpg"></div>
</div>
<p>Jupiter (5.2 AU), at 318 Earth masses, is 2.5 times the mass of all the other planets put together.
It is composed largely of hydrogen and helium.
Jupiter's strong internal heat creates semi-permanent features in its atmosphere,
such as cloud bands and the Great Red Spot. Jupiter has 67 known satellites.
The four largest, Ganymede, Callisto, Io, and Europa, show similarities to the terrestrial planets,
such as volcanism and internal heating.
Ganymede, the largest satellite in the Solar System, is larger than Mercury.</p>
<div class="news-item">
<div class="title">Подарочный каталог Apple 1983 \ Creative</div>
Etoday предлагает полистать страницы подарочного каталога продукции Apple образца 1983 года. Кажется, это было так давно! Эта серия - только треть творческой продукции Кокса. Он также создает традиционные картины и иллюстрации.
<div class="illustrations"><img src="https://js.cx/lazyimg/1.gif" width="600" height="393" realsrc="https://js.cx/lazyimg/5.jpg"></div>
</div>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/jupiter.jpg" width="390" height="390">
</figure>
<div class="news-item">
<div class="title">Винтажные открытки к празднику Halloween \ Illustrations</div>
<h1>Saturn</h1>
<p>Saturn (9.5 AU), distinguished by its extensive ring system,
has several similarities to Jupiter, such as its atmospheric composition and magnetosphere.
Although Saturn has 60% of Jupiter's volume, it is less than a third as massive,
at 95 Earth masses. Saturn is the only planet of the Solar System that is less dense than water.
The rings of Saturn are made up of small ice and rock particles.
Saturn has 62 confirmed satellites composed largely of ice.
Two of these, Titan and Enceladus, show signs of geological activity.
Titan, the second-largest moon in the Solar System, is larger than Mercury
and the only satellite in the Solar System with a substantial atmosphere.</p>
Занимательная коллекция старых почтовых открыток праздника Halloween. Открытки взяты из ньюйоркской публичной библиотеки и датируются примерно 1910 г.
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/saturn.jpg" width="805" height="390">
</figure>
<div class="illustrations"><img src="https://js.cx/lazyimg/1.gif" width="680" height="433" realsrc="https://js.cx/lazyimg/6.jpg"></div>
</div>
<h1>Uranus</h1>
<p>Uranus (19.2 AU), at 14 Earth masses, is the lightest of the outer planets.
Uniquely among the planets, it orbits the Sun on its side;
its axial tilt is over ninety degrees to the ecliptic.
It has a much colder core than the other giant planets and radiates very little heat into space.
Uranus has 27 known satellites, the largest ones being Titania,
Oberon, Umbriel, Ariel, and Miranda.</p>
<div class="news-item">
<div class="title">Фотограф Emily Lee \ Photography</div>
Молодой фотограф Эмили Ли (Emily Lee) использует фотографию, чтобы выразить свои чувства. "Когда я смотрю на жизнь через камеру, вижу все более ясно, - пишет она на своем профиле Flickr. - Фотосъемка - это искусство наблюдения." Эмили Ли - обладательница
большого таланта и умения глубоко понимать искусство, хотя учится еще только в средней школе.
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/uranus.jpg" width="390" height="390">
</figure>
<div class="illustrations"><img src="https://js.cx/lazyimg/1.gif" width="680" height="453" realsrc="https://js.cx/lazyimg/7.jpg"></div>
</div>
<div class="news-item">
<div class="title">Иконы моды в Fashimals \ Creative</div>
Fashimals - tumblr-блог, посвященный иконам моды, превращенным в животных. Здесь есть Анна Винтур, Карл Лагерфельд, Терри Ричардсон, а также много других их коллег.
<div class="illustrations"><img src="https://js.cx/lazyimg/1.gif" width="600" height="622" realsrc="https://js.cx/lazyimg/8.jpg"></div>
</div>
<h1>Neptune</h1>
<p>Neptune (30.1 AU), though slightly smaller than Uranus, is more massive (equivalent to 17 Earths)
and hence more dense. It radiates more internal heat,
but not as much as Jupiter or Saturn.
Neptune has 14 known satellites. The largest, Triton, is geologically active,
with geysers of liquid nitrogen.
Triton is the only large satellite with a retrograde orbit.
Neptune is accompanied in its orbit by several minor planets, termed Neptune trojans,
that are in 1:1 resonance with it.</p>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/neptune.jpg" width="390" height="390">
</figure>
<script>
/**
* Проверяет элемент на попадание в видимую часть экрана.
* Для попадания достаточно, чтобы верхняя или нижняя границы элемента были видны.
* Tests if the element is visible (within the visible part of the page)
* It's enough that the top or bottom edge of the element are visible
*/
function isVisible(elem) {
var coords = elem.getBoundingClientRect();
let coords = elem.getBoundingClientRect();
var windowHeight = document.documentElement.clientHeight;
let windowHeight = document.documentElement.clientHeight;
// верхняя граница elem в пределах видимости ИЛИ нижняя граница видима
var topVisible = coords.top > 0 && coords.top < windowHeight;
var bottomVisible = coords.bottom < windowHeight && coords.bottom > 0;
// top elem edge is visible OR bottom elem edge is visible
let topVisible = coords.top > 0 && coords.top < windowHeight;
let bottomVisible = coords.bottom < windowHeight && coords.bottom > 0;
return topVisible || bottomVisible;
}
/**
Вариант проверки, считающий элемент видимым,
если он не более чем -1 страница назад или +1 страница вперед
A variant of the test that considers the element visible if it's no more than
one page after/behind the current screen.
function isVisible(elem) {
var coords = elem.getBoundingClientRect();
let coords = elem.getBoundingClientRect();
var windowHeight = document.documentElement.clientHeight;
let windowHeight = document.documentElement.clientHeight;
var extendedTop = -windowHeight;
var extendedBottom = 2 * windowHeight;
let extendedTop = -windowHeight;
let extendedBottom = 2 * windowHeight;
// top visible || bottom visible
var topVisible = coords.top > extendedTop && coords.top < extendedBottom;
var bottomVisible = coords.bottom < extendedBottom && coords.bottom > extendedTop;
let topVisible = coords.top > extendedTop && coords.top < extendedBottom;
let bottomVisible = coords.bottom < extendedBottom && coords.bottom > extendedTop;
return topVisible || bottomVisible;
}
*/
function showVisible() {
var imgs = document.getElementsByTagName('img');
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
var realsrc = img.getAttribute('realsrc');
if (!realsrc) continue;
for (let img of document.querySelectorAll('img')) {
let realSrc = img.dataset.src;
if (!realSrc) continue;
if (isVisible(img)) {
img.src = realsrc;
img.setAttribute('realsrc', '');
// disable caching
// this line should be removed in production code
realSrc += '?nocache=' + Math.random();
img.src = realSrc;
img.dataset.src = '';
}
}
}
window.onscroll = showVisible;
window.addEventListener('scroll', showVisible);
showVisible();
</script>
</body>
</html>

View file

@ -0,0 +1 @@
<svg width='198px' height='198px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-default"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(0 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(36 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.1s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(72 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.2s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(108 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.3s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(144 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.4s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(180 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.5s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(216 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.6s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(252 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.7s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(288 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.8s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(324 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.9s' repeatCount='indefinite'/></rect></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -1,108 +1,228 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.news-item {
width: 700px;
text-align: justify;
margin-top: 20px;
}
.news-item .title {
font-weight: bold;
margin-bottom: 5px;
}
</style>
</head>
<body>
<p>Тексты и картинки взяты с сайта http://etoday.ru. </p>
<p>Text and pictures are from https://wikipedia.org.</p>
<h3>Все изображения с realsrc загружаются, когда становятся видимыми.</h3>
<h3>All images with <code>data-src</code> load when become visible.</h3>
<h1>Solar system</h1>
<p>The Solar System is the gravitationally bound system comprising the Sun and the objects that
orbit it, either directly or indirectly. Of those objects that orbit the Sun directly,
the largest eight are the planets, with the remainder being significantly smaller objects,
such as dwarf planets and small Solar System bodies.
Of the objects that orbit the Sun indirectly, the moons,
two are larger than the smallest planet, Mercury.</p>
<p>The Solar System formed 4.6 billion years ago from the gravitational collapse of a giant
interstellar molecular cloud. The vast majority of the system's mass is in the Sun, with most
of the remaining mass contained in Jupiter. The four smaller inner planets, Mercury, Venus,
Earth and Mars, are terrestrial planets, being primarily composed of rock and metal.
The four outer planets are giant planets, being substantially more massive than the terrestrials.
The two largest, Jupiter and Saturn, are gas giants, being composed mainly of hydrogen and helium;
the two outermost planets, Uranus and Neptune, are ice giants,
being composed mostly of substances with relatively high melting points compared with hydrogen
and helium, called volatiles, such as water, ammonia and methane.
All planets have almost circular orbits that lie within a nearly flat disc called the ecliptic.</p>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/planets.jpg" width="640" height="360">
</figure>
<h1>Sun</h1>
<p>The Sun is the Solar System's star and by far its most massive component.
Its large mass (332,900 Earth masses) produces temperatures and densities in its core
high enough to sustain nuclear fusion of hydrogen into helium, making it a main-sequence star.
This releases an enormous amount of energy,
mostly radiated into space as electromagnetic radiation peaking in visible light.</p>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/sun.jpg" width="658" height="658">
</figure>
<h1>Mercury</h1>
<p>Mercury (0.4 AU from the Sun) is the closest planet to the Sun and the smallest planet
in the Solar System (0.055 Earth masses).
Mercury has no natural satellites; besides impact craters, its only known geological features
are lobed ridges or rupes that were probably produced by a period of contraction early in
its history.[67] Mercury's very tenuous atmosphere consists of atoms blasted off its
surface by the solar wind.[68] Its relatively large iron core and thin mantle have not yet
been adequately explained. Hypotheses include that its outer layers were stripped off by a
giant impact; or, that it was prevented from fully accreting by the young Sun's energy.</p>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/mercury.jpg" width="390" height="390">
</figure>
<h1>Venus</h1>
<p>Venus (0.7 AU from the Sun) is close in size to Earth (0.815 Earth masses) and, like Earth,
has a thick silicate mantle around an iron core, a substantial atmosphere, and evidence of
internal geological activity. It is much drier than Earth, and its atmosphere is ninety times
as dense. Venus has no natural satellites. It is the hottest planet, with surface temperatures
over 400 °C (752°F), most likely due to the amount of greenhouse gases in the atmosphere.
No definitive evidence of current geological activity has been detected on Venus,
but it has no magnetic field that would prevent depletion of its substantial atmosphere,
which suggests that its atmosphere is being replenished by volcanic eruptions.</p>
<div class="news-item">
<div class="title">Космопорт Америка \ Architecture</div>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/venus.jpg" width="390" height="390">
</figure>
Будущее уже сейчас! Скоро фраза из фантастического фильма "флипнуть до космопорта" станет реальностью. По крайней мере вторую ее часть человечество обеспечило. В октябре состоялась официальная церемония открытия космопорта «Америка», первой в мире коммерческой
площадки для частных космических полетов. Космопорт открылся в пустыне штата Нью-Мексико. Проект был реализован английским бюро Foster and Partners. Космопорт включает в себя зал ожидания и подготовки к полетам, диспетчерский пункт и ангар. Также
обеспечена взлетно-посадочная полоса длиной в три километра.
<h1>Earth</h1>
<div class="illustrations">
<img src="https://js.cx/lazyimg/1.gif" width="200" height="140" realsrc="https://js.cx/lazyimg/1.jpg">
</div>
</div>
<p>Earth (1 AU from the Sun) is the largest and densest of the inner planets,
the only one known to have current geological activity, and the only place where life
is known to exist. Its liquid hydrosphere is unique among the terrestrial planets,
and it is the only planet where plate tectonics has been observed.
Earth's atmosphere is radically different from those of the other planets,
having been altered by the presence of life to contain 21% free oxygen.
It has one natural satellite, the Moon, the only large satellite of a terrestrial planet
in the Solar System.</p>
<div class="news-item">
<div class="title">Рокер и супермодель в Vogue Russia \ Celebrities</div>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/earth.jpg" width="390" height="390">
</figure>
Супермодель Анна Вялицына (Anne Vyalitsyna) и музыкант Адам Ливайн (Adam Levine) снялись в ноябрьском номере Vogue Russia. Снимал их Аликс Малка (Alix Malka). Анна и Адам примерили на себя рок-н-ролльные наряды от Alexander Wang, Louis Vuitton, Alexander
McQueen, Balmain, Yves Saint Laurent, подобранные для них Катериной Мухиной.
<div class="illustrations">
<img src="https://js.cx/lazyimg/1.gif" width="200" height="259" realsrc="https://js.cx/lazyimg/2-1.jpg">
<img src="https://js.cx/lazyimg/1.gif" width="200" height="260" realsrc="https://js.cx/lazyimg/2-2.jpg">
</div>
</div>
<h1>Mars</h1>
<div class="news-item">
<div class="title">Старость - не радость в Vogue Italia \ Fashion Photo</div>
<p>Mars (1.5 AU from the Sun) is smaller than Earth and Venus (0.107 Earth masses).
It has an atmosphere of mostly carbon dioxide with a surface pressure of 6.1 millibars
(roughly 0.6% of that of Earth). Its surface, peppered with vast volcanoes,
such as Olympus Mons, and rift valleys, such as Valles Marineris, shows geological
activity that may have persisted until as recently as 2 million years ago.
Its red colour comes from iron oxide (rust) in its soil.
Mars has two tiny natural satellites (Deimos and Phobos) thought to be captured asteroids.</p>
Стивен Мейзел (Steven Meisel) снял фотосессию для октябрьского Vogue Italia. В съемках приняли участие: Карен Элсон (Karen Elson), Джиневер ван Синус (Guinevere van Seenus), Эмма Балфур (Emma Balfour), Эн Уст (An Oost), Коринна Ингенлеф (Corinna Ingenleuf),
Танга Моро (Tanga Moreau), Кордула Рейер (Cordula Reyer), Гейл о`Нил (Gail O'Neil), Эвелин Кун (Evelyn Kuhn), Каролин де Мэгрэ (Caroline de Maigret), Дэльфин Бафор (Delfine Bafort), Кирстен Оуэн (Kirsten Owen), Гунилла Линдблад (Gunilla Lindblad).
<div class="illustrations">
<img src="https://js.cx/lazyimg/1.gif" width="341" height="474" realsrc="https://js.cx/lazyimg/3-1.jpg">
<img src="https://js.cx/lazyimg/1.gif" width="338" height="474" realsrc="https://js.cx/lazyimg/3-2.jpg">
</div>
</div>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/mars.jpg" width="390" height="390">
</figure>
<div class="news-item">
<div class="title">"Вышитый рентген" Matthew Cox \ Art</div>
<h1>Jupiter</h1>
Художник из Филадельфии Мэтью Кокс (Matthew Cox) создал серию работ, в которых объединены медицинский рентген и вышивка. Художник взял рентгенограммы и вышил их предполагаемое содержание частично со скелетными элементами. Получилось зловеще и интригующе.
Выставка "Вышитый рентген" будет демонстрироваться в галерее Packer/Schopf в Майами, в рамках Базельской Художественной Недели. Эта серия - только треть творческой продукции Кокса. Он также создает традиционные картины и иллюстрации.
<div class="illustrations"><img src="https://js.cx/lazyimg/1.gif" width="680" height="452" realsrc="https://js.cx/lazyimg/4.jpg"></div>
</div>
<p>Jupiter (5.2 AU), at 318 Earth masses, is 2.5 times the mass of all the other planets put together.
It is composed largely of hydrogen and helium.
Jupiter's strong internal heat creates semi-permanent features in its atmosphere,
such as cloud bands and the Great Red Spot. Jupiter has 67 known satellites.
The four largest, Ganymede, Callisto, Io, and Europa, show similarities to the terrestrial planets,
such as volcanism and internal heating.
Ganymede, the largest satellite in the Solar System, is larger than Mercury.</p>
<div class="news-item">
<div class="title">Подарочный каталог Apple 1983 \ Creative</div>
Etoday предлагает полистать страницы подарочного каталога продукции Apple образца 1983 года. Кажется, это было так давно! Эта серия - только треть творческой продукции Кокса. Он также создает традиционные картины и иллюстрации.
<div class="illustrations"><img src="https://js.cx/lazyimg/1.gif" width="600" height="393" realsrc="https://js.cx/lazyimg/5.jpg"></div>
</div>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/jupiter.jpg" width="390" height="390">
</figure>
<div class="news-item">
<div class="title">Винтажные открытки к празднику Halloween \ Illustrations</div>
<h1>Saturn</h1>
<p>Saturn (9.5 AU), distinguished by its extensive ring system,
has several similarities to Jupiter, such as its atmospheric composition and magnetosphere.
Although Saturn has 60% of Jupiter's volume, it is less than a third as massive,
at 95 Earth masses. Saturn is the only planet of the Solar System that is less dense than water.
The rings of Saturn are made up of small ice and rock particles.
Saturn has 62 confirmed satellites composed largely of ice.
Two of these, Titan and Enceladus, show signs of geological activity.
Titan, the second-largest moon in the Solar System, is larger than Mercury
and the only satellite in the Solar System with a substantial atmosphere.</p>
Занимательная коллекция старых почтовых открыток праздника Halloween. Открытки взяты из ньюйоркской публичной библиотеки и датируются примерно 1910 г.
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/saturn.jpg" width="805" height="390">
</figure>
<div class="illustrations"><img src="https://js.cx/lazyimg/1.gif" width="680" height="433" realsrc="https://js.cx/lazyimg/6.jpg"></div>
</div>
<h1>Uranus</h1>
<p>Uranus (19.2 AU), at 14 Earth masses, is the lightest of the outer planets.
Uniquely among the planets, it orbits the Sun on its side;
its axial tilt is over ninety degrees to the ecliptic.
It has a much colder core than the other giant planets and radiates very little heat into space.
Uranus has 27 known satellites, the largest ones being Titania,
Oberon, Umbriel, Ariel, and Miranda.</p>
<div class="news-item">
<div class="title">Фотограф Emily Lee \ Photography</div>
Молодой фотограф Эмили Ли (Emily Lee) использует фотографию, чтобы выразить свои чувства. "Когда я смотрю на жизнь через камеру, вижу все более ясно, - пишет она на своем профиле Flickr. - Фотосъемка - это искусство наблюдения." Эмили Ли - обладательница
большого таланта и умения глубоко понимать искусство, хотя учится еще только в средней школе.
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/uranus.jpg" width="390" height="390">
</figure>
<div class="illustrations"><img src="https://js.cx/lazyimg/1.gif" width="680" height="453" realsrc="https://js.cx/lazyimg/7.jpg"></div>
</div>
<div class="news-item">
<div class="title">Иконы моды в Fashimals \ Creative</div>
Fashimals - tumblr-блог, посвященный иконам моды, превращенным в животных. Здесь есть Анна Винтур, Карл Лагерфельд, Терри Ричардсон, а также много других их коллег.
<div class="illustrations"><img src="https://js.cx/lazyimg/1.gif" width="600" height="622" realsrc="https://js.cx/lazyimg/8.jpg"></div>
</div>
<h1>Neptune</h1>
<p>Neptune (30.1 AU), though slightly smaller than Uranus, is more massive (equivalent to 17 Earths)
and hence more dense. It radiates more internal heat,
but not as much as Jupiter or Saturn.
Neptune has 14 known satellites. The largest, Triton, is geologically active,
with geysers of liquid nitrogen.
Triton is the only large satellite with a retrograde orbit.
Neptune is accompanied in its orbit by several minor planets, termed Neptune trojans,
that are in 1:1 resonance with it.</p>
<figure>
<img src="placeholder.svg" data-src="https://en.js.cx/clipart/solar/neptune.jpg" width="390" height="390">
</figure>
<script>
// ... ваш код ...
/**
* Tests if the element is visible (within the visible part of the page)
* It's enough that the top or bottom edge of the element are visible
*/
function isVisible(elem) {
var coords = elem.getBoundingClientRect();
var windowHeight = document.documentElement.clientHeight;
// top elem edge is visible OR bottom elem edge is visible
var topVisible = coords.top > 0 && coords.top < windowHeight;
var bottomVisible = coords.bottom < windowHeight && coords.bottom > 0;
return topVisible || bottomVisible;
}
/**
A variant of the test that considers the element visible if it's no more than
one page after/behind the current screen.
function isVisible(elem) {
var coords = elem.getBoundingClientRect();
var windowHeight = document.documentElement.clientHeight;
var extendedTop = -windowHeight;
var extendedBottom = 2 * windowHeight;
// top visible || bottom visible
var topVisible = coords.top > extendedTop && coords.top < extendedBottom;
var bottomVisible = coords.bottom < extendedBottom && coords.bottom > extendedTop;
return topVisible || bottomVisible;
}
*/
function showVisible() {
for (let img of document.querySelectorAll('img')) {
let realSrc = img.dataset.src;
if (!realSrc) continue;
if (isVisible(img)) {
// disable caching
// this line should be removed in production code
realSrc += '?nocache=' + Math.random();
img.src = realSrc;
img.dataset.src = '';
}
}
}
window.addEventListener('scroll', showVisible);
showVisible();
</script>
</body>
</html>

View file

@ -0,0 +1 @@
<svg width='198px' height='198px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-default"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(0 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(36 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.1s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(72 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.2s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(108 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.3s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(144 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.4s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(180 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.5s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(216 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.6s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(252 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.7s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(288 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.8s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='rgba(44,44,44,0.6)' transform='rotate(324 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.9s' repeatCount='indefinite'/></rect></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -2,49 +2,29 @@ importance: 4
---
# Загрузка видимых изображений
# Load visible images
Задача, которая описана ниже, демонстрирует результативный метод оптимизации страницы.
Let's say we have a slow-speed client and want to save his mobile traffic.
С целью экономии трафика и более быстрой загрузки страницы изображения на ней заменяются на "макеты".
Вместо такого изображения:
For that purpose we decide not to show images immediately, but rather replace them with placeholders, like this:
```html
<img src="yozhik.jpg" width="128" height="128">
<img *!*src="placeholder.svg"*/!* width="128" height="128" *!*data-src="real.jpg"*/!*>
```
![|width="128" height="128"](https://js.cx/clipart/yozhik.jpg)
So, initially all images are `placeholder.svg`. When the page scrolls to the position where the user can see the image -- we change `src` to the one in `data-src`, and so the image loads.
Стоит вот такое:
```html
<img *!*src="1.gif"*/!* width="128" height="128" *!*realsrc="yozhik.jpg"*/!*>
```
![|width="128" height="128"](https://js.cx/lazyimg/1.gif)
То есть настоящий URL находится в атрибуте `realsrc` (название атрибута можно выбрать любое). А в `src` поставлен серый GIF размера 1x1, и так как `width/height` правильные, то он растягивается, так что вместо изображения виден серый прямоугольник.
При этом, чтобы браузер загрузил изображение, нужно заменить значение `src` на то, которое находится в `realsrc`.
Если страница большая, то замена больших изображений на такие макеты существенно убыстряет полную загрузку страницы. Это особенно заметно в случае, когда на странице много анонсов новостей с картинками или изображений товаров, из которых многие находятся за пределами прокрутки.
Кроме того, для мобильных устройств JavaScript может подставлять URL уменьшенного варианта картинки.
Напишите код, который при прокрутке окна загружает ставшие видимыми изображения.
То есть, как только изображение попало в видимую часть документа -- в `src` нужно прописать правильный URL из `realsrc`.
Пример работы вы можете увидеть в `iframe` ниже, если прокрутите его:
Here's an example in `iframe`:
[iframe src="solution"]
Особенности реализации:
Scroll it to see images load "on-demand".
- При начальной загрузке некоторые изображения должны быть видны сразу, до прокрутки. Код должен это учитывать.
- Некоторые изображения могут быть обычными, без `realsrc`. Их код не должен трогать вообще.
- Также код не должен перегружать уже показанное изображение.
- Желательно предусмотреть загрузку изображений не только видимых сейчас, но и на страницу вперед и назад от текущего места.
Requirements:
- When the page loads, those images that are on-screen should load immediately, prior to any scrolling.
- Some images may be regular, without `data-src`. The code should not touch them.
- Once an image is loaded, it should not reload any more when scrolled in/out.
P.S. Горизонтальной прокрутки нет.
P.S. If you can, make a more advanced solution that would "preload" images that are one page below/after the current position.
P.P.S. Only vertical scroll is to be handled, no horizontal scrolling.

View file

@ -1,29 +1,39 @@
# Прокрутка: событие scroll
# Scrolling
Событие `onscroll` происходит, когда элемент прокручивается.
Scroll events allow to react on a page or element scrolling. There are quite a few good things we can do here.
В отличие от события `onwheel` (колесико мыши), его могут генерировать только прокручиваемые элементы или окно `window`. Но зато оно генерируется всегда, при любой прокрутке, не обязательно "мышиной".
For instance:
- Show/hide additional controls or information depending on where in the document the user is.
- Load more data when the user scrolls down till the end of the page.
[cut]
Например, следующая функция при прокрутке окна выдает количество прокрученных пикселей:
Here's a small function to show the current scroll:
```js autorun
window.onscroll = function() {
var scrolled = window.pageYOffset || document.documentElement.scrollTop;
document.getElementById('showScroll').innerHTML = scrolled + 'px';
window.addEventListener('scroll', function() {
document.getElementById('showScroll').innerHTML = pageYOffset + 'px';
}
```
В действии:
Текущая прокрутка = <b id="showScroll">прокрутите окно</b>
```online
In action:
Каких-либо особенностей события здесь нет, разве что для его использования нужно отлично представлять, как получить текущее значение прокрутки или прокрутить документ. Об этом мы говорили ранее, в главе <info:metrics>.
Current scroll = <b id="showScroll">scroll the window</b>
```
Некоторые области применения `onscroll`:
The `scroll` event works both on the `window` and on scrollable elements.
- Показ дополнительных элементов навигации при прокрутке.
- Подгрузка и инициализация элементов интерфейса, ставших видимыми после прокрутки.
## Prevent scrolling
Вашему вниманию предлагаются несколько задач, которые вы можете решить сами или посмотреть использование `onscroll` на их примере.
How do we make something unscrollable? We can't prevent scrolling by using `event.preventDefault()` in `onscroll` listener, because it triggers *after* the scroll has already happened.
But we can prevent scrolling by `event.preventDefault()` on an event that causes the scroll.
For instance:
- `wheel` event -- a mouse wheel roll (a "scrolling" touchpad action generates it too).
- `keydown` event for `key:pageUp` and `key:pageDown`.
Sometimes that may help. But there are more ways to scroll, so it's quite hard to handle all of them. So it's more reliable to use CSS to make something unscrollable, like `overflow` property.
Here are few tasks that you can solve or look through to see the applications on `onscroll`.

View file

@ -1,35 +0,0 @@
# Подсказка: выбор события
Нам нужно событие `keypress`, так как по скан-коду мы не отличим, например, клавишу `'2'` обычную и в верхнем регистре (символ `'@'`).
Нужно отменять действие по умолчанию (т.е. ввод), если введена не цифра.
# Решение
Нам нужно проверять *символы* при вводе, поэтому, будем использовать событие `keypress`.
Алгоритм такой: получаем символ и проверяем, является ли он цифрой. Если не является, то отменяем действие по умолчанию.
Кроме того, игнорируем специальные символы и нажатия со включенным `key:Ctrl`/`key:Alt`/`key:Cmd`.
Итак, вот решение:
```js
input.onkeypress = function(e) {
e = e || event;
if (e.ctrlKey || e.altKey || e.metaKey) return;
var chr = getChar(e);
// с null надо осторожно в неравенствах,
// т.к. например null >= '0' => true
// на всякий случай лучше вынести проверку chr == null отдельно
if (chr == null) return;
if (chr < '0' || chr > '9') {
return false;
}
}
```

View file

@ -1,49 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
Введите ваш возраст:
<input type="text">
<script>
document.getElementsByTagName('input')[0].onkeypress = function(e) {
e = e || event;
if (e.ctrlKey || e.altKey || e.metaKey) return;
var chr = getChar(e);
// с null надо осторожно в неравенствах, т.к. например null >= '0' => true!
// на всякий случай лучше вынести проверку chr == null отдельно
if (chr == null) return;
if (chr < '0' || chr > '9') {
return false;
}
}
function getChar(event) {
if (event.which == null) {
if (event.keyCode < 32) return null;
return String.fromCharCode(event.keyCode) // IE
}
if (event.which != 0 && event.charCode != 0) {
if (event.which < 32) return null;
return String.fromCharCode(event.which) // остальные
}
return null; // специальная клавиша
}
</script>
</body>
</html>

View file

@ -1,36 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
Введите ваш возраст:
<input type="text">
<script>
/* ваш код .. .*/
// вспомогательная функция, если понадобится
function getChar(event) {
if (event.which == null) {
if (event.keyCode < 32) return null;
return String.fromCharCode(event.keyCode) // IE
}
if (event.which != 0 && event.charCode != 0) {
if (event.which < 32) return null;
return String.fromCharCode(event.which) // остальные
}
return null; // специальная клавиша
}
</script>
</body>
</html>

View file

@ -1,13 +0,0 @@
importance: 5
---
# Поле только для цифр
При помощи событий клавиатуры сделайте так, чтобы в поле можно было вводить только цифры. Пример ниже.
[iframe border=1 src="solution"]
В поле должны нормально работать специальные клавиши `key:Delete`/`key:Backspace` и сочетания с `key:Ctrl`/`key:Alt`/`key:Cmd`.
P.S. Конечно, при помощи альтернативных способов ввода (например, вставки мышью), посетитель всё же может ввести что угодно.

View file

@ -1,8 +0,0 @@
# Ход решения
- Функция `runOnKeys` -- с переменным числом аргументов. Для их получения используйте `arguments`.
- Используйте два обработчика: `document.onkeydown` и `document.onkeyup`. Первый отмечает нажатие клавиши в объекте `pressed = {}`, устанавливая `pressed[keyCode] = true`, а второй -- удаляет это свойство. Если все клавиши с кодами из `arguments` нажаты -- запускайте `func`.
- Возникнет проблема с повторным нажатием сочетания клавиш после `alert`, решите её.
# Решение

View file

@ -1,55 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<body>
<p>Нажмите одновременно "Q" и "W" (в любой раскладке).</p>
<script>
function runOnKeys(func) {
var codes = [].slice.call(arguments, 1);
var pressed = {};
document.onkeydown = function(e) {
e = e || window.event;
pressed[e.keyCode] = true;
for (var i = 0; i < codes.length; i++) { // проверить, все ли клавиши нажаты
if (!pressed[codes[i]]) {
return;
}
}
// во время показа alert, если посетитель отпустит клавиши - не возникнет keyup
// при этом JavaScript "пропустит" факт отпускания клавиш, а pressed[keyCode] останется true
// чтобы избежать "залипания" клавиши -- обнуляем статус всех клавиш, пусть нажимает всё заново
pressed = {};
func();
};
document.onkeyup = function(e) {
e = e || window.event;
delete pressed[e.keyCode];
};
}
runOnKeys(
function() {
alert("Привет!")
},
"Q".charCodeAt(0),
"W".charCodeAt(0)
);
</script>
</body>
</html>

View file

@ -1,19 +0,0 @@
importance: 3
---
# Отследить одновременное нажатие
Создайте функцию `runOnKeys(func, code1, code2, ... code_n)`, которая запускает `func` при одновременном нажатии клавиш со скан-кодами `code1`, `code2`, ..., `code_n`.
Например, код ниже выведет `alert` при одновременном нажатии клавиш `"Q"` и `"W"` (в любом регистре, в любой раскладке)
```js no-beautify
runOnKeys(
function() { alert("Привет!") },
"Q".charCodeAt(0),
"W".charCodeAt(0)
);
```
[demo src="solution"]

View file

@ -1,315 +0,0 @@
# Клавиатура: keyup, keydown, keypress
Здесь мы рассмотрим основные "клавиатурные" события и работу с ними.
[cut]
## Тестовый стенд [#keyboard-test-stand]
Для того, чтобы лучше понять, как работают события клавиатуры, можно использовать тестовый стенд.
Попробуйте различные варианты нажатия клавиш в текстовом поле.
[codetabs src="keyboard-dump" height=480]
По мере чтения, если возникнут вопросы -- возвращайтесь к этому стенду.
## События keydown и keyup
События `keydown/keyup` происходят при нажатии/отпускании клавиши и позволяют получить её *скан-код* в свойстве `keyCode`.
Скан-код клавиши одинаков в любой раскладке и в любом регистре. Например, клавиша `key:z` может означать символ `"z"`, `"Z"` или `"я"`, `"Я"` в русской раскладке, но её *скан-код* будет всегда одинаков: `90`.
````online
В действии:
```html
<input onkeydown="this.nextSibling.innerHTML = event.keyCode"> <b></b>
```
<input size="40" placeholder="Нажмите клавишу, скан-код будет справа" onkeydown="this.nextElementSibling.innerHTML = event.keyCode"> <b></b>
````
### Скан-коды
Для буквенно-цифровых клавиш есть очень простое правило: скан-код будет равен коду соответствующей заглавной английской буквы/цифры.
Например, при нажатии клавиши `key:S` (не важно, каков регистр и раскладка) её скан-код будет равен `"S".charCodeAt(0)`.
Для других символов, в частности, знаков пунктуации, есть таблица кодов, которую можно взять, например, из статьи Джона Уолтера: <a href="http://unixpapa.com/js/key.html">JavaScript Madness: Keyboard Events</a>, или же можно нажать на нужную клавишу на [тестовом стенде](#keyboard-test-stand) и получить код.
Когда-то в этих кодах была масса кросс-браузерных несовместимостей. Сейчас всё проще -- таблицы кодов в различных браузерах почти полностью совпадают. Но некоторые несовместимости, всё же, остались. Вы можете увидеть их в таблице ниже. Слева -- клавиша с символом, а справа -- скан-коды в различных браузерах.
Таблица несовместимостей:
| Клавиша | Firefox | Остальные браузеры |
|-----------|---------|--------------------|
| `key:;` | 59 | 186 |
| `key:=` | 107 | 187 |
| `key:-` | 109 | 188 |
Остальные коды одинаковы, код для нужного символа будет в тестовом стенде.
## Событие keypress
Событие `keypress` возникает сразу после `keydown`, если нажата *символьная* клавиша, т.е. нажатие приводит к появлению символа.
Любые буквы, цифры генерируют `keypress`. Управляющие клавиши, такие как `key:Ctrl`, `key:Shift`, `key:F1`, `key:F2`.. -- `keypress` не генерируют.
Событие `keypress` позволяет получить *код символа*. В отличие от скан-кода, он специфичен именно для символа и различен для `"z"` и `"я"`.
Код символа хранится в свойствах: `charCode` и `which`. Здесь скрывается целое "гнездо" кросс-браузерных несовместимостей, разбираться с которыми нет никакого смысла -- запомнить сложно, а на практике нужна лишь одна "правильная" функция, позволяющая получить код везде.
### Получение символа в keypress [#getChar]
Кросс-браузерная функция для получения символа из события `keypress`:
```js
// event.type должен быть keypress
function getChar(event) {
if (event.which == null) { // IE
if (event.keyCode < 32) return null; // спец. символ
return String.fromCharCode(event.keyCode)
}
if (event.which != 0 && event.charCode != 0) { // все кроме IE
if (event.which < 32) return null; // спец. символ
return String.fromCharCode(event.which); // остальные
}
return null; // спец. символ
}
```
Для общей информации -- вот основные браузерные особенности, учтённые в `getChar(event)`:
1. Во всех браузерах, кроме IE, у события `keypress` есть свойство `charCode`, которое содержит код символа.
2. Браузер IE для `keypress` не устанавливает `charCode`, а вместо этого он записывает код символа в `keyCode``keydown/keyup` там хранится скан-код).
3. Также в функции выше используется проверка `if(event.which!=0)`, а не более короткая `if(event.which)`. Это не случайно! При `event.which=null` первое сравнение даст `true`, а второе -- `false`.
````online
В действии:
```html
<input onkeypress="this.nextSibling.innerHTML = getChar(event)+''"><b></b>
```
<input size="40" placeholder="Наберите символ, он будет справа" onkeypress="this.nextElementSibling.innerHTML = getChar(event)+''"> <b></b>
````
````warn header="Неправильный `getChar`"
В сети вы можете найти другую функцию того же назначения:
```js
function getChar(event) {
return String.fromCharCode(event.keyCode || event.charCode);
}
```
Она работает неверно для многих специальных клавиш, потому что не фильтрует их. Например, она возвращает символ амперсанда `"&"`, когда нажата клавиша 'Стрелка Вверх'. Лучше использовать ту, что приведена выше.
````
Как и у других событий, связанных с пользовательским вводом, поддерживаются свойства `shiftKey`, `ctrlKey`, `altKey` и `metaKey`.
Они установлены в `true`, если нажаты клавиши-модификаторы -- соответственно, `key:Shift`, `key:Ctrl`, `key:Alt` и `key:Cmd` для Mac.
## Отмена пользовательского ввода
Появление символа можно предотвратить, если отменить действие браузера на `keydown/keypress`:
```html
Попробуйте что-нибудь ввести в этих полях:
<input *!*onkeydown="return false"*/!* type="text" size="30">
<input *!*onkeypress="return false"*/!* type="text" size="30">
```
```online
Попробуйте что-нибудь ввести в этих полях (не получится):
<input onkeydown="return false" type="text" size="30">
<input onkeypress="return false" type="text" size="30">
```
При тестировании на стенде вы можете заметить, что отмена действия браузера при `keydown` также предотвращает само событие `keypress`.
```warn header="При `keydown/keypress` значение ещё старое"
На момент срабатывания `keydown/keypress` *клавиша ещё не обработана браузером*.
Поэтому в обработчике значение `input.value` -- старое, т.е. до ввода. Это можно увидеть в примере ниже. Вводите символы `abcd..`, а справа будет текущее `input.value`: `abc..`
<input onkeydown="this.nextSibling.innerHTML=this.value" type="text" placeholder="Вводите символы"><b></b>
А что, если мы хотим обработать `input.value` именно после ввода? Самое простое решение -- использовать событие `keyup`, либо запланировать обработчик через `setTimeout(..,0)`.
```
### Отмена любых действий
Отменять можно не только символ, а любое действие клавиш.
Например:
- При отмене `key:Backspace` -- символ не удалится.
- При отмене `key:PageDown` -- страница не прокрутится.
- При отмене `key:Tab` -- курсор не перейдёт на следующее поле.
Конечно же, есть действия, которые в принципе нельзя отменить, в первую очередь -- те, которые происходят на уровне операционной системы. Комбинация Alt+F4 инициирует закрытие браузера в Windows, что бы мы ни делали в JavaScript.
### Демо: перевод символа в верхний регистр
В примере ниже действие браузера отменяется с помощью `return false`, а вместо него в `input` добавляется значение в верхнем регистре:
```html
<input id="only-upper" type="text" size="2">
<script>
document.getElementById('only-upper').onkeypress = function(e) {
// спец. сочетание - не обрабатываем
if (e.ctrlKey || e.altKey || e.metaKey) return;
var char = getChar(e);
if (!char) return; // спец. символ - не обрабатываем
this.value = char.toUpperCase();
return false;
};
</script>
```
```online
В действии: <input id="only-upper" type="text" size="2">
<script>
document.getElementById('only-upper').onkeypress = function(e) {
var char = getChar(e);
// спец. сочетание - не обрабатываем
if (e.ctrlKey || e.altKey || e.metaKey) return;
if (!char) return; // спец. символ - не обрабатываем
this.value = char.toUpperCase();
return false;
}
</script>
```
## Несовместимости [#keyboard-events-order]
Некоторые несовместимости в порядке срабатывания клавиатурных событий (когда что) ещё существуют.
Стоит иметь в виду три основных категории клавиш, работа с которыми отличается.
<table>
<thead>
<tr>
<th>Категория</th>
<th>События</th>
<th>Описание</th>
</tr>
</thead>
<tbody>
<tr>
<td>Печатные клавиши <code>S</code> <code>1</code> <code>,</code></td>
<td><code>keydown</code><br>
<code>keypress</code><br>
<code>keyup</code></td>
<td>Нажатие вызывает <code>keydown</code> и <code>keypress</code>.
Когда клавишу отпускают, срабатывает <code>keyup</code>.
<p>Исключение CapsLock под MacOS, с ним есть проблемы:</p>
<ul>
<li>В Safari/Chrome/Opera: при включении только <code>keydown</code>, при отключении только <code>keyup</code>.</li>
<li>В Firefox: при включении и отключении только <code>keydown</code>.</li>
</ul>
</td>
<tr>
<td>Специальные клавиши <code>Alt</code> <code>Esc</code> <code></code></td>
<td><code>keydown</code>
<code>keyup</code></td>
<td>Нажатие вызывает <code>keydown</code>.
Когда клавишу отпускают, срабатывает <code>keyup</code>.
<p>Некоторые браузеры могут дополнительно генерировать и <code>keypress</code>, например IE для <code>Esc</code>.</p>
<p>На практике это не доставляет проблем, так как для специальных клавиш мы всегда используем <code>keydown/keyup</code>.</p>
</td>
</tr>
<tr>
<td>Сочетания с печатной клавишей
<code>Alt+E</code><br>
<code>Ctrl+У</code><br>
<code>Cmd+1</code><br>
</td>
<td><code>keydown</code><br>
<code>keypress?</code><br>
<code>keyup</code></td>
<td>
<p>Браузеры под Windows не генерируют <code>keypress</code>, браузеры под MacOS генерируют.</p>
<p>Кроме того, если сочетание вызвало браузерное действие или диалог ("Сохранить файл", "Открыть" и т.п., ряд диалогов можно отменить при <code>keydown</code>), то может быть только <code>keydown</code>.</p>
</td>
</tr>
</tbody>
</table>
Общий вывод можно сделать такой:
- Обычные символы работают везде корректно.
- CapsLock под MacOS ведёт себя плохо, не стоит ставить на него обработчики вообще.
- Для других специальных клавиш и сочетаний с ними следует использовать только `keydown`.
## Автоповтор
При долгом нажатии клавиши возникает *автоповтор*. По стандарту, должны генерироваться многократные события `keydown (+keypress)`, и вдобавок стоять свойство [repeat=true](http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent-repeat) у объекта события.
То есть поток событий должен быть такой:
```
keydown
keypress
keydown
keypress
..повторяется, пока клавиша не отжата...
keyup
```
Однако в реальности на это полагаться нельзя. На момент написания статьи, под Firefox(Linux) генерируется и `keyup`:
```
keydown
keypress
keyup
keydown
keypress
keyup
..повторяется, пока клавиша не отжата...
keyup
```
...А Chrome под MacOS не генерирует `keypress`. В общем, "зоопарк".
Полагаться можно только на `keydown` при каждом автонажатии и `keyup` по отпусканию клавиши.
## Итого
Ряд рецептов по итогу этой главы:
1. Для реализации горячих клавиш, включая сочетания -- используем `keydown`. Скан-код будет в `keyCode`, почти все скан-коды кросс-браузерны, кроме нескольких пунктуационных, перечисленных в таблице выше.
2. Если нужен именно символ -- используем `keypress`. При этом функция `getChar` позволит получить символ и отфильтровать лишние срабатывания. Гарантированно получать символ можно только при нажатии обычных клавиш, если речь о сочетаниях с модификаторами, то `keypress` не всегда генерируется.
3. Ловля CapsLock глючит под MacOS. Её можно организовать при помощи проверки `navigator.userAgent` и `navigator.platform`, а лучше вообще не трогать эту клавишу.
Распространённая ошибка -- использовать события клавиатуры для работы с полями ввода в формах.
Это нежелательно. События клавиатуры предназначены именно для работы с клавиатурой. Да, их можно использовать для проверки ввода в `<input>`, но будут недочёты. Например, текст может быть вставлен мышкой, при помощи правого клика и меню, без единого нажатия клавиши. И как нам помогут события клавиатуры?
Некоторые мобильные устройства также не генерируют `keypress/keydown`, а сразу вставляют текст в поле. Обработать ввод на них при помощи клавиатурных событий нельзя.
Далее мы разберём [события для элементов форм](/events-change), которые позволяют работать с вводом в формы правильно.
Их можно использовать как отдельно от событий клавиатуры, так и вместе с ними.

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After