This commit is contained in:
Ilya Kantor 2015-11-18 16:15:29 +03:00
parent 4bca225593
commit 547854a151
1655 changed files with 847 additions and 89231 deletions

View file

@ -1,69 +0,0 @@
# Окружение: DOM, BOM и JS
Сам по себе язык JavaScript не предусматривает работы с браузером.
Он вообще не знает про HTML. Но позволяет легко расширять себя новыми функциями и объектами.
[cut]
На рисунке ниже схематически отображена структура, которая получается если посмотреть на совокупность браузерных объектов с "высоты птичьего полёта".
<img src="windowObjects.png">
Как видно из рисунка, на вершине стоит `window`.
У этого объекта двоякая позиция -- он с одной стороны является глобальным объектом в JavaScript, с другой -- содержит свойства и методы для управления окном браузера, открытия новых окон, например:
```js
//+ run
// открыть новое окно/вкладку с URL http://ya.ru
window.open('http://ya.ru');
```
## Объектная модель документа (DOM)
Глобальный объект `document` даёт возможность взаимодействовать с содержимым страницы.
Пример использования:
```js
//+ run
document.body.style.background = 'red';
alert( 'Элемент BODY стал красным, а сейчас обратно вернётся' );
document.body.style.background = '';
```
Он и громадное количество его свойств и методов описаны в [стандарте W3C DOM](http://www.w3.org/DOM/DOMTR).
По историческим причинам когда-то появилась первая версия стандарта DOM Level 1, затем придумали ещё свойства и методы, и появился DOM Level 2, на текущий момент поверх них добавили ещё DOM Level 3 и готовится DOM 4.
Современные браузеры также поддерживают некоторые возможности, которые не вошли в стандарты, но де-факто существуют давным-давно и отказываться от них никто не хочет. Их условно называют "DOM Level 0".
Также информацию по работе с элементами страницы можно найти в стандарте [HTML 5](http://www.w3.org/TR/html5/Overview.html).
Мы подробно ознакомимся с DOM далее в этой части учебника.
## Объектная модель браузера (BOM)
BOM -- это объекты для работы с чем угодно, кроме документа.
Например:
<ul>
<li>Объект [navigator](https://developer.mozilla.org/en/DOM/window.navigator) содержит общую информацию о браузере и операционной системе. Особенно примечательны два свойства: `navigator.userAgent` -- содержит информацию о браузере и `navigator.platform` -- содержит информацию о платформе, позволяет различать Windows/Linux/Mac и т.п.</li>
<li>Объект [location](https://developer.mozilla.org/en-US/docs/Web/API/Window.location) содержит информацию о текущем URL страницы и позволяет перенаправить посетителя на новый URL.</li>
<li>Функции `alert/confirm/prompt` -- тоже входят в BOM.</li>
</ul>
Пример использования:
```js
//+ run
alert( location.href ); // выведет текущий адрес
```
Большинство возможностей BOM стандартизированы в [HTML 5](http://www.w3.org/TR/html5/Overview.html), хотя различные браузеры и предоставляют зачастую что-то своё, в дополнение к стандарту.
## Итого
Итак, у нас есть DOM, BOM и, собственно, язык JavaScript, который даёт возможность управлять всем этим.
Далее мы приступим к изучению DOM, поскольку именно документ занимает центральную роль в организации интерфейса, и работа с ним -- сложнее всего.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

View file

@ -1,170 +0,0 @@
# Методы contains и compareDocumentPosition
Если есть два элемента, то иногда бывает нужно понять, лежит ли один из них выше другого, то есть является ли его предком.
Обычные поисковые методы здесь не дают ответа, но есть два специальных. Они используются редко, но когда подобная задача встаёт, то знание метода может сэкономить много строк кода.
[cut]
## Метод contains для проверки на вложенность
Синтаксис:
```js
var result = parent.contains(child);
```
Возвращает `true`, если `parent` содержит `child` или `parent == child`.
## Метод compareDocumentPosition для порядка узлов
Бывает, что у нас есть два элемента, к примеру, `<li>` в списке, и нужно понять, какой из них выше другого.
Метод `compareDocumentPosition` -- более мощный, чем `contains`, он предоставляет одновременно информацию и о содержании и об относительном порядке элементов.
Синтаксис:
```js
var result = nodeA.compareDocumentPosition(nodeB);
```
Возвращаемое значение -- битовая маска (см. [](/bitwise-operators)), биты в которой означают следующее:
<table>
<thead>
<tr>
<th>Биты</th>
<th>Число</th>
<th>Значение</th>
</tr>
</thead>
<tbody>
<tr><td>000000</td><td>0</td><td>`nodeA` и `nodeB` -- один и тот же узел</td></tr>
<tr><td>000001</td><td>1</td><td>Узлы в разных документах (или один из них не в документе)</td></tr>
<tr><td>000010</td><td>2</td><td>`nodeB` предшествует `nodeA` (в порядке обхода документа)</td></tr>
<tr><td>000100</td><td>4</td><td>`nodeA` предшествует `nodeB`</td></tr>
<tr><td>001000</td><td>8</td><td>`nodeB` содержит `nodeA`</td></tr>
<tr><td>010000</td><td>16</td><td>`nodeA` содержит `nodeB`</td></tr>
<tr><td>100000</td><td>32</td><td>Зарезервировано для браузера</td></tr>
</tbody>
</table>
Понятие "предшествует" -- означает не только "предыдущий сосед при общем родителе", но и имеет более общий смысл: "раньше встречается в порядке [прямого обхода](http://algolist.manual.ru/ds/walk.php) дерева документа.
Могут быть и сочетания битов. Примеры реальных значений:
```html
<!--+ run -->
<p>...</p>
<ul>
<li>1.1</li>
</ul>
<script>
var p = document.body.children[0];
var ul = document.body.children[1];
var li = ul.children[0];
// 1. <ul> находится после <p>
alert( ul.compareDocumentPosition(p) ); // 2 = 10
// 2. <p> находится до <ul>
alert( p.compareDocumentPosition(ul) ); // 4 = 100
// 3. <ul> родитель <li>
alert( ul.compareDocumentPosition(li) ); // 20 = 10100
// 4. <ul> потомок <body>
alert( ul.compareDocumentPosition(document.body) ); // 10 = 1010
</script>
```
Более подробно:
<ol>
<li>Узлы не вложены один в другой, поэтому стоит только бит "предшествования", отсюда `10`.</li>
<li>То же самое, но обратный порядок узлов, поэтому `100`.</li>
<li>Здесь стоят сразу два бита: `10100` означает, что `ul` одновременно содержит `li` и является его предшественником, то есть при прямом обходе дерева документа сначала встречается `ul`, а потом `li`.</li>
<li>Аналогично предыдущему, `1010` означает, что `document.body` содержит `ul` и предшествует ему.</li>
</ol>
[smart header="Перевод в двоичную систему"]
Самый простой способ самостоятельно посмотреть, как число выглядит в 2-ной системе -- вызвать для него `toString(2)`, например:
```js
//+ run
var x = 20;
alert( x.toString(2) ); // "10100"
```
Или так:
```js
//+ run
alert( 20..toString(2) );
```
Здесь после `20` две точки, так как если одна, то JS подумает, что после неё десятичная часть -- будет ошибка.
[/smart]
Проверить конкретное условие, например, "`nodeA` содержит `nodeB`", можно при помощи битовых операций, в данном случае: `nodeA.compareDocumentPosition(nodeB) & 16`, например:
```html
<!--+ run -->
<ul>
<li>1</li>
</ul>
<script>
var body = document.body;
var li = document.body.children[0].children[0];
*!*
if (body.compareDocumentPosition(li) & 16) {
alert( body + ' содержит ' + li );
}
*/!*
</script>
```
Более подробно о битовых масках: [](/bitwise-operators).
## Поддержка в IE8-
В IE8- поддерживаются свои, нестандартные, метод и свойство:
<dl>
<dt>[nodeA.contains(nodeB)](http://msdn.microsoft.com/en-us/library/ms536377.aspx)</dt>
<dd>Результат: `true`, если `nodeA` содержит `nodeB`, а также в том случае, если `nodeA == nodeB`.</dd>
<dt>[node.sourceIndex](http://msdn.microsoft.com/en-us/library/ms534635.aspx)</dt>
<dd>Номер элемента `node` в порядке прямого обхода дерева. Только для узлов-элементов.</dd>
</dl>
На их основе можно написать полифилл для `compareDocumentPosition`:
```js
// код с http://compatibility.shwups-cms.ch/en/polyfills/?&id=82
(function() {
var el = document.documentElement;
if (!el.compareDocumentPosition && el.sourceIndex !== undefined) {
Element.prototype.compareDocumentPosition = function(other) {
return (this != other && this.contains(other) && 16) +
(this != other && other.contains(this) && 8) +
(this.sourceIndex >= 0 && other.sourceIndex >= 0 ?
(this.sourceIndex < other.sourceIndex && 4) +
(this.sourceIndex > other.sourceIndex && 2) : 1
) + 0;
}
}
}());
```
С этим полифиллом метод доступен для элементов во всех браузерах.
## Итого
<ul>
<li>Для проверки, является ли один узел предком другого, достаточно метода `nodeA.contains(nodeB)`.</li>
<li>Для расширенной проверки на предшествование есть метод `compareDocumentPosition`.</li>
<li>Для IE8 нужен полифилл для `compareDocumentPosition`.</li>
</ul>

View file

@ -1,31 +0,0 @@
Результат выполнения может быть разный: `innerHTML` вставит именно HTML, а `createTextNode` интерпретирует теги как текст.
Запустите следующие примеры, чтобы увидеть разницу:
<ul>
<li>`createTextNode` создает текст <code>'&lt;b&gt;текст&lt;/b&gt;'</code>:
```html
<!--+ run height=50 -->
<div id="elem"></div>
<script>
var text = '<b>текст</b>';
elem.appendChild(document.createTextNode(text));
</script>
```
</li>
<li>`innerHTML` присваивает HTML <code>&lt;b&gt;текст&lt;/b&gt;</code>:
```html
<!--+ run height=50 -->
<div id="elem"></div>
<script>
var text = '<b>текст</b>';
elem.innerHTML = text;
</script>
```
</li>
</ul>

View file

@ -1,21 +0,0 @@
# createTextNode vs innerHTML
[importance 5]
Есть *пустой* узел DOM `elem`.
**Одинаковый ли результат дадут эти скрипты?**
Первый:
```js
elem.appendChild(document.createTextNode(text));
```
Второй:
```js
elem.innerHTML = text;
```
Если нет -- дайте пример значения `text`, для которого результат разный.

View file

@ -1,56 +0,0 @@
Для начала, придумаем подходящую HTML/CSS-структуру.
Здесь каждый компонент времени удобно поместить в соответствующий `SPAN`:
```html
<div id="clock">
<span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span>
</div>
```
Каждый `SPAN` раскрашивается при помощи CSS.
Жизнь часам будет обеспечивать функция `update`, вызываемая каждую секунду:
```js
function update() {
var clock = document.getElementById('clock');
*!*
var date = new Date(); // (*)
*/!*
var hours = date.getHours();
if (hours < 10) hours = '0' + hours;
clock.children[0].innerHTML = hours;
var minutes = date.getMinutes();
if (minutes < 10) minutes = '0' + minutes;
clock.children[1].innerHTML = minutes;
var seconds = date.getSeconds();
if (seconds < 10) seconds = '0' + seconds;
clock.children[2].innerHTML = seconds;
}
```
В строке `(*)` каждый раз мы получаем текущую дату. Мы должны это сделать, несмотря на то, что, казалось бы, могли бы просто увеличивать счетчик каждую секунду.
На самом деле мы не можем опираться на счетчик для вычисления даты, т.к. `setInterval` не гарантирует точную задержку. Если в другом участке кода будет вызван `alert`, то часы остановятся, как и любые счетчики.
Функция `clockStart` для запуска часов:
```js
function clockStart() { // запустить часы
setInterval(update, 1000);
update(); // (*)
}
function clockStop() {
clearInterval(timerId);
timerId = null;
}
```
Обратите внимание, что вызов `update` не только запланирован, но и тут же производится тут же в строке `(*)`. Иначе посетителю пришлось бы ждать до первого выполнения `setInterval`, то есть целую секунду.

View file

@ -1,57 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
.hour {
color: red
}
.min {
color: green
}
.sec {
color: blue
}
</style>
</head>
<body>
<div id="clock">
<span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span>
</div>
<script>
var timerId;
function update() {
var clock = document.getElementById('clock');
var date = new Date();
var hours = date.getHours();
if (hours < 10) hours = '0' + hours;
clock.children[0].innerHTML = hours;
var minutes = date.getMinutes();
if (minutes < 10) minutes = '0' + minutes;
clock.children[1].innerHTML = minutes;
var seconds = date.getSeconds();
if (seconds < 10) seconds = '0' + seconds;
clock.children[2].innerHTML = seconds;
}
function clockStart() {
setInterval(update, 1000);
update(); // <-- начать тут же, не ждать 1 секунду пока setInterval сработает
}
clockStart();
</script>
</body>
</html>

View file

@ -1,13 +0,0 @@
<!DOCTYPE HTML>
<html>
<body>
<input type="button" onclick="clockStart()" value="Start">
<input type="button" onclick="clockStop()" value="Stop">
</body>
</html>

View file

@ -1,8 +0,0 @@
# Часики с использованием "setInterval"
[importance 4]
Создайте цветные часики как в примере ниже:
[iframe src="solution" height=100]

View file

@ -1,23 +0,0 @@
Родителя `parentNode` можно получить из `elem`.
Вот так выглядит решение:
```html
//+ run
<div>Это</div>
<div>Все</div>
<div>Элементы DOM</div>
<script>
if (!Element.prototype.remove) {
Element.prototype.remove = function remove() {
if (this.parentNode) {
this.parentNode.removeChild(this);
}
};
}
var elem = document.body.children[0];
elem.remove();
</script>
```

View file

@ -1,28 +0,0 @@
# Удаление элементов
[importance 5]
Напишите полифилл для метода `remove` для старых браузеров.
Вызов `elem.remove()`:
<ul>
<li>Если у `elem` нет родителя -- ничего не делает.</li>
<li>Если есть -- удаляет элемент из родителя.</li>
</ul>
```html
<div>Это</div>
<div>Все</div>
<div>Элементы DOM</div>
<script>
/* ваш код полифилла */
var elem = document.body.children[0];
*!*
elem.remove(); // <-- вызов должен удалить элемент
*/!*
</script>
```

View file

@ -1,29 +0,0 @@
Для того, чтобы добавить элемент *после* `refElem`, мы можем, используя `insertBefore`, вставить его *перед* `refElem.nextSibling`.
Но что если `nextSibling` нет? Это означает, что `refElem` является последним потомком своего родителя и можем использовать `appendChild`.
Код:
```js
function insertAfter(elem, refElem) {
var parent = refElem.parentNode;
var next = refElem.nextSibling;
if (next) {
return parent.insertBefore(elem, next);
} else {
return parent.appendChild(elem);
}
}
```
Но код может быть гораздо короче, если вспомнить, что `insertBefore` со вторым аргументом null работает как `appendChild`:
```js
function insertAfter(elem, refElem) {
return refElem.parentNode.insertBefore(elem, refElem.nextSibling);
}
```
Если нет `nextSibling`, то второй аргумент `insertBefore` становится `null` и тогда `insertBefore(elem, null)` осуществит вставку в конец, как и требуется.
В решении нет проверки на существование `refElem.parentNode`, поскольку вставка после элемента без родителя -- уже ошибка, пусть она возникнет в функции, это нормально.

View file

@ -1,26 +0,0 @@
# insertAfter
[importance 5]
Напишите функцию `insertAfter(elem, refElem)`, которая добавит `elem` после узла `refElem`.
```html
<div>Это</div>
<div>Элементы</div>
<script>
var elem = document.createElement('div');
elem.innerHTML = '<b>Новый элемент</b>';
function insertAfter(elem, refElem) { /* ваш код */ }
var body = document.body;
// вставить elem после первого элемента
insertAfter(elem, body.firstChild); // <--- должно работать
// вставить elem за последним элементом
insertAfter(elem, body.lastChild); // <--- должно работать
</script>
```

View file

@ -1,54 +0,0 @@
# Неправильное решение
Для начала рассмотрим забавный пример того, как делать *не надо*:
```js
function removeChildren(elem) {
for (var k = 0; k < elem.childNodes.length; k++) {
elem.removeChild(elem.childNodes[k]);
}
}
```
Если вы попробуете это на практике, то увидите, то это не сработает.
Не сработает потому, что коллекция `childNodes` всегда начинается с индекса 0 и автоматически обновляется, когда первый потомок удален(т.е. тот, что был вторым, станет первым). А переменная `k` в цикле всё время увеличивается, поэтому такой цикл пропустит половину узлов.
# Решение через DOM
Правильное решение:
```js
function removeChildren(elem) {
while (elem.lastChild) {
elem.removeChild(elem.lastChild);
}
}
```
# Альтернатива через innerHTML
Можно и просто обнулить содержимое через `innerHTML`:
```js
function removeChildren(elem) {
elem.innerHTML = '';
}
```
Это не будет работать в IE8- для таблиц, так как на большинстве табличных элементов (кроме ячеек `TH/TD`) в старых IE запрещено менять `innerHTML`.
Впрочем, можно завернуть `innerHTML` в `try/catch`:
```js
function removeChildren(elem) {
try {
elem.innerHTML = '';
} catch (e) {
while (elem.firstChild) {
elem.removeChild(elem.firstChild);
}
}
}
```

View file

@ -1,29 +0,0 @@
# removeChildren
[importance 5]
Напишите функцию `removeChildren`, которая удаляет всех потомков элемента.
```html
<table id="table">
<tr>
<td>Это</td>
<td>Все</td>
<td>Элементы DOM</td>
</tr>
</table>
<ol id="ol">
<li>Вася</li>
<li>Петя</li>
<li>Маша</li>
<li>Даша</li>
</ol>
<script>
function removeChildren(elem) { /* ваш код */ }
removeChildren(table); // очищает таблицу
removeChildren(ol); // очищает список
</script>
```

View file

@ -1,5 +0,0 @@
HTML в задаче некорректен. В этом всё дело. И вопрос легко решится, если открыть отладчик.
В нём видно, что браузер поместил текст `aaa` *перед* таблицей. Поэтому он и остался в документе.
Вообще, в стандарте HTML5 описано, как браузеру обрабатывать некорректный HTML, так что такое действие браузера является правильным.

View file

@ -1,25 +0,0 @@
# Почему остаётся "ааа" ?
[importance 1]
Запустите этот пример. Почему вызов `removeChild` не удалил текст `"aaa"`?
```html
<!--+ height=100 run -->
<table>
aaa
<tr>
<td>Test</td>
</tr>
</table>
<script>
var table = document.body.children[0];
alert( table ); // таблица, пока всё правильно
document.body.removeChild(table);
// почему в документе остался текст?
</script>
```

View file

@ -1,4 +0,0 @@
Делаем цикл, пока посетитель что-то вводит -- добавляет `<li>`.
Содержимое в `<li>` присваиваем через `document.createTextNode`, чтобы правильно работали &lt;, &gt; и т.д.

View file

@ -1,30 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h1>Создание списка</h1>
<script>
var ul = document.createElement('ul');
document.body.appendChild(ul);
while (true) {
var data = prompt("Введите текст для пункта списка", "");
if (!data) {
break;
}
var li = document.createElement('li');
li.appendChild(document.createTextNode(data));
ul.appendChild(li);
}
</script>
</body>
</html>

View file

@ -1,18 +0,0 @@
# Создать список
[importance 4]
Напишите интерфейс для создания списка.
Для каждого пункта:
<ol>
<li>Запрашивайте содержимое пункта у пользователя с помощью `prompt`.</li>
<li>Создавайте пункт и добавляйте его к `UL`.</li>
<li>Процесс прерывается, когда пользователь нажимает ESC или вводит пустую строку.</li>
</ol>
**Все элементы должны создаваться динамически.**
Если посетитель вводит теги -- пусть в списке они показываются как обычный текст.
[demo src="solution"]

View file

@ -1,70 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="container"></div>
<script>
var data = {
"Рыбы": {
"Форель": {},
"Щука": {}
},
"Деревья": {
"Хвойные": {
"Лиственница": {},
"Ель": {}
},
"Цветковые": {
"Берёза": {},
"Тополь": {}
}
}
};
function createTree(container, obj) {
container.appendChild(createTreeDom(obj));
}
function createTreeDom(obj) {
// если нет детей, то рекурсивный вызов ничего не возвращает
// так что вложенный UL не будет создан
if (isObjectEmpty(obj)) return;
var ul = document.createElement('ul');
for (var key in obj) {
var li = document.createElement('li');
li.innerHTML = key;
var childrenUl = createTreeDom(obj[key]);
if (childrenUl) li.appendChild(childrenUl);
ul.appendChild(li);
}
return ul;
}
function isObjectEmpty(obj) {
for (var key in obj) {
return false;
}
return true;
}
var container = document.getElementById('container');
createTree(container, data);
</script>
</body>
</html>

View file

@ -1,6 +0,0 @@
Решения через рекурсию.
<ol>
<li>[edit src="solution"]Через innerHTML[/edit].</li>
<li>[edit src="build-tree-dom"]Через DOM[/edit].</li>
</ol>

View file

@ -1,52 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="container"></div>
<script>
var data = {
"Рыбы": {
"Форель": {},
"Щука": {}
},
"Деревья": {
"Хвойные": {
"Лиственница": {},
"Ель": {}
},
"Цветковые": {
"Берёза": {},
"Тополь": {}
}
}
};
function createTree(container, obj) {
container.innerHTML = createTreeText(obj);
}
function createTreeText(obj) { // отдельная рекурсивная функция
var li = '';
for (var key in obj) {
li += '<li>' + key + createTreeText(obj[key]) + '</li>';
}
if (li) {
var ul = '<ul>' + li + '</ul>'
}
return ul || '';
}
var container = document.getElementById('container');
createTree(container, data);
</script>
</body>
</html>

View file

@ -1,67 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="tree"></div>
<!-- Результат должен быть таким:
<div id="tree">
<ul>
<li>Рыбы
<ul>
<li>Форель</li>
<li>Щука</li>
</ul>
</li>
<li>Деревья
<ul>
<li>Хвойные
<ul>
<li>Лиственница</li>
<li>Ель</li>
</ul>
</li>
<li>Цветковые
<ul>
<li>Берёза</li>
<li>Тополь</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
-->
<script>
var data = {
"Рыбы": {
"Форель": {},
"Щука": {}
},
"Деревья": {
"Хвойные": {
"Лиственница": {},
"Ель": {}
},
"Цветковые": {
"Берёза": {},
"Тополь": {}
}
}
};
/* ваш код */
createTree(document.getElementById('tree'), data);
</script>
</body>
</html>

View file

@ -1,50 +0,0 @@
# Создайте дерево из объекта
[importance 5]
Напишите функцию, которая создаёт вложенный список `UL/LI` (дерево) из объекта.
Например:
```js
var data = {
"Рыбы": {
"Форель": {},
"Щука": {}
},
"Деревья": {
"Хвойные": {
"Лиственница": {},
"Ель": {}
},
"Цветковые": {
"Берёза": {},
"Тополь": {}
}
}
};
```
Синтаксис:
```js
var container = document.getElementById('container');
*!*
createTree(container, data); // создаёт
*/!*
```
Результат (дерево):
[iframe border=1 src="solution"]
Выберите один из двух способов решения этой задачи:
<ol>
<li>Создать строку, а затем присвоить через `container.innerHTML`.</li>
<li>Создавать узлы через методы DOM.</li>
</ol>
Если получится -- сделайте оба.
P.S. Желательно, чтобы в дереве не было лишних элементов, в частности -- пустых `<ul></ul>` на нижнем уровне.

View file

@ -1,8 +0,0 @@
# Подсказки
<ol>
<li>Получить количество вложенных узлов можно через `elem.getElementsByTagName('*').length`.</li>
<li>Текст в начале `<li>` доступен как `li.firstChild`, его содержимое -- `li.firstChild.data`.</li>
</ol>
# Решение

View file

@ -1,64 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<ul>
<li>Животные
<ul>
<li>Млекопитающие
<ul>
<li>Коровы</li>
<li>Ослы</li>
<li>Собаки</li>
<li>Тигры</li>
</ul>
</li>
<li>Другие
<ul>
<li>Змеи</li>
<li>Птицы</li>
<li>Ящерицы</li>
</ul>
</li>
</ul>
</li>
<li>Рыбы
<ul>
<li>Аквариумные
<ul>
<li>Гуппи</li>
<li>Скалярии</li>
</ul>
</li>
<li>Морские
<ul>
<li>Морская форель</li>
</ul>
</li>
</ul>
</li>
</ul>
<script>
var lis = document.getElementsByTagName('li');
for (i = 0; i < lis.length; i++) {
// получить количество детей
var childCount = lis[i].getElementsByTagName('li').length;
if (!childCount) continue;
// добавить кол-во детей к текстовому узлу
lis[i].firstChild.data += ' [' + childCount + ']';
}
</script>
</body>
</html>

View file

@ -1,54 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<ul>
<li>Животные
<ul>
<li>Млекопитающие
<ul>
<li>Коровы</li>
<li>Ослы</li>
<li>Собаки</li>
<li>Тигры</li>
</ul>
</li>
<li>Другие
<ul>
<li>Змеи</li>
<li>Птицы</li>
<li>Ящерицы</li>
</ul>
</li>
</ul>
</li>
<li>Рыбы
<ul>
<li>Аквариумные
<ul>
<li>Гуппи</li>
<li>Скалярии</li>
</ul>
</li>
<li>Морские
<ul>
<li>Морская форель</li>
</ul>
</li>
</ul>
</li>
</ul>
<script>
// .. ваш код ..
</script>
</body>
</html>

View file

@ -1,10 +0,0 @@
# Дерево
[importance 5]
Есть дерево [edit src="source"]в песочнице[/edit].
Напишите код, который добавит каждому элементу списка `<li>` количество вложенных в него элементов. Узлы нижнего уровня, без детей -- пропускайте.
Результат:
[iframe border=1 src="solution"]

View file

@ -1,11 +0,0 @@
Для решения задачи сгенерируем таблицу в виде строки: `"<table>...</table>"`, а затем присвоим в `innerHTML`.
Алгоритм:
<ol>
<li>Создать объект даты `d = new Date(year, month-1)`. Это первый день месяца `month` (с учетом того, что месяцы в JS начинаются от 0, а не от 1).</li>
<li>Ячейки первого ряда пустые от начала и до дня недели `d.getDay()`, с которого начинается месяц. Создадим их.</li>
<li>Увеличиваем день в `d` на единицу: `d.setDate(d.getDate()+1)`, и добавляем в календарь очередную ячейку, пока не достигли следующего месяца. При этом последний день недели означает вставку перевода строки <code>"&lt;/tr&gt;&lt;tr&gt;"</code>.</li>
<li>При необходимости, если календарь окончился не на воскресенье - добавить пустые `TD` в таблицу, чтобы было все ровно.</li>
</ol>
[edit src="solution"]Открыть полное решение[/edit]

View file

@ -1,84 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
table {
border-collapse: collapse;
}
td,
th {
border: 1px solid black;
padding: 3px;
text-align: center;
}
th {
font-weight: bold;
background-color: #E6E6E6;
}
</style>
<meta charset="utf-8">
</head>
<body>
<div id="calendar"></div>
<script>
function createCalendar(id, year, month) {
var elem = document.getElementById(id);
var mon = month - 1; // месяцы в JS идут от 0 до 11, а не от 1 до 12
var d = new Date(year, mon);
var table = '<table><tr><th>пн</th><th>вт</th><th>ср</th><th>чт</th><th>пт</th><th>сб</th><th>вс</th></tr><tr>';
// заполнить первый ряд от понедельника
// и до дня, с которого начинается месяц
// * * * | 1 2 3 4
for (var i = 0; i < getDay(d); i++) {
table += '<td></td>';
}
// ячейки календаря с датами
while (d.getMonth() == mon) {
table += '<td>' + d.getDate() + '</td>';
if (getDay(d) % 7 == 6) { // вс, последний день - перевод строки
table += '</tr><tr>';
}
d.setDate(d.getDate() + 1);
}
// добить таблицу пустыми ячейками, если нужно
if (getDay(d) != 0) {
for (var i = getDay(d); i < 7; i++) {
table += '<td></td>';
}
}
// закрыть таблицу
table += '</tr></table>';
// только одно присваивание innerHTML
elem.innerHTML = table;
}
function getDay(date) { // получить номер дня недели, от 0(пн) до 6(вс)
var day = date.getDay();
if (day == 0) day = 7;
return day - 1;
}
createCalendar("calendar", 2012, 9)
</script>
</body>
</html>

View file

@ -1,41 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
table {
border-collapse: collapse;
}
td,
th {
border: 1px solid black;
padding: 3px;
text-align: center;
}
th {
font-weight: bold;
background-color: #E6E6E6;
}
</style>
<meta charset="utf-8">
</head>
<body>
<div id="calendar"></div>
<script>
function createCalendar(id, year, month) {
var elem = document.getElementById(id)
// ... ваш код, который генерирует в elem календарь
}
createCalendar('calendar', 2011, 1)
</script>
</body>
</html>

View file

@ -1,19 +0,0 @@
# Создать календарь в виде таблицы
[importance 4]
Напишите функцию, которая умеет генерировать календарь для заданной пары (месяц, год).
Календарь должен быть таблицей, где каждый день -- это `TD`. У таблицы должен быть заголовок с названиями дней недели, каждый день -- `TH`.
Синтаксис: `createCalendar(id, year, month)`.
Такой вызов должен генерировать текст для календаря месяца `month` в году `year`, а затем помещать его внутрь элемента с указанным `id`.
Например: `createCalendar("cal", 2012, 9)` сгенерирует в <code>&lt;div id='cal'&gt;&lt;/div&gt;</code> следующий календарь:
[iframe height=210 src="solution"]
P.S. Достаточно сгенерировать календарь, кликабельным его делать не нужно.

View file

@ -1,412 +0,0 @@
# Добавление и удаление узлов
Изменение DOM -- ключ к созданию "живых" страниц.
В этой главе мы рассмотрим, как создавать новые элементы "на лету" и заполнять их данными.
[cut]
## Пример: показ сообщения
В качестве примера рассмотрим добавление сообщения на страницу, чтобы оно было оформленно красивее чем обычный `alert`.
HTML-код для сообщения:
```html
<!--+ autorun height="100" -->
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
*!*
<div class="alert">
<strong>Ура!</strong> Вы прочитали это важное сообщение.
</div>
*/!*
```
## Создание элемента
Для создания элементов используются следующие методы:
<dl>
<dt>`document.createElement(tag)`</dt>
<dd>Создает новый элемент с указанным тегом:
```js
var div = document.createElement('div');
```
</dd>
<dt>`document.createTextNode(text)`</dt>
<dd>Создает новый *текстовый* узел с данным текстом:
```js
var textElem = document.createTextNode('Тут был я');
```
</dl>
### Создание сообщения
В нашем случае мы хотим сделать DOM-элемент `div`, дать ему классы и заполнить текстом:
```js
var div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Ура!</strong> Вы прочитали это важное сообщение.";
```
После этого кода у нас есть готовый DOM-элемент. Пока что он присвоен в переменную `div`, но не виден, так как никак не связан со страницей.
## Добавление элемента: appendChild, insertBefore
Чтобы DOM-узел был показан на странице, его необходимо вставить в `document`.
Для этого первым делом нужно решить, куда мы будем его вставлять. Предположим, что мы решили, что вставлять будем в некий элемент `parentElem`, например `var parentElem = document.body`.
Для вставки внутрь `parentElem` есть следующие методы:
<dl>
<dt>`parentElem.appendChild(elem)`</dt>
<dd>Добавляет `elem` в конец дочерних элементов `parentElem`.
Следующий пример добавляет новый элемент в конец `<ol>`:
```html
<!--+ run height=100 -->
<ol id="list">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
var newLi = document.createElement('li');
newLi.innerHTML = 'Привет, мир!';
list.appendChild(newLi);
</script>
```
</dd>
<dt>`parentElem.insertBefore(elem, nextSibling)`</dt>
<dd>Вставляет `elem` в коллекцию детей `parentElem`, перед элементом `nextSibling`.
Следующий код вставляет новый элемент перед вторым `<li>`:
```html
<!--+ run height=100 -->
<ol id="list">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
var newLi = document.createElement('li');
newLi.innerHTML = 'Привет, мир!';
*!*
list.insertBefore(newLi, list.children[1]);
*/!*
</script>
```
Для вставки элемента в начало достаточно указать, что вставлять будем перед первым потомком:
```js
list.insertBefore(newLi, list.firstChild);
```
У читателя, который посмотрит на этот код внимательно, наверняка возникнет вопрос: "А что, если `list` вообще пустой, в этом случае ведь `list.firstChild = null`, произойдёт ли вставка?"
Ответ -- да, произойдёт.
**Дело в том, что если вторым аргументом указать `null`, то `insertBefore` сработает как `appendChild`:**
```js
parentElem.insertBefore(elem, null);
// то же, что и:
parentElem.appendChild(elem)
```
Так что `insertBefore` универсален.
</dd>
</dl>
[smart]
Все методы вставки возвращают вставленный узел.
Например, `parentElem.appendChild(elem)` возвращает `elem`.
[/smart]
### Пример использования
Добавим сообщение в конец `<body>`:
```html
<!--+ height=150 run autorun -->
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<body>
<h3>Моя страница</h3>
</body>
<script>
var div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Ура!</strong> Вы прочитали это важное сообщение.";
*!*
document.body.appendChild(div);
*/!*
</script>
```
...А теперь -- в начало `<body>`:
```html
<!--+ height=150 run autorun -->
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<body>
<h3>Моя страница</h3>
</body>
<script>
var div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Ура!</strong> Вы прочитали это важное сообщение.";
*!*
document.body.insertBefore(div, document.body.firstChild);
*/!*
</script>
```
## Клонирование узлов: cloneNode
А как бы вставить второе похожее сообщение?
Конечно, можно сделать функцию для генерации сообщений и поместить туда этот код, но в ряде случаев гораздо эффективнее -- *клонировать* существующий `div`, а потом изменить текст внутри. В частности, если элемент большой, то клонировать его будет гораздо быстрее, чем пересоздавать.
Вызов `elem.cloneNode(true)` создаст "глубокую" копию элемента -- вместе с атрибутами, включая подэлементы. Если же вызвать с аргументом `false`, то копия будет сделана без дочерних элементов. Это нужно гораздо реже.
Пример со вставкой копии сообщения:
```html
<!--+ height=200 run autorun -->
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<body>
<h3>Моя страница</h3>
</body>
<script>
var div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Ура!</strong> Вы прочитали это важное сообщение.";
document.body.insertBefore(div, document.body.firstChild);
*!*
// создать копию узла
var div2 = div.cloneNode(true);
// копию можно подправить
div2.querySelector('strong').innerHTML = 'Супер!';
// вставим её после текущего сообщения
div.parentNode.insertBefore(div2, div.nextSibling);
*/!*
</script>
```
Обратите внимание на последнюю строку, которая вставляет `div2` после `div`:
```js
div.parentNode.insertBefore(div2, div.nextSibling);
```
<ol>
<li>Для вставки нам нужен будущий родитель. Мы, возможно, не знаем, где точно находится `div` (или не хотим зависеть от того, где он), но если нужно вставить рядом с `div`, то родителем определённо будет `div.parentNode`.</li>
<li>Мы хотели бы вставить *после* `div`, но метода `insertAfter` нет, есть только `insertBefore`, поэтому вставляем *перед* его правым соседом `div.nextSibling`.</li>
</ol>
## Удаление узлов: removeChild
Для удаления узла есть два метода:
<dl>
<dt>`parentElem.removeChild(elem)`</dt>
<dd>Удаляет `elem` из списка детей `parentElem`.</dd>
<dt>`parentElem.replaceChild(newElem, elem)`</dt>
<dd>Среди детей `parentElem` удаляет `elem` и вставляет на его место `newElem`.</dd>
</dl>
Оба этих метода возвращают удаленный узел, то есть `elem`. Если нужно, его можно вставить в другое место DOM тут же или в будущем.
[smart]
Если вы хотите *переместить* элемент на новое место -- не нужно его удалять со старого.
**Все методы вставки автоматически удаляют вставляемый элемент со старого места.**
Конечно же, это очень удобно.
Например, поменяем элементы местами:
```html
<!--+ run height=150 -->
<div>Первый</div>
<div>Второй</div>
<script>
var first = document.body.children[0];
var last = document.body.children[1];
// нет необходимости в предварительном removeChild(last)
document.body.insertBefore(last, first); // поменять местами
</script>
```
[/smart]
[smart header="Метод `remove`"]
В современном стандарте есть также метод [elem.remove()](https://dom.spec.whatwg.org/#dom-childnode-remove), который удаляет элемент напрямую, не требуя ссылки на родителя. Это зачастую удобнее, чем `removeChild`.
Он поддерживается во всех современных браузерах, кроме IE11-. Впрочем, легко подключить или даже сделать полифилл.
[/smart]
### Удаление сообщения
Сделаем так, что через секунду сообщение пропадёт:
```html
<!--+ run -->
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<body>
<h3>Сообщение пропадёт через секунду</h3>
</body>
<script>
var div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Ура!</strong> Вы прочитали это важное сообщение.";
document.body.appendChild(div);
*!*
setTimeout(function() {
div.parentNode.removeChild(div);
}, 1000);
*/!*
</script>
```
## Текстовые узлы для вставки текста
При работе с сообщением мы использовали только узлы-элементы и `innerHTML`.
Но и текстовые узлы тоже имеют интересную область применения!
Если текст для сообщения нужно показать именно как текст, а не как HTML, то можно обернуть его в текстовый узел.
Например:
```html
<!--+ run -->
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
var div = document.createElement('div');
div.className = "alert alert-success";
document.body.appendChild(div);
*!*
var text = prompt("Введите текст для сообщения", "Жили были <a> и <b>!");
// вставится именно как текст, без HTML-обработки
div.appendChild(document.createTextNode(text));
*/!*
</script>
```
В современных браузерах (кроме IE8-) в качестве альтернативы можно использовать присвоение `textContent`.
## Итого
Методы для создания узлов:
<ul>
<li>`document.createElement(tag)` -- создает элемент</li>
<li>`document.createTextNode(value)` -- создает текстовый узел</li>
<li>`elem.cloneNode(deep)` -- клонирует элемент, если `deep == true`, то со всеми потомками, если `false` -- без потомков.</li>
</ul>
Вставка и удаление узлов:
<ul>
<li>`parent.appendChild(elem)`</li>
<li>`parent.insertBefore(elem, nextSibling)`</li>
<li>`parent.removeChild(elem)`</li>
<li>`parent.replaceChild(newElem, elem)`</li>
</ul>
Все эти методы возвращают `elem`.
Методы для изменения DOM также описаны в спецификации <a href="http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html">DOM Level 1</a>.

View file

@ -1,8 +0,0 @@
Решение:
```js
var ul = document.body.children[0];
ul.insertAdjacentHTML("beforeEnd", "<li>3</li><li>4</li><li>5</li>");
```

View file

@ -1,15 +0,0 @@
# Вставьте элементы в конец списка
[importance 5]
Напишите код для вставки текста `html` в конец списка `ul` с использованием метода `insertAdjacentHTML`. Такая вставка, в отличие от присвоения `innerHTML+=`, не будет перезаписывать текущее содержимое.
Добавьте к списку ниже элементы `<li>3</li><li>4</li><li>5</li>`:
```html
<ul>
<li>1</li>
<li>2</li>
</ul>
```

View file

@ -1,11 +0,0 @@
Для сортировки нам поможет функция `sort` массива.
Общая идея лежит на поверхности: сделать массив из строк и отсортировать его. Тонкости кроются в деталях.
В ифрейме ниже загружен документ, описывающий и реализующий разные алгоритмы. Обратите внимание: разница в производительности может достигать нескольких раз!
[iframe height=800 border=1 src="solution" link edit]
P.S. Создавать `DocumentFragment` здесь ни к чему. Можно вытащить из документа `TBODY` и иметь дело с ним в отрыве от DOM (алгоритм 4).
P.P.S. Если нужно сделать много узлов, то обычно `innerHTML` работает быстрее, чем удаление и вставка элементов через DOM-вызовы. То есть, сгенерировать таблицу заново эффективнее.

View file

@ -1,202 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<fieldset>
<legend>Алгоритм 1.</legend>
<ol>
<li>Все <code>TR</code> удалить из таблицы, при этом собрав их в JavaScript-массив.</li>
<li>Отсортировать этот массив, используя свою функцию в <code>sort(...)</code> для сравнения <code>TR</code></li>
<li>Добавить <code>TR</code> из массива в таблицу в нужном порядке</li>
</ol>
<button onclick="bench(sort1, this)">Померять время</button>
</fieldset>
<fieldset>
<legend>Алгоритм 2.</legend>
<ol>
<li>Скопировать <code>TR</code> в JavaScript-массив.</li>
<li>Отсортировать этот массив, используя свою функцию в <code>sort(...)</code> для сравнения <code>TR</code></li>
<li>Добавить <code>TR</code> из массива в таблицу в нужном порядке. При добавлении каждый <code>TR</code> сам удалится с предыдущего места.</li>
</ol>
<button onclick="bench(sort2, this)">Померять время</button>
</fieldset>
<fieldset>
<legend>Алгоритм 3.</legend>
<ol>
<li>Создать массив из объектов вида <code>{elem: ссылка на TR, value: содержимое TR}</code>.</li>
<li>Отсортировать массив по <code>value</code>. Функция сравнения во время сортировки теперь будет обращаться не к <code>innerHTML</code>, а к свойству объекта, это быстрее. Сортировка может потребовать многократных сравнений одного и того же элемента,
отсюда выигрыш.</li>
<li>Добавить <code>TR</code> в таблицу в нужном порядке (автоудалятся с предыдущего места).</li>
</ol>
<button onclick="bench(function(){sortOpt(false)}, this)">Померять время</button>
</fieldset>
<fieldset>
<legend>Алгоритм 4.</legend>
<ol>
<li>Выполнить алгоритм 3, но перед этим удалить таблицу из документа, а после - вставить обратно.</li>
</ol>
<button onclick="bench(function(){sortOpt(true)}, this)">Померять время</button>
</fieldset>
<fieldset>
<legend>Алгоритм 5.</legend>
<ol>
<li>Замерить время генерации таблицы (создаётся строка и пишется в <code>innerHTML</code>).</li>
</ol>
<button onclick="benchMake(this)">Померять время</button>
</fieldset>
<div id="table-holder"></div>
<script>
function makeTable() {
var tableHolder = document.getElementById('table-holder');
var contents = '<thead><th>Имя</th><th>Фамилия</th><th>Отчество</th><th>Возраст</th></thead>';
contents += '<tbody>';
for (var i = 0; i < 100; i++) {
contents += "<tr><td> ... </td><td>Разные</td><td>Данные</td><td>" + ((i + 50) % 30) + "</td></tr>";
}
contents += '</tbody>';
tableHolder.innerHTML = '<table>' + contents + '</table>';
}
/* перенести элементы в массив, отсортировать его и перевставить */
function sort1() {
var tbody = document.getElementsByTagName('tbody')[0];
var rows = [];
for (var i = tbody.children.length - 1; i >= 0; i--) {
var child = tbody.removeChild(tbody.children[i]);
rows.push(child);
}
rows.sort(function(a, b) {
return a.lastChild.innerHTML - b.lastChild.innerHTML;
})
for (var i = 0; i < rows.length; i++) {
tbody.appendChild(rows[i]);
}
}
/* скопировать ссылки в массив, отсортировать его и перевставить */
function sort2() {
var tbody = document.getElementsByTagName('tbody')[0];
var rows = [];
for (var i = 0; i < tbody.children.length; i++) {
rows.push(tbody.children[i]);
}
rows.sort(function(a, b) {
return a.lastChild.innerHTML - b.lastChild.innerHTML;
})
for (var i = 0; i < rows.length; i++) {
tbody.appendChild(rows[i]);
}
}
/* создать массив из значений и ссылок, отсортировать, перевставить
doRemove = предварительно вынуть из документа
*/
function sortOpt(doRemove) {
var tbody = document.getElementsByTagName('tbody')[0];
var table = tbody.parentNode;
if (doRemove) table.removeChild(tbody);
var rows = [];
for (var i = 0; i < tbody.children.length; i++) {
var elem = tbody.children[i];
rows.push({
value: elem.lastChild.innerHTML,
elem: elem
});
}
rows.sort(function(a, b) {
return a.value - b.value;
});
for (var i = 0; i < rows.length; i++) {
tbody.appendChild(rows[i].elem);
}
if (doRemove) table.appendChild(tbody);
}
function bench(f, elem) {
var sum = 0;
for (var i = 0; i < 100; i++) {
makeTable();
var d = new Date;
f();
sum += new Date - d;
}
elem.innerHTML = sum + 'мс';
}
function benchMake(elem) {
var sum = 0;
for (var i = 0; i < 100; i++) {
var d = new Date;
makeTable();
sum += new Date - d;
}
elem.innerHTML = sum + 'мс';
}
</script>
<h3>Содержимое документа для придания "реалистичности"</h3>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
<div><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></div>
</body>
</html>

View file

@ -1,48 +0,0 @@
# Отсортировать таблицу
[importance 5]
Есть таблица:
<table>
<tr>
<th>Имя</th>
<th>Фамилия</th>
<th>Отчество</th>
<th>Возраст</th>
</tr>
<tr>
<td>Вася</td>
<td>Петров</td>
<td>Александрович</td>
<td>10</td>
</tr>
<tr>
<td>Петя</td>
<td>Иванов</td>
<td>Петрович</td>
<td>15</td>
</tr>
<tr>
<td>Владимир</td>
<td>Ленин</td>
<td>Ильич</td>
<td>9</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
</table>
Строк в таблице много: может быть 20, 50, 100.. Есть и другие элементы в документе.
Как бы вы предложили отсортировать содержимое таблицы по полю `Возраст`? Обдумайте алгоритм, реализуйте его.
Как сделать, чтобы сортировка работала как можно быстрее? А если в таблице 10000 строк (бывает и такое)?
P.S. Может ли здесь помочь `DocumentFragment`?
P.P.S. Если предположить, что у нас заранее есть массив данных для таблицы в JavaScript -- что быстрее: отсортировать эту таблицу или сгенерировать новую?

View file

@ -1,362 +0,0 @@
# Мультивставка: insertAdjacentHTML и DocumentFragment
Обычные методы вставки работают с одним узлом. Но есть и способы вставлять множество узлов одновременно.
[cut]
## Оптимизация вставки в документ
Рассмотрим задачу: сгенерировать список `UL/LI`.
Есть две возможных последовательности:
<ol>
<li>Сначала вставить `UL` в документ, а потом добавить к нему `LI`:
```js
var ul = document.createElement('ul');
document.body.appendChild(ul); // сначала в документ
for (...) ul.appendChild(li); // потом узлы
```
</li>
<li>Полностью создать список "вне DOM", а потом -- вставить в документ:
```js
//+ no-beautify
var ul = document.createElement('ul');
for(...) ul.appendChild(li); // сначала вставить узлы
document.body.appendChild(ul); // затем в документ
```
</li>
</ol>
Как ни странно, между этими последовательностями есть разница. В большинстве браузеров, второй вариант -- быстрее.
Почему же? Иногда говорят: "потому что браузер перерисовывает каждый раз при добавлении элемента". Это не так. Дело вовсе не в перерисовке.
Браузер достаточно "умён", чтобы ничего не перерисовывать понапрасну. В большинстве случаев процессы перерисовки и сопутствующие вычисления будут отложены до окончания работы скрипта, и на тот момент уже совершенно без разницы, в какой последовательности были изменены узлы.
**Тем не менее, при вставке узла происходят разные внутренние события и обновления внутренних структур данных, скрытые от наших глаз.**
Что именно происходит -- зависит от конкретной, внутренней браузерной реализации DOM, но это отнимает время. Конечно, браузеры развиваются и стараются свести лишние действия к минимуму.
[online]
### Бенчмарк [#insert-bench-tbody]
Чтобы легко проверить текущее состояние дел -- вот два бенчмарка.
Оба они создают таблицу 20x20, наполняя <code>TBODY</code> элементами <code>TR/TD</code>.
При этом первый вставляет все в документ тут же, второй -- задерживает вставку <code>TBODY</code> в документ до конца процесса.
Кликните, чтобы запустить.
<input type="button" onclick="alert(bench(appendFirst,50))" value="TBODY cразу в DOM"/> <input type="button" onclick="alert(bench(appendLast,50))" value="Отложенная вставка TBODY в DOM"/>
<table id="bench-table"></table>
Код для тестов находится в файле [insert-bench.js](insert-bench.js).
[/online]
## Добавление множества узлов
Продолжим работать со вставкой узлов.
Рассмотрим случай, когда в документе *уже есть* большой список `UL`. И тут понадобилось срочно добавить еще 20 элементов `LI`.
Как это сделать?
Если новые элементы пришли в виде строки, то можно попробовать добавить их так:
```js
ul.innerHTML += "<li>1</li><li>2</li>...";
```
Но операцию `ul.innerHTML += "..."` можно по-другому переписать как `ul.innerHTML = ul.innerHTML + "..."`. Иначе говоря, она *не прибавляет, а заменяет* всё содержимое списка на дополненную строку. Это и нехорошо с точки зрения производительности, но и будут побочные эффекты. В частности, все внешние ресурсы (картинки) внутри перезаписываемого `innerHTML` будут загружены заново. Если в каких-то переменных были ссылки на элементы списка -- они станут неверны, так как содержимое полностью заменяется.В общем, так лучше не делать.
А если нужно вставить в середину списка? Здесь `innerHTML` вообще не поможет.
Можно, конечно, вставить строку во временный DOM-элемент и перенести оттуда элементы, но есть и гораздо лучший вариант: метод `insertAdjacentHTML`!
## insertAdjacent*
Метод [insertAdjacentHTML](https://developer.mozilla.org/en/DOM/element.insertAdjacentHTML) позволяет вставлять произвольный HTML в любое место документа, в том числе *и между узлами*!
Он поддерживается всеми браузерами, кроме Firefox меньше версии 8, ну а там его можно эмулировать.
Синтаксис:
```js
elem.insertAdjacentHTML(where, html);
```
<dl>
<dt>`html`</dt>
<dd>Строка HTML, которую нужно вставить</dd>
<dt>`where`</dt>
<dd>Куда по отношению к `elem` вставлять строку. Всего четыре варианта:
<ol>
<li>`beforeBegin` -- перед `elem`.</li>
<li>`afterBegin` -- внутрь `elem`, в самое начало.</li>
<li>`beforeEnd` -- внутрь `elem`, в конец.</li>
<li>`afterEnd` -- после `elem`.</li>
</ol>
</dd>
<img src="insertAdjacentHTML.png">
Например, вставим пропущенные элементы списка *перед* `<li>5</li>`:
```html
<!--+ run -->
<ul>
<li>1</li>
<li>2</li>
<li>5</li>
</ul>
<script>
var ul = document.body.children[0];
var li5 = ul.children[2];
li5.insertAdjacentHTML("beforeBegin", "<li>3</li><li>4</li>");
</script>
```
Единственный недостаток этого метода -- он не работает в Firefox до версии 8. Но его можно легко добавить, используя [полифилл insertAdjacentHTML для Firefox](insertAdjacentFF.js).
У этого метода есть "близнецы-братья", которые поддерживаются везде, кроме Firefox, но в него они добавляются тем же полифиллом:
<ul>
<li>[elem.insertAdjacentElement(where, newElem)](http://help.dottoro.com/ljbreokf.php) -- вставляет в произвольное место не строку HTML, а элемент `newElem`.</li>
<li>[elem.insertAdjacentText(where, text)](http://help.dottoro.com/ljrsluxu.php) -- создаёт текстовый узел из строки `text` и вставляет его в указанное место относительно `elem`.</li>
</ul>
Синтаксис этих методов, за исключением последнего параметра, полностью совпадает с `insertAdjacentHTML`. Вместе они образуют "универсальный швейцарский нож" для вставки чего угодно куда угодно.
## DocumentFragment
[warn header="Важно для старых браузеров"]
Оптимизация, о которой здесь идёт речь, важна в первую очередь для старых браузеров, включая IE9-. В современных браузерах эффект от нее, как правило, небольшой, а иногда может быть и отрицательным.
[/warn]
До этого мы говорили о вставке строки в DOM. А что делать в случае, когда надо в существующий `UL` вставить много *DOM-элементов*?
Можно вставлять их один за другим, вызовом `insertBefore/appendChild`, но при этом получится много операций с большим живым документом.
**Вставить пачку узлов единовременно поможет `DocumentFragment`. Это особенный *кросс-браузерный* DOM-объект, который похож на обычный DOM-узел, но им не является.**
Синтаксис для его создания:
```js
var fragment = document.createDocumentFragment();
```
В него можно добавлять другие узлы.
```js
fragment.appendChild(node);
```
Его можно клонировать:
```js
fragment.cloneNode(true); // клонирование с подэлементами
```
**У `DocumentFragment` нет обычных свойств DOM-узлов, таких как `innerHTML`, `tagName` и т.п. Это не узел.**
Его "Фишка" заключается в том, что когда `DocumentFragment` вставляется в DOM -- то он исчезает, а вместо него вставляются его дети. Это свойство является уникальной особенностью `DocumentFragment`.
Например, если добавить в него много `LI`, и потом вызвать `ul.appendChild(fragment)`, то фрагмент растворится, и в DOM вставятся именно `LI`, причём в том же порядке, в котором были во фрагменте.
Псевдокод:
```js
// хотим вставить в список UL много LI
// делаем вспомогательный DocumentFragment
var fragment = document.createDocumentFragment();
for (цикл по li) {
fragment.appendChild(list[i]); // вставить каждый LI в DocumentFragment
}
ul.appendChild(fragment); // вместо фрагмента вставятся элементы списка
```
В современных браузерах эффект от такой оптимизации может быть различным, а на небольших документах иногда и отрицательным.
Понять текущее положение вещей вы можете, запустив следующий [edit src="benchmark"]небольшой бенчмарк[/edit].
## append/prepend, before/after, replaceWith
Сравнительно недавно в [стандарте](https://dom.spec.whatwg.org/) появились методы, которые позволяют вставить что угодно и куда угодно.
Синтаксис:
<ul>
<li>`node.append(...nodes)` -- вставляет `nodes` в конец `node`,</li>
<li>`node.prepend(...nodes)` -- вставляет `nodes` в начало `node`,</li>
<li>`node.after(...nodes)` -- вставляет `nodes` после узла `node`,</li>
<li>`node.before(...nodes)` -- вставляет `nodes` перед узлом `node`,</li>
<li>`node.replaceWith(...nodes)` -- вставляет `nodes` вместо `node`.</li>
</ul>
Эти методы ничего не возвращают.
Во всех этих методах `nodes` -- DOM-узлы или строки, в любом сочетании и количестве. Причём строки вставляются именно как текстовые узлы, в отличие от `insertAdjacentHTML`.
Пример (с полифиллом):
```html
<!--+ run autorun height=80 -->
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.polyfill.io/v1/polyfill.js?features=Element.prototype.append,Element.prototype.after"></script>
</head>
<body>
<script>
// добавим элемент в конец <body>
var p = document.createElement('p');
document.body.append(p);
var em = document.createElement('em');
em.append('Мир!');
// вставить в параграф текстовый и обычный узлы
p.append("Привет, ", em);
// добавить элемент после <p>
p.after(document.createElement('hr'))
</script>
</body>
</html>
```
## Итого
<ul>
<li>Манипуляции, меняющие структуру DOM (вставка, удаление элементов), как правило, быстрее с отдельным маленьким узлом, чем с большим DOM, который находится в документе.
Конкретная разница зависит от внутренней реализации DOM в браузере.</li>
<li>Семейство методов для вставки HTML/элемента/текста в произвольное место документа:
<ul>
<li>`elem.insertAdjacentHTML(where, html)`</li>
<li>`elem.insertAdjacentElement(where, node)`</li>
<li>`elem.insertAdjacentText(where, text)`</li>
</ul>
Два последних метода не поддерживаются в Firefox, на момент написания текста, но есть небольшой полифилл [insertAdjacentFF.js](insertAdjacentFF.js), который добавляет их. Конечно, он нужен только для Firefox.
</li>
<li>`DocumentFragment` позволяет минимизировать количество вставок в большой живой DOM. Эта оптимизация особо эффективна в старых браузерах, в новых эффект от неё меньше или наоборот отрицательный.
Элементы сначала вставляются в него, а потом -- он вставляется в DOM. При вставке `DocumentFragment` "растворяется", и вместо него вставляются содержащиеся в нём узлы.
`DocumentFragment`, в отличие от `insertAdjacent*`, работает с коллекцией DOM-узлов.
</li>
<li>Современные методы, работают с любым количеством узлов и текста, желателен полифилл:
<ul>
<li>`append/prepend` -- вставка в конец/начало.</li>
<li>`before/after` -- вставка после/перед.</li>
<li>`replaceWith` -- замена.</li>
</ul>
</li>
</li>
</ul>
[head]
<script>
function bench(test, times) {
var sum = 0;
for(var i = 0; i < times; i++) {
if(test.setup) test.setup();
var t = new Date();
test.work();
sum += (new Date() - t);
if(test.tearDown) test.tearDown();
}
return sum;
}
/* 1. Вставляет TBODY в документ сразу. а затем элементы */
var appendFirst = new function() {
var benchTable;
this.setup = function() {
// очистить всё
benchTable = document.getElementById('bench-table')
while(benchTable.firstChild) {
benchTable.removeChild(benchTable.firstChild);
}
}
this.work = function() {
// встаить TBODY и элементы
var tbody = document.createElement('TBODY');
benchTable.appendChild(tbody);
for(var i = 0; i < 20; i++) {
var tr = document.createElement('TR');
tbody.appendChild(tr);
for(var j = 0; j < 20; j++) {
var td = document.createElement('td');
td.appendChild(document.createTextNode(''+i.toString(20)+j.toString(20)));
tr.appendChild(td);
}
}
}
}
/* 2. Полностью делает TBODY, а затем вставляет в документ */
var appendLast = new function() {
var benchTable;
this.setup = function() {
// очистить всё
benchTable = document.getElementById('bench-table');
while(benchTable.firstChild) {
benchTable.removeChild(benchTable.firstChild);
}
}
this.work = function() {
var tbody = document.createElement('TBODY');
for(var i = 0; i < 20; i++) {
var tr = document.createElement('TR');
tbody.appendChild(tr);
for(var j = 0; j < 20; j++) {
var td = document.createElement('td');
tr.appendChild(td);
td.appendChild(document.createTextNode(''+i.toString(20)+j.toString(20)));
}
}
benchTable.appendChild(tbody);
}
}
</script>
<style>
#bench-table td {
padding: 0;
}
#bench-list li {
display: inline-block;
margin: 0;
padding: 2px;
list-style-image: none;
list-style: none;
}
</style>
[/head]

View file

@ -1,11 +0,0 @@
function bench(test, times) {
var sum = 0;
for (var i = 0; i < times; i++) {
if (test.setup) test.setup();
var t = new Date();
test.work();
sum += (new Date() - t);
if (test.tearDown) test.tearDown();
}
return sum;
}

View file

@ -1,45 +0,0 @@
var DocumentFragmentTest = new function() {
var benchList = document.getElementById('bench-list');
var items = [];
for (var i = 0; i < 100; i++) {
var li = document.createElement('li');
li.innerHTML = i;
items.push(li);
}
this.insertPlain = new function() {
this.setup = function() {
while (benchList.firstChild) {
benchList.removeChild(benchList.firstChild);
}
}
this.work = function() {
for (var i = 0; i < items.length; i++) {
benchList.appendChild(items[i]);
}
}
};
this.insertDocumentFragment = new function() {
this.setup = function() {
// очистить всё
while (benchList.firstChild) {
benchList.removeChild(benchList.firstChild);
}
}
this.work = function() {
var docFrag = document.createDocumentFragment();
for (var i = 0; i < items.length; i++) {
docFrag.appendChild(items[i]);
}
benchList.appendChild(docFrag);
}
};
}

View file

@ -1,23 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
Вставляются 100 элементов LI в пустой UL.
<input type="button" onclick="alert(bench(DocumentFragmentTest.insertPlain,200))" value="Замерить время на обычную вставку" />
<input type="button" onclick="alert(bench(DocumentFragmentTest.insertDocumentFragment,200))" value="Замерить время на вставку через DocumentFragment">
<ul id="bench-list"></ul>
<script src="bench.js"></script>
<script src="documentfragment-bench.js"></script>
</body>
</html>

View file

@ -1,45 +0,0 @@
var DocumentFragmentTest = new function() {
var benchList = document.getElementById('bench-list');
var items = [];
for (var i = 0; i < 100; i++) {
var li = document.createElement('li');
li.innerHTML = i;
items.push(li);
}
this.insertPlain = new function() {
this.setup = function() {
while (benchList.firstChild) {
benchList.removeChild(benchList.firstChild);
}
}
this.work = function() {
for (var i = 0; i < items.length; i++) {
benchList.appendChild(items[i]);
}
}
};
this.insertDocumentFragment = new function() {
this.setup = function() {
// очистить всё
while (benchList.firstChild) {
benchList.removeChild(benchList.firstChild);
}
}
this.work = function() {
var docFrag = document.createDocumentFragment();
for (var i = 0; i < items.length; i++) {
docFrag.appendChild(items[i]);
}
benchList.appendChild(docFrag);
}
};
}

View file

@ -1,59 +0,0 @@
/* 1. Вставляет TBODY в документ сразу. а затем элементы */
var appendFirst = new function() {
var benchTable;
this.setup = function() {
// очистить всё
benchTable = document.getElementById('bench-table')
while (benchTable.firstChild) {
benchTable.removeChild(benchTable.firstChild);
}
}
this.work = function() {
// встаить TBODY и элементы
var tbody = document.createElement('TBODY');
benchTable.appendChild(tbody);
for (var i = 0; i < 20; i++) {
var tr = document.createElement('TR');
tbody.appendChild(tr);
for (var j = 0; j < 20; j++) {
var td = document.createElement('td');
td.appendChild(document.createTextNode('' + i.toString(20) + j.toString(20)));
tr.appendChild(td);
}
}
}
}
/* 2. Полностью делает TBODY, а затем вставляет в документ */
var appendLast = new function() {
var benchTable;
this.setup = function() {
// очистить всё
benchTable = document.getElementById('bench-table');
while (benchTable.firstChild) {
benchTable.removeChild(benchTable.firstChild);
}
}
this.work = function() {
var tbody = document.createElement('TBODY');
for (var i = 0; i < 20; i++) {
var tr = document.createElement('TR');
tbody.appendChild(tr);
for (var j = 0; j < 20; j++) {
var td = document.createElement('td');
tr.appendChild(td);
td.appendChild(document.createTextNode('' + i.toString(20) + j.toString(20)));
}
}
benchTable.appendChild(tbody);
}
}

View file

@ -1,35 +0,0 @@
// http://learn.javascript.ru/files/tutorial/browser/dom/insertAdjacentFF.js
// Добавляет поддержку insertAdjacent* в Firefox
if (typeof HTMLElement != "undefined" && !HTMLElement.prototype.insertAdjacentElement) {
HTMLElement.prototype.insertAdjacentElement = function(where, parsedNode) {
switch (where) {
case 'beforeBegin':
this.parentNode.insertBefore(parsedNode, this)
break;
case 'afterBegin':
this.insertBefore(parsedNode, this.firstChild);
break;
case 'beforeEnd':
this.appendChild(parsedNode);
break;
case 'afterEnd':
if (this.nextSibling) this.parentNode.insertBefore(parsedNode, this.nextSibling);
else this.parentNode.appendChild(parsedNode);
break;
}
}
HTMLElement.prototype.insertAdjacentHTML = function(where, htmlStr) {
var r = this.ownerDocument.createRange();
r.setStartBefore(this);
var parsedHTML = r.createContextualFragment(htmlStr);
this.insertAdjacentElement(where, parsedHTML)
}
HTMLElement.prototype.insertAdjacentText = function(where, txtStr) {
var parsedText = document.createTextNode(txtStr)
this.insertAdjacentElement(where, parsedText)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -1,149 +0,0 @@
# Метод document.write
Метод `document.write` -- один из наиболее древних методов добавления текста к документу.
У него есть существенные ограничения, поэтому он используется редко, но по своей сути он совершенно уникален и иногда, хоть и редко, может быть полезен.
[cut]
## Как работает document.write
Метод `document.write(str)` работает только пока HTML-страница находится в процессе загрузки. Он дописывает текст в текущее место HTML ещё до того, как браузер построит из него DOM.
HTML-документ ниже будет содержать `1 2 3`.
```html
<!--+ run -->
<body>
1
<script>
document.write(2);
</script>
3
</body>
```
**Нет никаких ограничений на содержимое `document.write`**.
Строка просто пишется в HTML-документ без проверки структуры тегов, как будто она всегда там была.
Например:
```html
<!--+ run -->
<script>
document.write('<style> td { color: #F40 } </style>');
</script>
<table>
<tr>
<script>
document.write('<td>')
</script>
Текст внутри TD.
<script>
document.write('</td>')
</script>
</tr>
</table>
```
Также существует метод `document.writeln(str)` -- не менее древний, который добавляет после `str` символ перевода строки `"\n"`.
## Только до конца загрузки
Во время загрузки браузер читает документ и тут же строит из него DOM, по мере получения информации достраивая новые и новые узлы, и тут же отображая их. Этот процесс идет непрерывным потоком. Вы наверняка видели это, когда заходили на сайты в качестве посетителя -- браузер зачастую отображает неполный документ, добавляя его новыми узлами по мере их получения.
**Методы `document.write` и `document.writeln` пишут напрямую в текст документа, до того как браузер построит из него DOM, поэтому они могут записать в документ все, что угодно, любые стили и незакрытые теги.**
Браузер учтёт их при построении DOM, точно так же, как учитывает очередную порцию HTML-текста.
Технически, вызвать `document.write` можно в любое время, однако, когда HTML загрузился, и браузер полностью построил DOM, документ становится *"закрытым"*. Попытка дописать что-то в закрытый документ открывает его заново. При этом все текущее содержимое удаляется.
Текущая страница, скорее всего, уже загрузилась, поэтому если вы нажмёте на эту кнопку -- её содержимое удалится:
[pre no-typography]
<input type="button" onclick='document.write("Пустая страница!");' value="Запустить document.write('Пустая страница!')">
[/pre]
Из-за этой особенности `document.write` для загруженных документов не используют.
[warn header="XHTML и `document.write`"]
В некоторых современных браузерах при получении страницы с заголовком `Content-Type: text/xml` или `Content-Type: text/xhtml+xml` включается "XML-режим" чтения документа. Метод `document.write` при этом не работает.
Это лишь одна из причин, по которой XML-режим обычно не используют.
[/warn]
## Преимущества перед innerHTML
Метод `document.write` -- динозавр, он существовал десятки <strike>миллионов</strike> лет назад. С тех пор, как появился и стал стандартным метод `innerHTML`, нужда в нём возникает редко, но некоторые преимущества, всё же, есть.
<ul>
<li>Метод `document.write` работает быстрее, фактически это самый быстрый способ добавить на страницу текст, сгенерированный скриптом.
Это естественно, ведь он не модифицирует существующий DOM, а пишет в текст страницы до его генерации.</li>
<li>Метод `document.write` вставляет любой текст на страницу "как есть", в то время как `innerHTML` может вписать лишь валидный HTML (при попытке подсунуть невалидный -- браузер скорректирует его).</li>
</ul>
Эти преимущества являются скорее средством оптимизации, которое нужно использовать именно там, где подобная оптимизация нужна или уместна.
Однако, `document.write` по своей природе уникален: он добавляет текст "в текущее место документа", без всяких хитроумных DOM. Поэтому он бывает просто-напросто удобен, из-за чего его нередко используют не по назначению.
## Антипример: реклама
Например, `document.write` используют для вставки рекламных скриптов и различных счетчиков, когда URL скрипта необходимо генерировать динамически, добавляя в него параметры из JavaScript, например:
```html
<script>
// в url указано текущее разрешение экрана посетителя
var url = 'http://ads.com/buyme?screen=' + screen.width + "x" + screen.height;
// загрузить такой скрипт прямо сейчас
document.write('<script src="' + url + '"></scr' + 'ipt>');
</script>
```
[smart]
Закрывающий тег <code>&lt;/script&gt;</code> в строке разделён, чтобы браузер не увидел `</script>` и не посчитал его концом скрипта.
Также используют запись:
```js
document.write('<script src="' + url + '"><\/script>');
```
Здесь `<\/script>` вместо `</script>`: обратный слеш `\` обычно используется для вставки спецсимволов типа `\n`, а если такого спецсимвола нет, в данном случае `\/` не является спецсимволом, то будет проигнорирован. Так что получается такой альтернативный способ безопасно вставить строку `</script>`.
[/smart]
Сервер, получив запрос с такими параметрами, обрабатывает его и, учитывая переданную информацию, генерирует текст скрипта, в котором обычно есть какой-то другой `document.write`, рисующий на этом месте баннер.
**Проблема здесь в том, что загрузка такого скрипта блокирует отрисовку всей страницы.**
То есть, дело даже не в самом `document.write`, а в том, что в страницу вставляется сторонний скрипт, а браузер устроен так, что пока он его не загрузит и не выполнит -- он не будет дальше строить DOM и показывать документ.
Представим на минуту, что сервер `ads.com`, с которого грузится скрипт, работает медленно или вообще завис -- зависнет и наша страница.
Что делать?
В современных браузерах у скриптов есть атрибуты `async` и `defer`, которые разрешают браузеру продолжать обработку страницы, но применить их здесь нельзя, так как рекламный скрипт захочет вызвать `document.write` именно на этом месте, и браузер не должен двигаться вперёд по документу.
Альтернатива -- использовать другие техники вставки рекламы и счётчиков. Примеры вы можете увидеть в коде Google Analytics, Яндекс.Метрики и других.
Если это невозможно -- применяют всякие хитрые оптимизации, например заменяют метод `document.write` своей функцией, и она уже разбирается со скриптами и баннерами.
## Итого
Метод `document.write` (или `writeln`) пишет текст прямо в HTML, как будто он там всегда был.
<ul>
<li>Этот метод редко используется, так как работает только из скриптов, выполняемых в процессе загрузки страницы.
Запуск после загрузки приведёт к очистке документа.</li>
<li>Метод `document.write` очень быстр.
В отличие от установки `innerHTML` и DOM-методов, он не изменяет существующий документ, а работает на стадии текста, до того как DOM-структура сформирована. </li>
<li>Иногда `document.write` используют для добавления скриптов с динамическим URL.
Рекомендуется избегать этого, так как браузер остановится на месте добавления скрипта и будет ждать его загрузки. Если скрипт будет тормозить, то и страница -- тоже.
Поэтому желательно подключать внешние скрипты, используя вставку скрипта через DOM или `async/defer`. Современные системы рекламы и статистики так и делают.
</li>
</ul>

View file

@ -1,43 +0,0 @@
Есть два варианта.
<ol>
<li>Можно использовать свойство `elem.style.cssText` и присвоить стиль в текстовом виде. При этом все присвоенные ранее свойства `elem.style` будут удалены.</li>
<li>Можно назначить подсвойства `elem.style` одно за другим. Этот способ более безопасен, т.к. меняет только явно присваемые свойства.</li>
</ol>
Мы выберем второй путь.
[edit src="solution"]Открыть решение[/edit]
**Описание CSS-свойств:**
```css
.button {
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
border-radius: 8px;
border: 2px groove green;
display: block;
height: 30px;
line-height: 30px;
width: 100px;
text-decoration: none;
text-align: center;
color: red;
font-weight: bold;
}
```
<dl>
<dt>`*-border-radius`</dt>
<dd>Добавляет скругленные углы. Свойство присваивается в вариантах для Firefox `-moz-...`, Chrome/Safari `-webkit-...` и стандартное CSS3-свойство для тех, кто его поддерживает (Opera).</dd>
<dt>`display`</dt>
<dd>По умолчанию, у `A` это свойство имеет значение `display: inline`.</dd>
<dt>`height`, `line-height`</dt>
<dd>Устанавливает высоту и делает текст вертикально центрированным путем установки `line-height` в значение, равное высоте. Такой способ центрирования текста работает, если он состоит из одной строки.</dd>
<dt>`text-align`</dt>
<dd>Центрирует текст горизонтально.</dd>
<dt>`color`, `font-weight`</dt>
<dd>Делает текст красным и жирным.</dd>
</dl>

View file

@ -1,40 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div>
Кнопка:
<!-- создайте элемент и расположите его тут -->
</div>
<script>
var a = document.createElement('a');
a.className = 'button';
a.appendChild(document.createTextNode('Нажми меня'));
a.href = '/';
var s = a.style
s.MozBorderRadius = s.WebkitBorderRadius = s.borderRadius = '8px';
s.border = '2px groove green';
s.display = 'block';
s.height = '30px';
s.lineHeight = '30px';
s.width = '100px';
s.textDecoration = 'none';
s.textAlign = 'center';
s.color = 'red';
s.fontWeight = 'bold';
var div = document.body.children[0];
div.appendChild(a);
</script>
</body>
</html>

View file

@ -1,22 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div>
Кнопка:
<!-- создайте элемент и расположите его тут -->
</div>
<script>
// .. ваш код
</script>
</body>
</html>

View file

@ -1,33 +0,0 @@
# Скругленая кнопка со стилями из JavaScript
[importance 3]
Создайте кнопку в виде элемента `<a>` с заданным стилем, используя JavaScript.
В примере ниже такая кнопка создана при помощи HTML/CSS. В вашем решении кнопка должна создаваться, настраиваться и добавляться в документ при помощи *только JavaScript*, без тегов `<style>` и `<a>`.
```html
<!--+ autorun height="50" -->
<style>
.button {
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
border-radius: 8px;
border: 2px groove green;
display: block;
height: 30px;
line-height: 30px;
width: 100px;
text-decoration: none;
text-align: center;
color: red;
font-weight: bold;
}
</style>
<a class="button" href="/">Нажми меня</a>
```
**Проверьте себя: вспомните, что означает каждое свойство. В чём состоит эффект его появления здесь?**

View file

@ -1,14 +0,0 @@
.notification {
position: fixed;
z-index: 1000;
padding: 5px;
border: 1px solid black;
font: normal 20px Georgia;
background: white;
text-align: center;
}
.welcome {
background: red;
color: yellow;
}

View file

@ -1,65 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="index.css">
</head>
<body>
<h2>Уведомление</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum aspernatur quam ex eaque inventore quod voluptatem adipisci omnis nemo nulla fugit iste numquam ducimus cumque minima porro ea quidem maxime necessitatibus beatae labore soluta voluptatum
magnam consequatur sit laboriosam velit excepturi laborum sequi eos placeat et quia deleniti? Corrupti velit impedit autem et obcaecati fuga debitis nemo ratione iste veniam amet dicta hic ipsam unde cupiditate incidunt aut iure ipsum officiis soluta
temporibus. Tempore dicta ullam delectus numquam consectetur quisquam explicabo culpa excepturi placeat quo sequi molestias reprehenderit hic at nemo cumque voluptates quidem repellendus maiores unde earum molestiae ad.
</p>
<script>
/**
* Показывает уведомление, пропадающее через 1.5 сек
*
* @param options.top {number} вертикальный отступ, в px
* @param options.right {number} правый отступ, в px
* @param options.cssText {string} строка стиля
* @param options.className {string} CSS-класс
* @param options.html {string} HTML-текст для показа
*/
function showNotification(options) {
var notification = document.createElement('div');
notification.className = "notification";
if (options.cssText) {
notification.style.cssText = options.cssText;
}
notification.style.top = (options.top || 0) + 'px'; // can use cssText
notification.style.right = (options.right || 0) + 'px'; // can use cssText
if (options.className) {
notification.classList.add(options.className);
}
notification.innerHTML = options.html;
document.body.appendChild(notification); // over cover
setTimeout(function() {
document.body.removeChild(notification);
}, 1500);
}
// тест работоспособности
var i = 0;
setInterval(function() {
showNotification({
top: 10,
right: 10,
html: 'Привет ' + ++i,
className: "welcome"
});
}, 2000);
</script>
</body>
</html>

View file

@ -1,14 +0,0 @@
.notification {
position: fixed;
z-index: 1000;
padding: 5px;
border: 1px solid black;
font: normal 20px Georgia;
background: white;
text-align: center;
}
.welcome {
background: red;
color: yellow;
}

View file

@ -1,50 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="index.css">
</head>
<body>
<h2>Уведомление</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum aspernatur quam ex eaque inventore quod voluptatem adipisci omnis nemo nulla fugit iste numquam ducimus cumque minima porro ea quidem maxime necessitatibus beatae labore soluta voluptatum
magnam consequatur sit laboriosam velit excepturi laborum sequi eos placeat et quia deleniti? Corrupti velit impedit autem et obcaecati fuga debitis nemo ratione iste veniam amet dicta hic ipsam unde cupiditate incidunt aut iure ipsum officiis soluta
temporibus. Tempore dicta ullam delectus numquam consectetur quisquam explicabo culpa excepturi placeat quo sequi molestias reprehenderit hic at nemo cumque voluptates quidem repellendus maiores unde earum molestiae ad.
</p>
<p>В CSS есть готовый класс notification, который можно ставить уведомлению.</p>
<script>
/**
* Показывает уведомление, пропадающее через 1.5 сек
*
* @param options.top {number} вертикальный отступ, в px
* @param options.right {number} правый отступ, в px
* @param options.cssText {string} строка стиля
* @param options.className {string} CSS-класс
* @param options.html {string} HTML-текст для показа
*/
function showNotification(options) {
// ваш код
}
// тест работоспособности
var i = 0;
setInterval(function() {
showNotification({
top: 10,
right: 10,
html: 'Привет ' + ++i,
className: "welcome"
});
}, 2000);
</script>
</body>
</html>

View file

@ -1,41 +0,0 @@
# Создать уведомление
[importance 5]
Напишите функцию `showNotification(options)`, которая показывает уведомление, пропадающее через 1.5 сек.
Описание функции:
```js
/**
* Показывает уведомление, пропадающее через 1.5 сек
*
* @param options.top {number} вертикальный отступ, в px
* @param options.right {number} правый отступ, в px
* @param options.cssText {string} строка стиля
* @param options.className {string} CSS-класс
* @param options.html {string} HTML-текст для показа
*/
function showNotification(options) {
// ваш код
}
```
Пример использования:
```js
// покажет элемент с текстом "Привет" и классом welcome справа-сверху окна
showNotification({
top: 10,
right: 10,
html: "Привет",
className: "welcome"
});
```
[demo src="solution"]
Элемент уведомления должен иметь CSS-класс `notification`, к которому добавляется класс из `options.className`, если есть. Исходный документ содержит готовые стили.

View file

@ -1,319 +0,0 @@
# Стили, getComputedStyle
Эта глава -- о свойствах стиля, получении о них информации и изменении при помощи JavaScript.
Перед прочтением убедитесь, что хорошо знакомы с [блочной моделью CSS](http://www.w3.org/TR/CSS2/box.html) и понимаете, что такое `padding`, `margin`, `border`.
[cut]
## Стили элемента: свойство style
Объект `element.style` дает доступ к стилю элемента на чтение и запись.
С его помощью можно изменять большинство CSS-свойств, например `element.style.width="100px"` работает так, как будто у элемента в атрибуте прописано `style="width:100px"`.
[warn header="Единицы измерения обязательны в `style`"]
Об этом иногда забывают, но в `style` так же, как и в CSS, нужно указывать единицы измерения, например `px`.
Ни в коем случае не просто `elem.style.width = 100` -- работать не будет.
[/warn]
**Для свойств, названия которых состоят из нескольких слов, используется вотТакаяЗапись:**
```js
//+ no-beautify
background-color => elem.style.backgroundColor
z-index => elem.style.zIndex
border-left-width => elem.style.borderLeftWidth
```
Пример использования `style`:
```js
//+ run
document.body.style.backgroundColor = prompt('background color?', 'green');
```
[warn header="`style.cssFloat` вместо `style.float`"]
Исключением является свойство `float`. В старом стандарте JavaScript слово `"float"` было зарезервировано и недоступно для использования в качестве свойства объекта. Поэтому используется не `elem.style.float`, а `elem.style.cssFloat`.
[/warn]
[smart header="Свойства с префиксами"]
Специфические свойства браузеров, типа `-moz-border-radius`, `-webkit-border-radius`, записываются следующим способом:
```js
button.style.MozBorderRadius = '5px';
button.style.WebkitBorderRadius = '5px';
```
То есть, каждый дефис даёт большую букву.
[/smart]
**Чтобы сбросить поставленный стиль, присваивают в `style` пустую строку: `elem.style.width=""`.**
При сбросе свойства `style` стиль будет взят из CSS.
Например, для того, чтобы спрятать элемент, можно присвоить: `elem.style.display = "none"`.
А вот чтобы показать его обратно -- не обязательно явно указывать другой `display`, наподобие `elem.style.display = "block"`. Можно просто снять поставленный стиль: `elem.style.display = ""`.
```js
//+ run
// если запустить этот код, то <body> "мигнёт"
document.body.style.display = "none";
setTimeout(function() {
document.body.style.display = "";
}, 1000);
```
**Стиль в `style` находится в формате браузера, а не в том, в котором его присвоили.**
Например:
```html
<!--+ run height=100 -->
<body>
<script>
*!*
document.body.style.margin = '20px';
alert( document.body.style.marginTop ); // 20px!
*/!*
*!*
document.body.style.color = '#abc';
alert( document.body.style.color ); // rgb(170, 187, 204)
*/!*
</script>
</body>
```
Обратите внимание на то, как браузер "распаковал" свойство `style.margin`, предоставив для чтения `style.marginTop`. То же самое произойдет и для `border`, `background` и т.д.
[warn header="Свойство `style` мы используем лишь там, где не работают классы"]
В большинстве случаев внешний вид элементов задаётся классами. А JavaScript добавляет или удаляет их. Такой код красив и гибок, дизайн можно легко изменять.
Свойство `style` нужно использовать лишь там, где классы не подходят, например если точное значение цвета/отступа/высоты вычисляется в JavaScript.
[/warn]
### Строка стилей style.cssText
Свойство `style` является специальным объектом, ему нельзя присваивать строку.
Запись `div.style="color:blue"` работать не будет. Но как же, всё-таки, поставить свойство стиля, если хочется задать его строкой?
Можно попробовать использовать атрибут: `elem.setAttribute("style", ...)`, но самым правильным и, главное, кросс-браузерным (с учётом старых IE) решением такой задачи будет использование свойства `style.cssText`.
**Свойство `style.cssText` позволяет поставить стиль целиком в виде строки.**
Например:
```html
<!--+ run -->
<div>Button</div>
<script>
var div = document.body.children[0];
div.style.cssText="*!*color: red !important;*/!* \
background-color: yellow; \
width: 100px; \
text-align: center; \
*!*blabla: 5; \*/!*
";
alert(div.style.cssText);
</script>
```
Браузер разбирает строку `style.cssText` и применяет известные ему свойства. Неизвестные, наподобие `blabla`, большинство браузеров просто проигнорируют.
**При установке `style.cssText` все предыдущие свойства `style` удаляются.**
Итак, `style.cssText` осуществляет полную перезапись `style`. Если же нужно заменить какое-то конкретно свойство стиля, то обращаются именно к нему: `style.color`, `style.width` и т.п, чтобы не затереть что-то важное по ошибке.
Свойство `style.cssText` используют, например, для новосозданных элементов, когда старых стилей точно нет.
### Чтение стиля из style
Записать в стиль очень просто. А как прочитать?
Например, мы хотим узнать размер, отступы элемента, его цвет... Как это сделать?
**Свойство `style` содержит лишь тот стиль, который указан в атрибуте элемента, без учёта каскада CSS.**
Вот так `style` уже ничего не увидит:
```html
<!--+ run height=100 no-beautify -->
<head>
<style> body { color: red; margin: 5px } </style>
</head>
<body>
Красный текст
<script>
*!*
alert(document.body.style.color); //в большинстве браузеров
alert(document.body.style.marginTop); // ничего не выведет
*/!*
</script>
</body>
```
## Полный стиль из getComputedStyle
Итак, свойство `style` дает доступ только к той информации, которая хранится в `elem.style`.
Он не скажет ничего об отступе, если он появился в результате наложения CSS или встроенных стилей браузера:
А если мы хотим, например, сделать анимацию и плавно увеличивать `marginTop` от текущего значения? Как нам сделать это? Ведь для начала нам надо это текущее значение получить.
**Для того, чтобы получить текущее используемое значение свойства, используется метод `window.getComputedStyle`, описанный в стандарте <a href="http://www.w3.org/TR/2000/REC-DOM-Level-2-Style-20001113/css.html">DOM Level 2</a>.**
Его синтаксис таков:
```js
getComputedStyle(element[, pseudo])
```
<dl>
<dt>element</dt>
<dd>Элемент, значения для которого нужно получить</dd>
<dt>pseudo</dt>
<dd>Указывается, если нужен стиль псевдо-элемента, например `"::before"`. Пустая строка или отсутствие аргумента означают сам элемент.</dd>
</dl>
Поддерживается всеми браузерами, кроме IE8-. Следующий код будет работать во всех не-IE браузерах и в IE9+:
```html
<!--+ run height=100 -->
<style>
body {
margin: 10px
}
</style>
<body>
<script>
var computedStyle = getComputedStyle(document.body);
alert( computedStyle.marginTop ); // выведет отступ в пикселях
alert( computedStyle.color ); // выведет цвет
</script>
</body>
```
[smart header="Вычисленное (computed) и окончательное (resolved) значения"]
В CSS есть две концепции:
<ol>
<li>*Вычисленное* (computed) значение -- это то, которое получено после применения всех правил CSS и CSS-наследования. Например, `width: auto` или `font-size: 125%`.</li>
<li>*Окончательное* ([resolved](http://dev.w3.org/csswg/cssom/#resolved-values)) значение -- непосредственно применяемое к элементу. При этом все размеры приводятся к пикселям, например `width: 212px` или `font-size: 16px`. В некоторых браузерах пиксели могут быть дробными.</li>
</ol>
Когда-то `getComputedStyle` задумывалось для возврата вычисленного значения, но со временем оказалось, что окончательное гораздо удобнее.
Поэтому сейчас в целом все значения возвращаются именно окончательные, кроме некоторых небольших глюков в браузерах, которые постепенно вычищаются.
[/smart]
[warn header="`getComputedStyle` требует полное свойство!"]
Для правильного получения значения нужно указать точное свойство. Например: `paddingLeft`, `marginTop`, `borderLeftWidth`.
**При обращении к сокращенному: `padding`, `margin`, `border` -- правильный результат не гарантируется.**
Действительно, допустим свойства `paddingLeft/paddingTop` взяты из разных классов CSS. Браузер не обязан объединять их в одно свойство `padding`. Иногда, в простейших случаях, когда свойство задано сразу целиком, `getComputedStyle` сработает для сокращённого свойства, но не во всех браузерах.
Например, некоторые браузеры (Chrome) выведут `10px` в документе ниже, а некоторые (Firefox) -- нет:
```html
<!--+ run -->
<style>
body {
margin: 10px;
}
</style>
<script>
var style = getComputedStyle(document.body);
alert( style.margin ); // в Firefox пустая строка
</script>
```
[/warn]
[smart header="Стили посещенных ссылок -- тайна!"]
У посещенных ссылок может быть другой цвет, фон, чем у обычных. Это можно поставить в CSS с помощью псевдокласса `:visited`.
Но `getComputedStyle` не дает доступ к этой информации, чтобы произвольная страница не могла определить, посещал ли пользователь ту или иную ссылку.
Кроме того, большинство браузеров запрещают применять к `:visited` CSS-стили, которые могут изменить геометрию элемента, чтобы даже окольным путем нельзя было это понять. В целях безопасности.
[/smart]
## currentStyle для IE8-
В IE8- нет `getComputedStyle`, но у элементов есть свойство <a href="http://msdn.microsoft.com/en-us/library/ms536497.aspx">currentStyle</a>, которое возвращает вычисленное (computed) значение: уже с учётом CSS-каскада, но не всегда в окончательном формате.
Чтобы код работал и в старых и новых браузерах, обычно пишут кросс-браузерный код, наподобие такого:
```js
function getStyle(elem) {
return window.getComputedStyle ? getComputedStyle(elem, "") : elem.currentStyle;
}
```
Если вы откроете такой документ в IE8-, то размеры будут в процентах, а в современных браузерах -- в пикселях.
```html
<!--+ run height=100 -->
<style>
body {
margin: 10%
}
</style>
<body>
<script>
var elem = document.body;
function getStyle(elem) {
return window.getComputedStyle ? getComputedStyle(elem, "") : elem.currentStyle;
}
var marginTop = getStyle(elem).marginTop;
alert( marginTop ); // IE8-: 10%, иначе пиксели
</script>
</body>
```
[smart header="IE8-: перевод `pt,em,%` из `currentStyle` в пиксели"]
Эта информация -- дополнительная, она не обязательна для освоения.
В IE для того, чтобы получить из процентов реальное значение в пикселях существует метод "runtimeStyle+pixel", [описанный Дином Эдвардсом](http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291).
Он основан на свойствах `runtimeStyle` и `pixelLeft`, работающих только в IE.
В [edit src="getiecomputedstyle"]песочнице[/edit] вы можете найти функцию `getIEComputedStyle(elem, prop)`, которая получает значение в пикселях для свойства `prop`, используя `elem.currentStyle` и метод Дина Эдвардса, и пример её применения.
Если вам интересно, как он работает, ознакомьтесь со свойствами с <a href="http://msdn.microsoft.com/en-us/library/ms535889(v=vs.85).aspx">runtimeStyle</a> и <a href="http://msdn.microsoft.com/en-us/library/ms531129%28VS.85%29.aspx">pixelLeft</a> в MSDN и раскройте код.
Конечно, это актуально только для IE8- и полифиллов.
[/smart]
## Итого
Все DOM-элементы предоставляют следующие свойства.
<ul>
<li>Свойство `style` -- это объект, в котором CSS-свойства пишутся `вотТакВот`. Чтение и изменение его свойств -- это, по сути, работа с компонентами атрибута `style`.</li>
<li>`style.cssText` -- строка стилей для чтения или записи. Аналог полного атрибута `style`.</li>
<li>Свойство `currentStyle`(IE8-) и метод `getComputedStyle` (IE9+, стандарт) позволяют получить реальное, применённое сейчас к элементу свойство стиля с учётом CSS-каскада и браузерных стилей по умолчанию.
При этом `currentStyle` возвращает значение из CSS, до окончательных вычислений, а `getComputedStyle` -- окончательное, непосредственно применённое к элементу (как правило).</li>
</ul>
Более полная информация о свойстве `style`, включающая другие, реже используемые методы работы с ним, доступна [в документации](https://developer.mozilla.org/en-US/docs/DOM/CSSStyleDeclaration).

View file

@ -1,18 +0,0 @@
function getIEComputedStyle(elem, prop) {
var value = elem.currentStyle[prop] || 0;
// we use 'left' property as a place holder so backup values
var leftCopy = elem.style.left;
var runtimeLeftCopy = elem.runtimeStyle.left;
// assign to runtimeStyle and get pixel value
elem.runtimeStyle.left = elem.currentStyle.left;
elem.style.left = (prop === "fontSize") ? "1em" : value;
value = elem.style.pixelLeft + "px";
// restore values for left
elem.style.left = leftCopy;
elem.runtimeStyle.left = runtimeLeftCopy;
return value;
}

View file

@ -1,30 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="getiecomputedstyle.js"></script>
</head>
<body>
<style>
#margin-test {
margin: 1%;
border: 1px solid black;
}
</style>
<div id="margin-test">Тестовый элемент с margin 1%</div>
<script>
var elem = document.getElementById('margin-test');
if (!window.getComputedStyle) { // старые IE
document.write(getIEComputedStyle(elem, 'marginTop'));
} else {
document.write('Пример работает только в IE8-');
}
</script>
</body>
</html>

View file

@ -1 +0,0 @@
Решение: `elem.scrollHeight - elem.scrollTop - elem.clientHeight`.

View file

@ -1,9 +0,0 @@
# Найти размер прокрутки снизу
[importance 5]
Свойство `elem.scrollTop` содержит размер прокрученной области при отсчете сверху. А как подсчитать размер прокрутки снизу?
Напишите соответствующее выражение для произвольного элемента `elem`.
Проверьте: если прокрутки нет вообще или элемент полностью прокручен -- оно должно давать ноль.

View file

@ -1,23 +0,0 @@
Создадим элемент с прокруткой, но без `border` и `padding`. Тогда разница между его полной шириной `offsetWidth` и внутренней `clientWidth` будет равна как раз прокрутке:
```js
//+ run
// создадим элемент с прокруткой
var div = document.createElement('div');
div.style.overflowY = 'scroll';
div.style.width = '50px';
div.style.height = '50px';
// при display:none размеры нельзя узнать
// нужно, чтобы элемент был видим,
// visibility:hidden - можно, т.к. сохраняет геометрию
div.style.visibility = 'hidden';
document.body.appendChild(div);
var scrollWidth = div.offsetWidth - div.clientWidth;
document.body.removeChild(div);
alert( scrollWidth );
```

View file

@ -1,7 +0,0 @@
# Узнать ширину полосы прокрутки
[importance 3]
Напишите код, который возвращает ширину стандартной полосы прокрутки. Именно самой полосы, где ползунок. Обычно она равна `16px`, в редких и мобильных браузерах может колебаться от `14px` до `18px`, а кое-где даже равна `0px`.
P.S. Ваш код должен работать на любом HTML-документе, независимо от его содержимого.

View file

@ -1,29 +0,0 @@
Нам нужно создать `div` с такими же размерами и вставить его на место "переезжающего".
Один из вариантов -- это просто клонировать элемент.
Если делать это при помощи `div.cloneNode(true)`, то склонируется все содержимое, которого может быть много. Обычно нам это не нужно, поэтому можно использовать `div.cloneNode(false)` для клонирования элемента со стилями, и потом поправить его `width/height`.
Можно и просто создать новый `div` и поставить ему нужные размеры.
**Всё, кроме `margin`, можно получить из свойств DOM-элемента, а `margin` -- только через `getComputedStyle`.**
Причём `margin` мы обязаны поставить, так как иначе наш элемент при вставке будет вести себя иначе, чем исходный.
Код:
```js
var div = document.getElementById('moving-div');
var placeHolder = document.createElement('div');
placeHolder.style.height = div.offsetHeight + 'px';
// можно и width, но в этом примере это не обязательно
// IE || другой браузер
var computedStyle = div.currentStyle || getComputedStyle(div, '');
placeHolder.style.marginTop = computedStyle.marginTop; // (1)
placeHolder.style.marginBottom = computedStyle.marginBottom;
```
В строке `(1)` использование полного название свойства `"marginTop"` гарантирует, что полученное значение будет корректным.

View file

@ -1,50 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
#moving-div {
border: 5px groove green;
padding: 5px;
margin: 10px;
background-color: yellow;
}
</style>
</head>
<body>
Before Before Before
<div id="moving-div">
Text Text Text
<br> Text Text Text
<br>
</div>
After After After
<script>
var div = document.getElementById('moving-div')
var placeHolder = document.createElement('div')
placeHolder.style.height = div.offsetHeight + 'px'
var computedStyle = div.currentStyle || getComputedStyle(div, null)
placeHolder.style.marginTop = computedStyle.marginTop // full prop name
placeHolder.style.marginBottom = computedStyle.marginBottom
// highlight it for demo purposes
placeHolder.style.backgroundColor = '#C0C0C0'
document.body.insertBefore(placeHolder, div)
div.style.position = 'absolute'
div.style.right = div.style.top = 0
</script>
</body>
</html>

View file

@ -1,37 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
#moving-div {
border: 5px groove green;
background-color: yellow;
padding: 5px;
margin: 10px;
}
</style>
</head>
<body>
Before Before Before
<div id="moving-div">
Text Text Text
<br> Text Text Text
<br>
</div>
After After After
<script>
// .. Add your code here
var div = document.getElementById('moving-div')
div.style.position = 'absolute'
div.style.right = div.style.top = 0
</script>
</body>
</html>

View file

@ -1,52 +0,0 @@
# Подменить div на другой с таким же размером
[importance 3]
Посмотрим следующий случай из жизни. Был текст, который, в частности, содержал `div` с зелеными границами:
```html
<!--+ run no-beautify -->
<style>
#moving-div {
border: 5px groove green;
padding: 5px;
margin: 10px;
background-color: yellow;
}
</style>
Before Before Before
<div id="moving-div">
Text Text Text<br>
Text Text Text<br>
</div>
After After After
```
Программист Валера из вашей команды написал код, который позиционирует его абсолютно и смещает в правый верхний угол. Вот этот код:
```js
var div = document.getElementById('moving-div');
div.style.position = 'absolute';
div.style.right = div.style.top = 0;
```
Побочным результатом явилось смещение текста, который раньше шел после `DIV`. Теперь он поднялся вверх:
[iframe height=90 src="source"]
**Допишите код Валеры, сделав так, чтобы текст оставался на своем месте после того, как `DIV` будет смещен.**
Сделайте это путем создания вспомогательного `DIV` с теми же `width`, `height`, `border`, `margin`, `padding`, что и у желтого `DIV`.
Используйте только JavaScript, без CSS.
Должно быть так (новому блоку задан фоновый цвет для демонстрации):
[iframe height=140 src="solution"]

View file

@ -1,41 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
#field {
width: 200px;
border: 10px groove black;
background-color: #00FF00;
position: relative;
}
#ball {
position: absolute;
}
</style>
</head>
<body>
<div id="field">
<img src="https://js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
<script>
var ball = document.getElementById('ball')
var field = document.getElementById('field')
// ball.offsetWidth=0 before image loaded!
// to fix: set width
ball.style.left = Math.round(field.clientWidth / 2) + 'px'
ball.style.top = Math.round(field.clientHeight / 2) + 'px'
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1,51 +0,0 @@
При абсолютном позиционировании мяча внутри поля его координаты `left/top` отсчитываются от **внутреннего** угла поля, например верхнего-левого:
<img src="field.png">
Метрики для внутренней зоны поля -- это `clientWidth/Height`.
Центр - это `(clientWidth/2, clientHeight/2)`.
Но если мы установим мячу такие значения `ball.style.left/top`, то в центре будет не сам мяч, а его левый верхний угол:
```js
var ball = document.getElementById('ball');
var field = document.getElementById('field');
ball.style.left = Math.round(field.clientWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2) + 'px';
```
[iframe hide="Нажмите, чтобы посмотреть текущий результат" height=180 src="ball-half"]
Для того, чтобы центр мяча находился в центре поля, нам нужно сместить мяч на половину его ширины влево и на половину его высоты вверх.
```js
var ball = document.getElementById('ball');
var field = document.getElementById('field');
ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px';
```
**Внимание, подводный камень!**
Код выше стабильно работать не будет, потому что `IMG` идет без ширины/высоты:
```html
<img src="ball.png" id="ball">
```
**Высота и ширина изображения неизвестны браузеру до тех пор, пока оно не загрузится, если размер не указан явно.**
После первой загрузки изображение уже будет в кеше браузера, и его размеры будут известны. Но когда браузер впервые видит документ -- он ничего не знает о картинке, поэтому значение `ball.offsetWidth` равно `0`. Вычислить координаты невозможно.
Чтобы это исправить, добавим `width/height` к картинке:
```html
<img src="ball.png" *!*width="40" height="40"*/!* id="ball">
```
Теперь браузер всегда знает ширину и высоту, так что все работает. Тот же эффект дало бы указание размеров в CSS.
[edit src="solution"]Полный код решения[/edit]

View file

@ -1,41 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
#field {
width: 200px;
border: 10px groove black;
background-color: #00FF00;
position: relative;
}
#ball {
position: absolute;
}
</style>
</head>
<body>
<div id="field">
<img src="https://js.cx/clipart/ball.svg" width="40" height="40" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
<script>
var ball = document.getElementById('ball')
var field = document.getElementById('field')
// ball.offsetWidth=0 before image loaded!
// to fix: set width
ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px'
ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px'
</script>
</body>
</html>

View file

@ -1,30 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
#field {
width: 200px;
border: 10px groove black;
background-color: #00FF00;
position: relative;
}
#ball {
position: absolute;
}
</style>
</head>
<body>
<div id="field">
<img src="https://js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
</body>
</html>

View file

@ -1,19 +0,0 @@
# Поместите мяч в центр поля
[importance 5]
Поместите мяч в центр поля.
Исходный документ выглядит так:
[iframe src="source" edit link height=180]
**Используйте JavaScript, чтобы поместить мяч в центр:**
[iframe src="solution" height=180]
<ul>
<li>Менять CSS нельзя, мяч должен переносить в центр ваш скрипт, через установку нужных стилей элемента.</li>
<li>JavaScript-код должен работать при разных размерах мяча (`10`, `20`, `30` пикселей) без изменений.</li>
<li>JavaScript-код должен работать при различных размерах и местоположениях поля на странице без изменений. Также он не должен зависеть от ширины рамки поля `border`.</li>
</ul>
P.S. Да, центрирование можно сделать при помощи чистого CSS, но задача именно на JavaScript. Далее будут другие темы и более сложные ситуации, когда JavaScript будет уже точно необходим, это -- своего рода "разминка".

View file

@ -1,43 +0,0 @@
# Решение через width: auto
Вначале рассмотрим решение через "умную" установку CSS-свойства.
Они могут быть разными. Самое простое выглядит так:
```js
elem.style.width = 'auto';
```
Такой способ работает, так как `<div>` по умолчанию распахивается на всю ширину.
Конечно, такое решение не будет работать для элементов, которые сами по себе не растягиваются, например в случае со `<span>` или при наличии `position: absolute`.
Обратим внимание, такой вариант был бы неверен:
```js
elem.style.width = '100%';
```
По умолчанию в CSS ширина `width` -- это то, что *внутри `padding`*, а проценты отсчитываются от ширины родителя. То есть, ставя ширину в `100%`, мы говорим: "внутренняя область должна занимать `100%` ширины родителя". А в элементе есть ещё `padding`, которые в итоге вылезут наружу.
Можно бы поменять блочную модель, указав `box-sizing` через свойство `elem.style.boxSizing`, но такое изменение потенциально может затронуть много других свойств, поэтому нежелательно.
# Точное вычисление
Альтернатива -- вычислить ширину родителя через `clientWidth`.
Доступную внутреннюю ширину родителя можно получить, вычитая из `clientWidth` размеры `paddingLeft/paddingRight`, и затем присвоить её элементу:
```js
var bodyClientWidth = document.body.clientWidth;
var style = getComputedStyle(elem);
*!*
var bodyInnerWidth = bodyClientWidth - parseInt(style.paddingLeft) - parseInt(style.paddingRight);
*/!*
elem.style.width = bodyInnerWidth + 'px';
```
Такое решение будет работать всегда, вне зависимости от типа элемента. Конечно, при изменении размеров окна браузера ширина не адаптируется к новому размеру автоматически, как с `width:auto`. Это недостаток. Его, конечно, тоже можно обойти при помощи событий (изучим далее), но как общий рецепт -- если CSS может решить задачу -- лучше использовать CSS.

View file

@ -1,53 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#elem {
width: 200px;
height: 150px;
background-color: red;
padding: 20px;
overflow: auto;
/* position: absolute */
}
body {
border: 1px solid black;
}
</style>
</head>
<body>
<div id="elem">
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст
</div>
<script>
var elem = document.getElementById("elem");
// неверно!
//elem.style.width = '100%';
// верно (так как обычный div по умолчанию растягивается во всю ширину)
//elem.style.width = 'auto';
// верно (решение с JS)
var bodyWidth = document.body.clientWidth;
var style = getComputedStyle(elem);
var bodyInnerWidth = bodyWidth - parseInt(style.paddingLeft) - parseInt(style.paddingRight);
elem.style.width = bodyInnerWidth + 'px';
</script>
</body>
</html>

View file

@ -1,39 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#elem {
width: 200px;
height: 150px;
background-color: red;
padding: 20px;
overflow: auto;
}
body {
border: 1px solid black;
}
</style>
</head>
<body>
<div id="elem">
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст
</div>
<script>
var elem = document.getElementById("elem");
// ... ваш код
</script>
</body>
</html>

View file

@ -1,15 +0,0 @@
# Расширить элемент
[importance 4]
В `<body>` есть элемент `<div>` с заданной шириной `width`.
Задача -- написать код, который "распахнет" `<div>` по ширине на всю страницу.
Исходный документ (`<div>` содержит текст и прокрутку):
[iframe height=220 src="source"]
P.S. Пользоваться следует исключительно средствами JS, CSS в этой задаче менять нельзя. Также ваш код должен быть универсален и не ломаться, если цифры в CSS станут другими.
P.P.S. При расширении элемент `<div>` не должен вылезти за границу `<body>`.

View file

@ -1,11 +0,0 @@
Отличия:
<ol>
<li>`getComputedStyle` не работает в IE8-.</li>
<li>`clientWidth` возвращает число, а `getComputedStyle(...).width` -- строку, на конце `px`.</li>
<li>`getComputedStyle` не всегда даст ширину, он может вернуть, к примеру, `"auto"` для инлайнового элемента.</li>
<li>`clientWidth` соответствует внутренней видимой области элемента, *включая `padding`, а CSS-ширина `width` при стандартном значении `box-sizing` соответствует зоне *внутри `padding`*.</li>
<li>Если есть полоса прокрутки, то некоторые браузеры включают её ширину в `width`, а некоторые -- нет.
Свойство `clientWidth`, с другой стороны, полностью кросс-браузерно. Оно всегда обозначает размер *за вычетом прокрутки*, т.е. реально доступный для содержимого.</li>
</ol>

View file

@ -1,7 +0,0 @@
# В чём отличие "width" и "clientWidth" ?
[importance 5]
В чём отличия между `getComputedStyle(elem).width` и `elem.clientWidth`?
Укажите хотя бы три отличия, лучше -- больше.

View file

@ -1,317 +0,0 @@
# Размеры и прокрутка элементов
Для того, чтобы показывать элементы на произвольных местах страницы, необходимо во-первых, знать CSS-позиционирование, а во-вторых -- уметь работать с "геометрией элементов" из JavaScript.
В этой главе мы поговорим о размерах элементов DOM, способах их вычисления и *метриках* -- различных свойствах, которые содержат эту информацию.
[cut]
## Образец документа
Мы будем использовать для примера вот такой элемент, у которого есть рамка (border), поля (padding), и прокрутка:
```html
<!--+ no-beautify -->
<div id="example">
...Текст...
</div>
<style>
#example {
width: 300px;
height: 200px;
border: 25px solid #E8C48F; /* рамка 25px */
padding: 20px; /* поля 20px */
overflow: auto; /* прокрутка */
}
</style>
```
У него нет отступов `margin`, в этой главе они не важны, так как метрики касаются именно размеров самого элемента, отступы в них не учитываются.
Результат выглядит так:
<img src="metric-css.png">
Вы можете открыть [edit src="metric"]этот документ в песочнице[/edit].
[smart header="Внимание, полоса прокрутки!"]
В иллюстрации выше намеренно продемонстрирован самый сложный и полный случай, когда у элемента есть ещё и полоса прокрутки.
В этом случае полоса прокрутки "отодвигает" содержимое вместе с `padding` влево, отбирая у него место.
Именно поэтому ширина содержимого обозначена как `content width` и равна `284px`, а не `300px`, как в CSS.
Точное значение получено в предположении, что ширина полосы прокрутки равна `16px`, то есть после её вычитания на содержимое остаётся `300 - 16 = 284px`. Конечно, она сильно зависит от браузера, устройства, ОС.
Мы должны в точности понимать, что происходит с размерами элемента при наличии полосы прокрутки, поэтому на картинке выше это отражено.
[/smart]
[smart header="Поле `padding` заполнено текстом"]
Обычно поля `padding` изображают пустыми, но так как текста много, то он заполняет нижнее поле `padding-bottom` в примере выше.
Во избежание путаницы заметим, что `padding` там, всё же, есть. Поля `padding` по CSS в элементе выше одинаковы со всех сторон. А такое заполнение -- нормальное поведение браузера.
[/smart]
## Метрики
У элементов существует ряд свойств, содержащих их внешние и внутренние размеры. Мы будем называть их "метриками".
Метрики, в отличие от свойств CSS, содержат числа, всегда в пикселях и без единиц измерения на конце.
Вот общая картина:
<img src="metric-all.png">
На картинке все они с трудом помещаются, но, как мы увидим далее, их значения просты и понятны.
Будем исследовать их снаружи элемента и вовнутрь.
## offsetParent, offsetLeft/Top
Ситуации, когда эти свойства нужны, можно перечислить по пальцам. Они возникают действительно редко. Как правило, эти свойства используют, потому что не знают средств правильной работы с координатами, о которых мы поговорим позже.
Несмотря на то, что эти свойства нужны реже всего, они -- самые "внешние", поэтому начнём с них.
**В `offsetParent` находится ссылка на родительский элемент в смысле отображения на странице.**
Уточним, что имеется в виду.
Когда браузер рисует страницу, то он высчитывает дерево расположения элементов, иначе говоря "дерево геометрии" или "дерево рендеринга", которое содержит всю информацию о размерах.
При этом одни элементы естественным образом рисуются внутри других. Но, к примеру, если у элемента стоит `position:absolute`, то его расположение вычисляется уже не относительно непосредственного родителя `parentNode`, а относительно ближайшего <a href="http://www.w3.org/TR/CSS21/visuren.html#position-props">позиционированного элемента</a> (т.е. свойство `position` которого не равно `static`), или `BODY`, если такой отсутствует.
Получается, что элемент имеет в дополнение к обычному родителю в DOM -- ещё одного "родителя по позиционированию", то есть относительно которого он рисуется. Этот элемент и будет в свойстве `offsetParent`.
**Свойства `offsetLeft/Top` задают смещение относительно `offsetParent`.**
В примере ниже внутренний `<div>` имеет DOM-родителя `<form>`, но `offsetParent` у него `<main>`, и сдвиги относительно его верхнего-левого угла будут в `offsetLeft/Top`:
```html
<main style="position: relative">
<form>
<div id="example" style="position: absolute; left: 180px; top: 180px">...</div>
</form>
</main>
```
<img src="metric-offset-parent.png">
## offsetWidth/Height
Теперь переходим к самому элементу.
Эти два свойства -- самые простые. Они содержат "внешнюю" ширину/высоту элемента, то есть его полный размер, включая рамки `border`.
<img src="metric-offset-width-height.png">
Для нашего элемента:
<ul>
<li>`offsetWidth = 390` -- внешняя ширина блока, её можно получить сложением CSS-ширины (`300px`, но её часть на рисунке выше отнимает прокрутка, поэтому `284 + 16`), полей(`2*20px`) и рамок (`2*25px`).</li>
<li>`offsetHeight = 290` -- внешняя высота блока.</li>
</ul>
[smart header="Метрики для невидимых элементов равны нулю."]
Координаты и размеры в JavaScript устанавливаются только для *видимых* элементов.
Для элементов с `display:none` или находящихся вне документа дерево рендеринга не строится. Для них метрики равны нулю. Кстати, и `offsetParent` для таких элементов тоже `null`.
**Это дает нам замечательный способ для проверки, виден ли элемент**:
```js
function isHidden(elem) {
return !elem.offsetWidth && !elem.offsetHeight;
}
```
<ul>
<li>Работает, даже если родителю элемента установлено свойство `display:none`.</li>
<li>Работает для всех элементов, кроме `TR`, с которым возникают некоторые проблемы в разных браузерах. Обычно, проверяются не `TR`, поэтому всё ок.</li>
<li>Считает элемент видимым, даже если позиционирован за пределами экрана или имеет свойство `visibility:hidden`.</li>
<li>"Схлопнутый" элемент, например пустой `div` без высоты и ширины, будет считаться невидимым.</li>
</ul>
[/smart]
## clientTop/Left
Далее внутри элемента у нас рамки `border`.
Для них есть свойства-метрики `clientTop` и `clientLeft`.
В нашем примере:
<ul>
<li>`clientLeft = 25` -- ширина левой рамки</li>
<li>`clientTop = 25` -- ширина верхней рамки</li>
</ul>
<img src="metric-client-left-top.png">
...Но на самом деле они -- вовсе не рамки, а отступ внутренней части элемента от внешней.
В чём же разница?
Она возникает тогда, когда документ располагается *справа налево* (операционная система на арабском языке или иврите). Полоса прокрутки в этом случае находится слева, и тогда свойство `clientLeft` включает в себя еще и ширину полосы прокрутки.
Получится так:
<img src="metric-client-left-top-rtl.png">
## clientWidth/Height
Эти свойства -- размер элемента внутри рамок `border`.
Они включают в себя ширину содержимого `width` вместе с полями `padding`, но без прокрутки.
<img src="metric-client-width-height.png">
На рисунке выше посмотрим вначале на `clientHeight`, её посчитать проще всего. Прокрутки нет, так что это в точности то, что внутри рамок: CSS-высота `200px` плюс верхнее и нижнее поля `padding` (по `20px`), итого `240px`.
На рисунке нижний `padding` заполнен текстом, но это неважно: по правилам он всегда входит в `clientHeight`.
Теперь `clientWidth` -- ширина содержимого здесь не равна CSS-ширине, её часть "съедает" полоса прокрутки.
Поэтому в `clientWidth` входит не CSS-ширина, а реальная ширина содержимого `284px` плюс левое и правое поля `padding` (по `20px`), итого `324px`.
**Если `padding` нет, то `clientWidth/Height` в точности равны размеру области содержимого, внутри рамок и полосы прокрутки.**
<img src="metric-client-width-nopadding.png">
Поэтому в тех случаях, когда мы точно знаем, что `padding` нет, их используют для определения внутренних размеров элемента.
## scrollWidth/Height
Эти свойства -- аналоги `clientWidth/clientHeight`, но с учетом прокрутки.
Свойства `clientWidth/clientHeight` относятся только к видимой области элемента, а `scrollWidth/scrollHeight` добавляют к ней прокрученную (которую не видно) по горизонтали/вертикали.
<img src="metric-scroll-width-height.png">
На рисунке выше:
<ul>
<li>`scrollHeight = 723` -- полная внутренняя высота, включая прокрученную область.</li>
<li>`scrollWidth = 324` -- полная внутренняя ширина, в данном случае прокрутки нет, поэтому она равна `clientWidth`.</li>
</ul>
Эти свойства можно использовать, чтобы "распахнуть" элемент на всю ширину/высоту, таким кодом:
```js
element.style.height = element.scrollHeight + 'px';
```
[online]
[pre no-typography]
Нажмите на кнопку, чтобы распахнуть элемент:
<div id="scrollOpen" style="width:300px;height:200px; padding: 0;overflow: auto; border:1px solid black;">текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст</div>
<button style="padding:0" onclick="document.getElementById('scrollOpen').style.height = document.getElementById('scrollOpen').scrollHeight + 'px'">element.style.height = element.scrollHeight + 'px'</button>
[/pre]
[/online]
## scrollLeft/scrollTop
Свойства `scrollLeft/scrollTop` -- ширина/высота невидимой, прокрученной в данный момент, части элемента слева и сверху.
Следующее иллюстрация показывает значения `scrollHeight` и `scrollTop` для блока с вертикальной прокруткой.
<img src="metric-scroll-top.png">
[smart header="`scrollLeft/scrollTop` можно изменять"]
В отличие от большинства свойств, которые доступны только для чтения, значения `scrollLeft/scrollTop` можно изменить, и браузер выполнит прокрутку элемента.
[online]
При клике на следующий элемент будет выполняться код `elem.scrollTop += 10`. Поэтому он будет прокручиваться на `10px` вниз:
<div onclick="this.scrollTop+=10" style="cursor:pointer;border:1px solid black;width:100px;height:80px;overflow:auto">Кликни<br>Меня<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9</div>
[/online]
[/smart]
## Не стоит брать width/height из CSS
Мы рассмотрели метрики -- свойства, которые есть у DOM-элементов. Их обычно используют для получения их различных высот, ширин и прочих расстояний.
Теперь несколько слов о том, как *не* надо делать.
Как мы знаем, CSS-высоту и ширину можно установить с помощью `elem.style` и извлечь, используя `getComputedStyle`, которые в подробностях обсуждаются в главе [](/styles-and-classes).
Получение ширины элемента может быть таким:
```js
//+ run
var elem = document.body;
alert( getComputedStyle(elem).width ); // вывести CSS-ширину для elem
```
Не лучше ли получать ширину так, вместо метрик? Вовсе нет!
<ol>
<li>Во-первых, CSS-свойства `width/height` зависят от другого свойства -- `box-sizing`, которое определяет, что такое, собственно, эти ширина и высота. Получается, что изменение `box-sizing`, к примеру, для более удобной вёрстки, сломает такой JavaScript.</li>
<li>Во-вторых, в CSS свойства `width/height` могут быть равны `auto`, например, для инлайн-элемента:
```html
<!--+ run -->
<span id="elem">Привет!</span>
<script>
*!*
alert( getComputedStyle(elem).width ); // auto
*/!*
</script>
```
Конечно, с точки зрения CSS размер `auto` -- совершенно нормально, но нам-то в JavaScript нужен конкретный размер в пикселях, который мы могли бы использовать для вычислений. Получается, что в данном случае ширина `width` из CSS вообще бесполезна.
</li>
</ol>
Есть и ещё одна причина.
Полоса прокрутки -- причина многих проблем и недопониманий. Как говорится, "дьявол кроется в деталях". Недопустимо, чтобы наш код работал на элементах без прокрутки и начинал "глючить" с ней.
Как мы говорили ранее, при наличии вертикальной полосы прокрутки, в зависимости от браузера, устройства и операционной системы, она может сдвинуть содержимое.
Получается, что реальная ширина содержимого меньше CSS-ширины. И это учитывают свойства `clientWidth/clientHeight`.
...Но при этом некоторые браузеры также учитывают это в результате `getComputedStyle(elem).width`, то есть возвращают реальную внутреннюю ширину, а некоторые -- именно CSS-свойство. Эти кросс-браузерные отличия -- ещё один повод не использовать такой подход, а использовать свойства-метрики.
[online]
Если ваш браузер показывает полосу прокрутки (например, под Windows почти все браузеры так делают), то вы можете протестировать это сами, нажав на кнопку в ифрейме ниже.
[iframe src="cssWidthScroll" link border=1]
У элемента с текстом в стилях указано `width:300px`.
На момент написания этой главы при тестировании в Chrome под Windows `alert` выводил `283px`, а в Firefox -- `300px`. При этом оба браузера показывали прокрутку. Это из-за того, что Firefox возвращал именно CSS-ширину, а Chrome -- реальную ширину, за вычетом прокрутки.
[/online]
Описанные разночтения касаются только чтения свойства `getComputedStyle(...).width` из JavaScript, визуальное отображение корректно в обоих случаях.
## Итого
У элементов есть следующие метрики:
<ul>
<li>`offsetParent` -- "родитель по дереву рендеринга" -- ближайшая ячейка таблицы, body для статического позиционирования или ближайший позиционированный элемент для других типов позиционирования.</li>
<li>`offsetLeft/offsetTop` -- позиция в пикселях левого верхнего угла блока, относительно его `offsetParent`.</li>
<li>`offsetWidth/offsetHeight` -- "внешняя" ширина/высота блока, включая рамки.</li>
<li>`clientLeft/clientTop` -- отступ области содержимого от левого-верхнего угла элемента. Если операционная система располагает вертикальную прокрутку справа, то равны ширинам левой/верхней рамки, если же слева (ОС на иврите, арабском), то `clientLeft` включает в себя прокрутку.
</li>
<li>`clientWidth/clientHeight` -- ширина/высота содержимого вместе с полями `padding`, но без полосы прокрутки.</li>
<li>`scrollWidth/scrollHeight` -- ширина/высота содержимого, включая прокручиваемую область. Включает в себя `padding` и не включает полосы прокрутки.</li>
<li>`scrollLeft/scrollTop` -- ширина/высота прокрученной части документа, считается от верхнего левого угла.</li>
</ul>
Все свойства, доступны только для чтения, кроме `scrollLeft/scrollTop`. Изменение этих свойств заставляет браузер прокручивать элемент.
В этой главе мы считали, что страница находится в режиме соответствия стандартам. В режиме совместимости -- некоторые старые браузеры требуют `document.body` вместо `documentElement`, в остальном всё так же. Конечно, по возможности, стоит использовать только режим соответствия стандарту.

View file

@ -1,26 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="elem" style="overflow-y:scroll;width:300px;height:200px;border:1px solid black">
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
текст текст текст текст текст текст текст текст текст текст текст текст текст текст
</div>
<script>
// FF: 200, Ch/Sa: 184, Op: 200, IE9: 184, IE8:200
</script>
У элемента стоит <code>style="width:300px"</code>
<br>
<button onclick="alert( getComputedStyle(elem).width )">alert( getComputedStyle(elem).width )</button>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Some files were not shown because too many files have changed in this diff Show more