renovations

This commit is contained in:
Ilya Kantor 2015-02-21 14:58:02 +03:00
parent a62682e188
commit 35081a779a
115 changed files with 439 additions and 325 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

View file

@ -1,4 +1,6 @@
Для решения этой задачи достаточно создать две функции: `valueToPosition` будет получать по значению положение бегунка, а `positionToValue` -- наоборот, транслировать текущую координату бегунка в значение.
Как сопоставить позицию слайдера и значение? Как сопоставить позицию слайдера и значение?
Для этого посмотрим крайние значения слайдера. Допустим, размер бегунка `10px`. Для этого посмотрим крайние значения слайдера. Допустим, размер бегунка `10px`.
@ -17,10 +19,16 @@ pixelsPerValue = (sliderElem.clientWidth-thumbElem.clientWidth) / max;
Используя `pixelsPerValue` мы сможем переводить позицию бегунка в значение и обратно. Используя `pixelsPerValue` мы сможем переводить позицию бегунка в значение и обратно.
Крайнее левое значение `thumbElem.style.left` равно нулю, крайнее правой -- как раз ширине доступной области `sliderElem.clientWidth - thumbElem.clientWidth`. Поэтому можно получив значение, поделив его на `pixelsPerValue`: Крайнее левое значение `thumbElem.style.left` равно нулю, крайнее правой -- как раз ширине доступной области `sliderElem.clientWidth - thumbElem.clientWidth`. Поэтому можно получить значение слайдера, поделив его на `pixelsPerValue`:
```js ```js
value = Math.round( newLeft / pixelsPerValue); function positionToValue(left) {
return Math.round( left / pixelsPerValue);
}
function valueToPosition(value) {
return pixelsPerValue * value;
}
``` ```

View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.polyfill.io/v1/polyfill.js?features=CustomEvents,Element.prototype.closest"></script>
<link rel="stylesheet" href="slider.css">
<script src="slider.js"></script>
</head>
<body>
<div id="slider" class="slider">
<div class="thumb"></div>
</div>
Slide:<span id="slide">&nbsp;</span>
Change:<span id="change">&nbsp;</span>
<button onclick="slider.setValue(50)">slider.setValue(50)</button>
<script>
var sliderElem = document.getElementById('slider');
var slider = new Slider({
elem: sliderElem,
max: 100
});
sliderElem.addEventListener('slide', function(event) {
document.getElementById('slide').innerHTML = event.detail;
});
sliderElem.addEventListener('change', function(event) {
document.getElementById('change').innerHTML = event.detail;
});
</script>
</body>
</html>

View file

@ -0,0 +1,19 @@
.slider {
margin: 5px;
width: 310px;
height: 15px;
border-radius: 5px;
background: #E0E0E0;
background: -moz-linear-gradient(left top, #E0E0E0, #EEEEEE) repeat scroll 0 0 transparent;
background: -webkit-gradient(linear, left top, right bottom, from(#E0E0E0), to(#EEEEEE));
background: linear-gradient(left top, #E0E0E0, #EEEEEE);
}
.thumb {
position: relative;
top: -5px;
width: 10px;
height: 25px;
border-radius: 3px;
background: blue;
cursor: pointer;
}

View file

@ -0,0 +1,88 @@
function Slider(options) {
var elem = options.elem;
var thumbElem = elem.querySelector('.thumb');
var max = options.max || 100;
var sliderCoords, thumbCoords, shiftX, shiftY;
// [<*>----------------]
// |...............|
// first last
var pixelsPerValue = (elem.clientWidth - thumbElem.clientWidth) / max;
elem.ondragstart = function() {
return false;
};
elem.onmousedown = function(event) {
if (event.target.closest('.thumb')) {
startDrag(event.clientX, event.clientY);
return false; // disable selection start (cursor change)
}
}
function startDrag(startClientX, startClientY) {
thumbCoords = thumbElem.getBoundingClientRect();
shiftX = startClientX - thumbCoords.left;
shiftY = startClientY - thumbCoords.top;
sliderCoords = elem.getBoundingClientRect();
document.addEventListener('mousemove', onDocumentMouseMove);
document.addEventListener('mouseup', onDocumentMouseUp);
}
function moveTo(clientX) {
// вычесть координату родителя, т.к. position: relative
var newLeft = clientX - shiftX - sliderCoords.left;
// курсор ушёл вне слайдера
if(newLeft < 0) {
newLeft = 0;
}
var rightEdge = elem.offsetWidth - thumbElem.offsetWidth;
if(newLeft > rightEdge) {
newLeft = rightEdge;
}
thumbElem.style.left = newLeft + 'px';
elem.dispatchEvent(new CustomEvent('slide', {
bubbles: true,
detail: positionToValue(newLeft)
}));
}
function valueToPosition(value) {
return pixelsPerValue * value;
}
function positionToValue(left) {
return Math.round( left / pixelsPerValue);
}
function onDocumentMouseMove(e) {
moveTo(e.clientX);
}
function onDocumentMouseUp() {
endDrag();
}
function endDrag() {
document.removeEventListener('mousemove', onDocumentMouseMove);
document.removeEventListener('mouseup', onDocumentMouseUp);
elem.dispatchEvent(new CustomEvent('change', {
bubbles: true,
detail: positionToValue(parseInt(thumbElem.style.left))
}));
}
function setValue(value) {
thumbElem.style.left = valueToPosition(value) + 'px';
}
this.setValue = setValue;
}

View file

@ -0,0 +1,46 @@
# Слайдер с событиями
[importance 5]
На основе слайдера из задачи [](/task/slider-widget) создайте графический компонент, который умеет возвращать/получать значение.
Синтаксис:
```js
var slider = new Slider({
elem: document.getElementById('slider'),
max: 100 // слайдер на самой правой позиции соответствует 100
});
```
Метод `setValue` устанавливает значение:
```js
slider.setValue(50);
```
У слайдера должно быть два события: `slide` при каждом передвижении и `change` при отпускании мыши (установке значения).
Пример использования:
```js
var sliderElem = document.getElementById('slider');
sliderElem.addEventListener('slide', function(event) {
document.getElementById('slide').innerHTML = event.detail;
});
sliderElem.addEventListener('change', function(event) {
document.getElementById('change').innerHTML = event.detail;
});
```
В действии:
[iframe src="solution" height="80"]
<ul>
<li>Ширина/высота слайдера может быть любой, JS-код это должен учитывать.</li>
<li>Центр бегунка должен располагаться в точности над выбранным значением. Например, он должен быть в центре для 50 при `max=100`.</li>
</ul>
Исходный документ -- возьмите решение задачи [](/task/slider-widget).

View file

@ -0,0 +1,32 @@
# Что изучать дальше
Если вы прочитали весь учебник и сделали задачи, то на текущий момент вы обладаете важнейшими фундаментальными знаниями и навыками JavaScript.
[cut]
В этом разделе мы изучали основы создания компонентов на JavaScript. Если проект большой и сложный, то понадобятся дополнительные инструменты для связывания компонент между собой, для привязки к ним данных и так далее.
Сейчас существует много фреймворков. Всё активно развивается, меняется, кипит и булькает, может быть из этого получится "общепринятая" архитектура, а может и нет. Сейчас явного победителя нет, выбор фреймворка зависит от проекта и личных предпочтений разработчиков.
Примеры удачных фреймворков, которые можно изучить:
<ul>
<li>[Angular.JS](http://angularjs.org)</li>
<li>[React.JS](http://facebook.github.io/react/) + [Flux](http://facebook.github.io/flux/)</li>
<li>[Backbone.JS](http://backbonejs.org/) + [Marionette](http://marionettejs.com/)</li>
</ul>
Также для работы с браузерами понадобятся различные [API](https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%BF%D1%80%D0%B8%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9), в частности:
<ul>
<li>Работу с окнами и фреймами.</li>
<li>Регулярные выражения, класс `RegExp`.</li>
<li>Объекты `XMLHttpRequest` и `WebSocket` для работы с сервером.</li>
<li>Другие возможности современных браузеров.</li>
</ul>
В дополнительных разделах учебника мы обязательно разберём что-то из этого.
...И, конечно, понадобится система сборки проектов, например [WebPack](http://webpack.github.io/).
Успехов вам!

View file

@ -1,183 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://code.jquery.com/jquery.min.js"></script>
<style>
.slider {
width: 310px;
height: 15px;
margin: 5px;
border-radius: 5px;
background: #E0E0E0;
background: -moz-linear-gradient(left top , #E0E0E0, #EEEEEE) repeat scroll 0 0 transparent;
background: -webkit-gradient(linear, left top, right bottom, from(#E0E0E0), to(#EEEEEE));
background: linear-gradient(left top, #E0E0E0, #EEEEEE);
}
.slider-thumb {
position: relative;
width: 10px;
height: 25px;
left: 10px;
top: -5px;
border-radius: 3px;
background: blue;
cursor: pointer;
}
.sliding .slider-thumb {
cursor: move;
}
</style>
</head>
<body>
<div id="slider" class="slider">
<div class="slider-thumb"></div>
</div>
Slide:<span id="slide">&nbsp;</span>
Change:<span id="change">&nbsp;</span>
<script>
function Slider(options) {
var self = this;
var elem = options.elem;
var thumbElem = elem.find('.slider-thumb');
// [<*>----------------]
// |...............|
// first last
var pixelsPerValue = (elem.width() - thumbElem.width()) / options.max;
var value = options.value || 0;
// при начале переноса фиксируются
var thumbCursorShift; // сдвиг курсора относительно бегунка
var sliderCoords; // и координаты слайдера
thumbElem.on('mousedown', onThumbMouseDown)
.on('selectstart dragstart', false);
setSlidingValue(value, true);
function onThumbMouseDown(e) {
startSlide(e.pageX, e.pageY);
return false;
}
function onDocumentMouseMove(e) {
// вычесть координату родителя, т.к. position: relative
var newLeft = e.pageX - thumbCursorShift.x - sliderCoords.left;
// курсор ушёл вне слайдера
if (newLeft < 0) {
newLeft = 0;
}
var rightEdge = elem.outerWidth() - thumbElem.outerWidth();
if (newLeft > rightEdge) {
newLeft = rightEdge;
}
setSlidingValue( Math.round( newLeft / pixelsPerValue) );
}
function onDocumentMouseUp() {
endSlide();
}
function endSlide() {
$(document).off('.slider');
$(self).triggerHandler({
type: "change",
value: value
});
elem.removeClass('sliding');
}
function startSlide(downPageX, downPageY) {
var thumbCoords = thumbElem.offset();
thumbCursorShift = {
x: downPageX - thumbCoords.left,
y: downPageY - thumbCoords.top
};
sliderCoords = elem.offset();
$(document).on({
'mousemove.slider': onDocumentMouseMove,
'mouseup.slider': onDocumentMouseUp
});
elem.addClass('sliding');
}
/**
* Установить промежуточное значение
* quiet -- означает "не генерировать событие"
*/
function setSlidingValue(newValue, quiet) {
value = newValue;
thumbElem.css('left', value * pixelsPerValue ^ 0);
if (!quiet) {
$(self).triggerHandler({
type: "slide",
value: value
});
}
}
/**
* Установить окончательное значение
* @param {number} newValue новое значение
* @param {boolean} quiet если установлен, то без события
*/
this.setValue = function(newValue, quiet) {
// установить значение БЕЗ генерации события slide
// т.е. slide в любом случае нет
setSlidingValue(newValue, true);
// ..а change будет, если не указан quiet
if (!quiet) {
$(self).triggerHandler({
type: "change",
value: value
});
}
}
}
var slider = new Slider({
elem: $('#slider'),
max: 100,
value: 10
});
$(slider).on({
slide: function(e) {
$('#slide').html(e.value);
},
change: function(e) {
$('#change').html(e.value);
}
});
slider.setValue(50);
</script>
</body>
</html>

View file

@ -1,45 +0,0 @@
# Слайдер с событиями
[importance 5]
На основе слайдера из задачи [](/task/slider) создайте графический компонент, который умеет возвращать/получать значение.
Синтаксис:
```js
var slider = new Slider({
elem: $('#slider'),
max: 100 // слайдер на самой правой позиции соответствует 100
});
```
Метод `setValue` устанавливает значение:
```js
slider.setValue(50);
```
У слайдера должно быть два события: `slide` при каждом передвижении и `change` при отпускании мыши (установке значения).
Пример использования:
```js
$(slider).on({
slide: function(value) {
$('#slide').html(value);
},
change: function(value) {
$('#change').html(value);
}
});
```
В действии:
[iframe src="solution" height="60"]
<ul>
<li>Дизайн слайдера, ширина/высота элементов должна быть изменяемой через CSS, без необходимости трогать JS-код.</li>
<li>Центр бегунка должен располагаться над значением. Например, он должен быть в центре для 50 при максимуме 100.</li>
</ul>
Исходный документ -- возьмите решение из задачи [](/task/slider).

View file

@ -1,2 +0,0 @@
# Практика, практика, практика!

View file

@ -1,21 +0,0 @@
# Что изучать дальше
Если вы прочитали весь учебник и сделали задачи, то на текущий момент вы обладаете важнейшими фундаментальными знаниями JavaScript и квалификацией, чтобы создавать графические компоненты, достойные современного сайта.
Ещё предстоит изучить:
<ul>
<li>Работу с окнами и фреймами.</li>
<li>Регулярные выражения.</li>
<li>JavaScript-фреймворк (jQuery?)</li>
<li>Оптимизацию.</li>
<li>AJAX и COMET.</li>
<li>HTML5, возможности современных браузеров.</li>
</ul>
Кое-что из этого вы можете узнать из дополнительных глав учебника. Многое -- из Open Source.
Обратите внимание на раздел [](/books).
Присоединитесь к какому-нибудь интересному проекту и сделайте что-то хорошее. Разберитесь в том, как оно работает. Пофиксите пару багов.
Успехов вам!

View file

@ -58,7 +58,7 @@
<li>"Твиты"</li> <li>"Твиты"</li>
</ol> </ol>
Как мы решаем, что именно выделять в компонент? Конечно, нам подсказывает опыт, но Как мы решаем, что именно выделять в компонент? Это нам подсказывает опыт и здравый смысл.
В случае с твиттером разбиение на компоненты особенно очевидно. Странца "сама распадается" на чётко очерченные блоки, каждый из которых выполняет свою роль. В случае с твиттером разбиение на компоненты особенно очевидно. Странца "сама распадается" на чётко очерченные блоки, каждый из которых выполняет свою роль.
@ -87,5 +87,7 @@
Веб-компоненты (Web Components) -- это не один стандарт, а целая платформа, комплекс стандартов, которые вместе добавляют в браузер технологии для удобной реализации компонент. Веб-компоненты (Web Components) -- это не один стандарт, а целая платформа, комплекс стандартов, которые вместе добавляют в браузер технологии для удобной реализации компонент.
Если глядеть "сверху", то веб-компоненты -- это возможность добавлять свои элементы в браузер, например `document.createElement("tweets-list")`. Но "под капотом" кроются расширенные возможности по инкапсуляции поддерева DOM и стилей, по генерации событий и многое другое. Если глядеть "сверху", то веб-компоненты -- это возможность добавлять свои элементы в браузер, например `document.createElement("tweets-list")`, которые описываются с помощью классов JavaScript, могут иметь свои методы и свойства.
Также "под капотом" кроются расширенные возможности по инкапсуляции поддерева DOM и стилей, по генерации событий и многое другое, что мы рассмотрим далее.

View file

@ -1,19 +1,21 @@
# Веб-компоненты # Свои элементы: Custom Elements
Сердцем платформы "веб-компоненты" является стандарт [Web Components](http://w3c.github.io/webcomponents/explainer/), который находится в разработке и позволяет описывать свои элементы. Платформа "веб-компоненты" включает в себя несколько стандартов [Web Components](http://www.w3.org/standards/techs/components#w3c_all), которые находятся в разработке.
Начнём мы со стандарта [Custom Elements](http://www.w3.org/TR/custom-elements/), который позволяет создавать свои типы элементов.
[cut] [cut]
## Зачем Web Components? ## Зачем Custom Elements?
Критично настроенный читатель скажет: "Зачем ещё стандарт для создания своих элементов? Я могу создать любой элемент и прямо сейчас! В любом из современных браузеров можно писать любой HTML, используя свои теги. В чём же разница?" Критично настроенный читатель скажет: "Зачем ещё стандарт для создания своих элементов? Я могу создать любой элемент и прямо сейчас! В любом из современных браузеров можно писать любой HTML, используя свои теги: `<mytag>`. Или создавать элементы из JavaScript при помощи `document.createElement('mytag')`. В чём же разница?"
Она в том, что обычно элемент с нестандартным названием (например `<message>`) воспринимается браузером, как нечто неопределённо-непонятное. Ему соответствует класс [HTMLUnknownElement](http://www.w3.org/TR/html5/dom.html#htmlunknownelement), и у него нет каких-либо особых методов. Она в том, что обычно элемент с нестандартным названием (например `<mytag>`) воспринимается браузером, как нечто неопределённо-непонятное. Ему соответствует класс [HTMLUnknownElement](http://www.w3.org/TR/html5/dom.html#htmlunknownelement), и у него нет каких-либо особых методов.
**Стандарт Web Components позволяет описывать для новых элементов свои свойства, методы, объявлять свой DOM, подобие конструктора и многое другое.** **Стандарт Custom Elements позволяет описывать для новых элементов свои свойства, методы, объявлять свой DOM, подобие конструктора и многое другое.**
Давайте посмотрим это на примерах. Давайте посмотрим это на примерах.
[warn header="Только Chrome"] [warn header="Для примеров рекомендуется Chrome"]
Так как спецификация не окончательна, то для запуска примеров рекомендуется использовать Chrome Canary. Так как спецификация не окончательна, то для запуска примеров рекомендуется использовать Google Chrome, лучше -- последнюю сборку [Chrome Canary](https://www.google.ru/chrome/browser/canary.html), в которой, как правило, отражены последние изменения.
[/warn] [/warn]
## Новый элемент ## Новый элемент
@ -22,7 +24,7 @@
Здесь: Здесь:
<ul> <ul>
<li>`имя` -- имя нового тега, например `"mega-select"`. Оно обязано содержать дефис `-`. Спецификация требует дефис, чтобы избежать в будущем конфликтов со стандартными элементами HTML. Нельзя создать элемент `timer` или `myTimer` -- будет ошибка.</li> <li>`имя` -- имя нового тега, например `"mega-select"`. Оно обязано содержать дефис `"-"`. Спецификация требует дефис, чтобы избежать в будущем конфликтов со стандартными элементами HTML. Нельзя создать элемент `timer` или `myTimer` -- будет ошибка.</li>
<li>`прототип` -- объект-прототип для нового элемента, он должен наследовать от `HTMLElement`, чтобы у элемента были стандартные свойства и методы.</li> <li>`прототип` -- объект-прототип для нового элемента, он должен наследовать от `HTMLElement`, чтобы у элемента были стандартные свойства и методы.</li>
</ul> </ul>
@ -62,7 +64,7 @@
</script> </script>
``` ```
**Метод `registerElement` может быть вызван в любое время, даже и после появления `<my-timer>` в HTML.** Использовать новый элемент в HTML можно и до его объявления через `registerElement`.
Для этого в браузере предусмотрен специальный режим "обновления" существующих элементов. Для этого в браузере предусмотрен специальный режим "обновления" существующих элементов.
@ -72,7 +74,7 @@
<li>При вызове `registerElement` такие элементы автоматически обновятся до нужного класса.</li> <li>При вызове `registerElement` такие элементы автоматически обновятся до нужного класса.</li>
</ul> </ul>
Вот демо того, что происходит, когда регистрация элемента происходит после его появления в разметке: В примере ниже регистрация элемента происходит через 2 секунды после его появления в разметке:
```html ```html
<!--+ run --> <!--+ run -->
@ -85,7 +87,7 @@
} }
hello-world { hello-world {
transition: color 5s; transition: color 3s;
} }
</style> </style>
@ -103,12 +105,15 @@
} }
}); });
*!*
// у нового типа элементов есть метод sayHi
*/!*
hello.sayHi(); hello.sayHi();
}, 2000); }, 2000);
</script> </script>
``` ```
Создание таких элементов в JavaScript никак не отличается от обычных: Можно создавать такие элементы и в JavaScript -- обычным вызовом `createElement`:
```js ```js
var timer = document.createElement('my-timer'); var timer = document.createElement('my-timer');
@ -116,9 +121,11 @@ var timer = document.createElement('my-timer');
## Расширение встроенных элементов ## Расширение встроенных элементов
Выше мы видели пример создания элемента на основе базового `HTMLElement`. Но можно расширить и другие, более конкретные HTML-элементы.
Для расширения встроенных элементов у `registerElement` предусмотрен параметр `extends`, в котором можно задать, какой тег мы расширяем. Для расширения встроенных элементов у `registerElement` предусмотрен параметр `extends`, в котором можно задать, какой тег мы расширяем.
Например: Например, кнопку:
```html ```html
<!--+ run --> <!--+ run -->
@ -167,7 +174,7 @@ var timer = document.createElement("button", "my-timer");
## Жизненный цикл ## Жизненный цикл
Следующие методы автоматически вызываются во время жизненного цикла элемента: В прототипе своего элемента мы можем задать специальные методы, которые будут вызываться при создании, добавлении и удалении элемента из DOM:
<table> <table>
<tr><td>`createdCallback`</td><td>Элемент создан</td></tr> <tr><td>`createdCallback`</td><td>Элемент создан</td></tr>
@ -176,9 +183,9 @@ var timer = document.createElement("button", "my-timer");
<tr><td>`attributeChangedCallback(name, prevValue, newValue)`</td><td>Атрибут добавлен, изменён или удалён</td></tr> <tr><td>`attributeChangedCallback(name, prevValue, newValue)`</td><td>Атрибут добавлен, изменён или удалён</td></tr>
</table> </table>
Как вы, наверняка, заметили, `createdCallback` является подобием конструктора. Можно добавить к элементу свойства, запросить с сервера данные и так далее. Как вы, наверняка, заметили, `createdCallback` является подобием конструктора. Он вызывается только при создании элемента, поэтому всю дополнительную инициализацию имеет смысл описывать в нём.
Давайте используем `attachedCallback`, чтобы автоматически запускать таймер при вставке в документ: Давайте используем `createdCallback`, чтобы инициализовать таймер, а `attachedCallback` -- чтобы автоматически запускать таймер при вставке в документ:
```html ```html
<!--+ run --> <!--+ run -->
@ -186,9 +193,16 @@ var timer = document.createElement("button", "my-timer");
var MyTimerProto = Object.create(HTMLElement.prototype); var MyTimerProto = Object.create(HTMLElement.prototype);
MyTimerProto.tick = function() { MyTimerProto.tick = function() {
this.innerHTML++; this.timer++;
this.innerHTML = this.timer;
}; };
*!*
MyTimerProto.createdCallback = function() {
this.timer = 0;
};
*/!*
*!* *!*
MyTimerProto.attachedCallback = function() { MyTimerProto.attachedCallback = function() {
setInterval(this.tick.bind(this), 1000); setInterval(this.tick.bind(this), 1000);
@ -203,10 +217,9 @@ var timer = document.createElement("button", "my-timer");
<my-timer id="timer">0</my-timer> <my-timer id="timer">0</my-timer>
``` ```
Спецификация: ## Итого
<ul>
<li>[Introduction to Web Components](http://w3c.github.io/webcomponents/explainer/)</li>
<li>[Custom Elements](http://w3c.github.io/webcomponents/spec/custom/)</li>
</ul>
Мы рассмотрели, как создавать свои DOM-элементы при помощи стандарта [Custom Elements](http://www.w3.org/TR/custom-elements/).
Далее мы перейдём к изучению дополнительных возможностей по работе с DOM.

View file

@ -1,8 +1,8 @@
# Shadow DOM # Shadow DOM, шаблоны и стили
Спецификация [Shadow DOM](http://w3c.github.io/webcomponents/spec/shadow/index.html) готовится как отдельный стандарт и применяется, в том числе, отдельно от веб-компонентов. Спецификация [Shadow DOM](http://w3c.github.io/webcomponents/spec/shadow/) является отдельным стандартом. Частично он уже используется для обычных DOM-элементов, но также применяется для создания веб-компонентов.
*Shadow DOM* -- это внутренний DOM элемента, который существует отдельно от внешнего документа. В нём могут быть свои ID, свои стили и так далее. Причём снаружи оно, без применения специальных техник, не видно, поэтому не возникает конфликтов. *Shadow DOM* -- это внутренний DOM элемента, который существует отдельно от внешнего документа. В нём могут быть свои ID, свои стили и так далее. Причём снаружи его, без применения специальных техник, не видно, поэтому не возникает конфликтов.
[cut] [cut]
## Внутри браузера ## Внутри браузера
@ -14,13 +14,11 @@
Например, вот такое содержимое будет у `<input type="date">`: Например, вот такое содержимое будет у `<input type="date">`:
<img src="shadow-dom-chrome.png"> <img src="shadow-dom-chrome.png">
То, что находится под `shadow-root` -- это и есть Shadow DOM. То, что находится под `#shadow-root` -- это и есть Shadow DOM.
**Получить элементы из Shadow DOM можно только при помощи специальных JavaScript-вызовов или селекторов. Это не обычные дети, а намного более мощное средство отделения содержимого.** **Получить элементы из Shadow DOM можно только при помощи специальных JavaScript-вызовов или селекторов. Это не обычные дети, а намного более мощное средство отделения содержимого.**
В Shadow DOM выше можно увидеть полезный атрибут `pseudo`. С его помощью можно стилизовать подэлементы через CSS. В Shadow DOM выше можно увидеть полезный атрибут `pseudo`. Он нестандартный, существует по историческим причинам. С его помощью можно стилизовать подэлементы через CSS, например, сделаем поле редактирования даты красным:
Например, сделаем поле редактирования даты красным:
```html ```html
<!--+ run --> <!--+ run -->
@ -35,7 +33,9 @@ input::-webkit-datetime-edit {
<input type="date"> <input type="date">
``` ```
Если говорить хронологически, то сначала браузеры начали экспериментировать внутри себя с инкапсуляцией внутренних DOM-структур, а уже потом, через некоторое время, появился стандарт Shadow DOM, который позволяет делать то же самое разработчикам. Ещё раз заметим, что `pseudo` -- нестандартный атрибут. Если говорить хронологически, то сначала браузеры начали экспериментировать внутри себя с инкапсуляцией внутренних DOM-структур, а уже потом, через некоторое время, появился стандарт Shadow DOM, который позволяет делать то же самое разработчикам.
Далее мы рассмотрим работу с Shadow DOM из JavaScript, по стандарту [Shadow DOM](http://w3c.github.io/webcomponents/spec/shadow/).
## Создание Shadow DOM ## Создание Shadow DOM
@ -44,7 +44,7 @@ Shadow DOM можно создать внутри любого элемента
Например: Например:
```html ```html
<!--+ run --> <!--+ run autorun -->
<p id="elem">Доброе утро, страна!</p> <p id="elem">Доброе утро, страна!</p>
<script> <script>
@ -57,12 +57,10 @@ Shadow DOM можно создать внутри любого элемента
**С момента создания Shadow DOM обычное содержимое (дети) элемента не отображается, а показывается только Shadow DOM.** **С момента создания Shadow DOM обычное содержимое (дети) элемента не отображается, а показывается только Shadow DOM.**
Внутрь этого Shadow DOM, при желании, можно поместить обычное содержимое. Для этого нужно указать, куда, то есть "точку вставки" (insertion point). Это делается при помощи тега `<content>`. Внутрь этого Shadow DOM, при желании, можно поместить обычное содержимое. Для этого нужно указать, куда. В Shadow DOM это делается через "точку вставки" (insertion point). Она объявляется при помощи тега `<content>`, например:
Укажем его:
```html ```html
<!--+ run --> <!--+ run autorun -->
<p id="elem">Доброе утро, страна!</p> <p id="elem">Доброе утро, страна!</p>
<script> <script>
@ -71,7 +69,7 @@ Shadow DOM можно создать внутри любого элемента
</script> </script>
``` ```
Теперь, если у вас последний Chrome, вы увидите две строчки: "Доброе утро, страна!" в заголовке, а затем "Привет из подполья". Теперь вы увидите две строчки: "Доброе утро, страна!" в заголовке, а затем "Привет из подполья".
Shadow DOM примера выше в инструментах разработки: Shadow DOM примера выше в инструментах разработки:
@ -80,21 +78,21 @@ Shadow DOM примера выше в инструментах разработ
Важные детали: Важные детали:
<ul> <ul>
<li>Тег `<content>` влияет только на отображение, он не перемещает узлы физически. Как видно из картинки выше, текстовый узел "Доброе утро, страна!" остался внутри `p#elem`. Его можно даже получить при помощи `elem.firstElementChild`. </li> <li>Тег `<content>` влияет только на отображение, он не перемещает узлы физически. Как видно из картинки выше, текстовый узел "Доброе утро, страна!" остался внутри `p#elem`. Его можно даже получить при помощи `elem.firstElementChild`. </li>
<li>Внутри `<content>` показывается не `<p>`, а его содержимое, то есть в данном случае текст "Привет из подполья".</li> <li>Внутри `<content>` показывается не элемент целиком `<p id="elem">`, а его содержимое, то есть в данном случае текст "Доброе утро, страна!".</li>
</ul> </ul>
**Атрибутом `select` можно указать конкретный селектор содержимого, которое нужно переносить. Например, `<content select="h3"></content>` перенесёт только заголовки.** **В `<content>` атрибутом `select` можно указать конкретный селектор содержимого, которое нужно переносить. Например, `<content select="h3"></content>` перенесёт только заголовки.**
Внутри Shadow DOM можно использовать `<content>` много раз, указывая таким образом, где конкретно какие части исходного содержимого разместить, но при этом дублирование узлов невозможно. Если узел показан в одном `<content>`, то в следующем он будет пропущен. Внутри Shadow DOM можно использовать `<content>` много раз с разными значениями `select`, указывая таким образом, где конкретно какие части исходного содержимого разместить. Но при этом дублирование узлов невозможно. Если узел показан в одном `<content>`, то в следующем он будет пропущен.
Например, если сначала идёт `<content select="h3.big">`, а затем `<content select="h3">`, то в первом `<content>` будут показаны заголовки `<h3>` с классом `big`, а во втором -- все остальные.</li> Например, если сначала идёт `<content select="h3.title">`, а затем `<content select="h3">`, то в первом `<content>` будут показаны заголовки `<h3>` с классом `title`, а во втором -- все остальные, кроме уже показанных.</li>
**Содержимое внутри `<content>...</content>` будет показано только в том случае, если узлов для вставки нет.** В примере выше тег `<content></content>` внутри пуст. Если в нём указать содержимое, то оно будет показано только в том случае, если узлов для вставки нет. Например потому что ни один узел не подпал под указанный `select`, или все они уже отображены другими, более ранними `<content>`.
Ещё пример: Например:
```html ```html
<!--+ run --> <!--+ run autorun -->
<section id="elem"> <section id="elem">
<h1>Новости</h1> <h1>Новости</h1>
@ -103,23 +101,24 @@ Shadow DOM примера выше в инструментах разработ
<script> <script>
var root = elem.createShadowRoot(); var root = elem.createShadowRoot();
root.innerHTML = "<content select='h1'></content> \ root.innerHTML = "<content select='h1'></content> \
<content select='.author'>Без автора.</content> \ <content select='.author'>Без автора.</content> \
<content></content>"; <content></content>";
alert(elem.innerHTML);
</script> </script>
<button onclick="alert(root.innerHTML)">root.innerHTML</button>
``` ```
При запуске мы увидим, что: При запуске мы увидим, что:
<ul> <ul>
<li>Первый `<content select='h1'>` выведет заголовок.</li> <li>Первый `<content select='h1'>` выведет заголовок.</li>
<li>Второй `<content select=".author">` вывел бы автора, но так как такого элемента нет -- выводится содержимое самого `<content select=".author">`.</li> <li>Второй `<content select=".author">` вывел бы автора, но так как такого элемента нет -- выводится содержимое самого `<content select=".author">`, то есть "Без автора".</li>
<li>Третий `<content>` выведет остальное содержимое (уже без заголовка `<h1>`, он выведен ранее!)</li> <li>Третий `<content>` выведет остальное содержимое исходного элемента -- уже без заголовка `<h1>`, он выведен ранее!</li>
</ul> </ul>
Ещё раз обратим внимание, что `<content>` физически не перемещает узлы по DOM. Он только показывает, где их отображать, а также, как мы увидим далее, влияет на применение стилей. Поэтому в примере выше `innerHTML` выведет содержимое `elem`, как если бы не было никакого Shadow DOM. Ещё раз обратим внимание, что `<content>` физически не перемещает узлы по DOM. Он только показывает, где их отображать, а также, как мы увидим далее, влияет на применение стилей.
## Корень shadowRoot ## Корень shadowRoot
@ -127,23 +126,30 @@ Shadow DOM примера выше в инструментах разработ
Он представляет собой специальный объект, поддерживающий основные методы CSS-запросов и подробно описанный в стандарте как [ShadowRoot](http://w3c.github.io/webcomponents/spec/shadow/#shadowroot-object). Он представляет собой специальный объект, поддерживающий основные методы CSS-запросов и подробно описанный в стандарте как [ShadowRoot](http://w3c.github.io/webcomponents/spec/shadow/#shadowroot-object).
Например: Если нужно работать с содержимым в Shadow DOM, то нужно перейти к нему через `elem.shadowRoot`. Можно и создать новое Shadow DOM-дерево из JavaScript, например:
```html ```html
<!--+ run --> <!--+ run autorun -->
<p id="elem">Доброе утро, страна!</p> <p id="elem">Доброе утро, страна!</p>
<script> <script>
*!*
// создать новое дерево Shadow DOM для elem
*/!*
var root = elem.createShadowRoot(); var root = elem.createShadowRoot();
root.innerHTML = "<h3><content></content></h3> <p>Привет из подполья!</p>";
root.innerHTML = "<h3><content></content></h3> <p>Привет из подполья!</p> <hr>";
</script> </script>
<script> <script>
*!* *!*
var root = elem.shadowRoot; // ...где-то в другом месте кода // прочитать данные из Shadow DOM для elem
alert(root.querySelector('p').innerHTML); // Привет из подполья!
alert(root.querySelector('content').innerHTML); // пусто, физически узлы - вне content
*/!* */!*
var root = elem.shadowRoot;
// Привет из подполья!
document.write("<p>p:" + root.querySelector('p').innerHTML);
// пусто, так как физически узлы - вне content
document.write("<p>content:" + root.querySelector('content').innerHTML);
</script> </script>
``` ```
@ -153,23 +159,25 @@ Shadow DOM примера выше в инструментах разработ
## Шаблоны <template> ## Шаблоны <template>
Элемент `<template>` предназначен для хранения "образца" разметки, предназначенного для вставки куда-либо. Элемент `<template>` предназначен для хранения "образца" разметки, невидимого и предназначенного для вставки куда-либо.
Его отличие от обычных тегов в том, что его содержимое обрабатывается особым образом. Оно не показывается и считается находящимся вообще "вне документа". Конечно, есть много способов записать произвольный невидимый текст в HTML. В чём же особенность `<template>`?
Однако, вместе с тем, оно всё же парсится браузером и записывается в виде `DocumentFragment` в свойство `content`. Предполагается, что мы, при необходимости, возьмём фрагмент `content` и вставим, куда надо. Его отличие от обычных тегов в том, что его содержимое обрабатывается особым образом. Оно не только показывается, но и считается находящимся вообще "вне документа".
Пример использования: Однако, вместе с тем, оно всё же обрабатывается браузером (а значит должно быть корректным HTML) и записывается как `DocumentFragment` в свойство тега `content`. Предполагается, что мы, при необходимости, возьмём `content` и вставим, куда надо.
Пример вставки шаблона `tmpl` в Shadow DOM элемента `elem`:
```html ```html
<!--+ run --> <!--+ run autorun -->
<p id="elem">Доброе утро, страна!</p> <p id="elem">Доброе утро, страна!</p>
<template id="tmpl"> <template id="tmpl">
<h3><content></content></h3> <h3><content></content></h3>
<p>Привет из подполья!</p> <p>Привет из подполья!</p>
<script> alert('Новость!'); </script> <script> document.write('...document.write:Новость!'); </script>
</template> </template>
<script> <script>
@ -178,10 +186,17 @@ Shadow DOM примера выше в инструментах разработ
</script> </script>
``` ```
Детали: У нас получилось, что:
<ol>
<li>В элементе `#elem` содержатся данные в некоторой оговорённой разметке.</li>
<li>Шаблон `#tmpl` указывает, как их отобразить, куда и в какие HTML-теги завернуть содержимое `#elem`.</li>
<li>Это содержимое добавляется в Shadow DOM тега. Технически, шаблон можно использовать и без Shadow DOM, но тогда не сработает тег `<content>`.</li>
</ol>
Важные детали:
<ul> <ul>
<li>В отличие от вставки через `innerHTML` и от обычного `DocumentFragment`, скрипт внутри шаблона выполнится при вставке. Содержимое шаблона изначально "вне документа" и "оживает", когда оно попадает в него. Это относится ко всему -- картинки начинают загружаться, видео -- проигрываться и т.п.</li> <li>В отличие от вставки через `innerHTML` и от обычного `DocumentFragment`, скрипт внутри шаблона выполнится при вставке. Содержимое шаблона изначально "вне документа" и "оживает", когда оно попадает в него. Это относится ко всему -- картинки начинают загружаться, видео -- проигрываться и т.п.</li>
<li>Вставляется не сам `tmpl.content`, а его клон. Это обычная практика, чтобы можно было использовать содержимое одного шаблона во многих однотипных элементах.</li> <li>Мы вставляем не сам `tmpl.content`, а его клон. Это обычная практика, чтобы можно было использовать один шаблон много раз.</li>
</ul> </ul>
## Стили ## Стили
@ -193,7 +208,7 @@ Shadow DOM примера выше в инструментах разработ
Например: Например:
```html ```html
<!--+ run --> <!--+ run autorun -->
<p>Жили мы тихо-мирно, и тут...</p> <p>Жили мы тихо-мирно, и тут...</p>
<p id="elem">Доброе утро, страна!</p> <p id="elem">Доброе утро, страна!</p>

View file

@ -1,2 +1,5 @@
# Веб-компоненты: взгляд в будущее # Веб-компоненты: взгляд в будущее
Веб-компоненты -- "платформа будущего": совокупность стандартов, которые позволяют описывать "свои теги" -- новые типы DOM-элементов, со своими свойствами и методами, инкапсулировать их DOM и стили.
Современные браузеры поддерживают их частично.

79
archive/menu.html Normal file
View file

@ -0,0 +1,79 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
.menu ul {
margin: 0;
list-style: none;
padding-left: 20px;
display: none;
}
.menu .title {
font-size: 18px;
cursor: pointer;
}
.menu .title::before {
color: green;
font-size: 80%;
content: '▶ ';
}
.menu_open .title::before {
color: green;
font-size: 80%;
content: '▼ ';
}
.menu_open ul {
display: block;
}
</style>
</head>
<body>
<div id="sweeties" class="menu">
<span class="title">Сладости (нажми меня)!</span>
<ul>
<li>Торт</li>
<li>Пончик</li>
<li>Пирожное</li>
</ul>
</div>
<script>
function Menu(options) {
var elem = options.elem;
function toggle() {
elem.classList.toggle('menu_open');
}
elem.onclick = function(event) {
console.log(event);
console.log(this == elem);
toggle();
}
this.toggle = toggle;
}
var menu = new Menu({
elem: document.querySelector('#sweeties')
});
setInterval(function() {
menu.toggle();
}, 3000)
</script>
</body>
</html>

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

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