Shadow DOM

от автора

Ссылка на стандарт: www.w3.org/TR/2013/WD-shadow-dom-20130514/

Итак, что же такое shadow DOM:
Shadow DOM (или теневая модель документа) — часть документа, реализующая инкапсуляцию в DOM дереве. Она (теневая модель) является частью документа и встраивается непосредственно внутрь страницы.
Для упрощения отладки shadow DOM, в хроме можно включить отображение в веб-инспекторе (Settings — General — Show shadow DOM).

Надо заметить, что в стандарте реализуемая инкапсуляция называется функциональной, поскольку shadow DOM встраивается в документ и является одной из многих его частей, работающих «независимо» (более-менее независимо) друг от друга. Соответственно, при проектировании реализации, нужно было установить функциональные границы в дереве документа, чтобы как-то оперировать с множеством таких «независимых» фрагментов. Для решения проблемы инкапсуляции, и была введена новая абстракция — shadow DOM, позволяющая создавать несколько DOM деревьев в пределах одного родительского дерева и был разработан документ, описывающий ее.

Дочернее дерево размещается внутри некоторого элемента на странице. Функциональные границы между главным деревом документа и теневым называются shadow boundaries (теневые границы). Элемент, который размещает в себе теневое дерево, называется shadow host, а корень теневого дерево, соответственно, называется shadow root.

Во время рендеринга shadow tree занимает место содержимого shadow host (элемента).

Пример реализации в chromium:

<div id="shadow-host"></div> 
var shadowHost = document.querySelector("#shadow-host"), 	shadowRoot = shadowHost.webkitCreateShadowRoot(); 

Insertion points

Для композиции потомков shadow host и shadow tree используются insertion points. Insertion points определяют местонахождение потомков shadow host в shadow tree. При рендеринге shadow tree потомки проецируются в это место. Механизм, определяющий какие потомки shadow host будут спроецированы в insertion point называется distribution.

Реализация:

<div id="shadow-host"> 	<span>Hi shadow DOM!</span> </div> 
var shadowHost = document.querySelector("#shadow-host"), 	shadowRoot = shadowHost.webkitCreateShadowRoot(), 	content = document.createElement("content"); content.select = "span"; // выбираем все спаны из shadow host shadowRoot.appendChild(content); 

Псевдо-элемент ::distributed()

::distributed(selector) — функциональный псевдо-элемент принимающий относительный селектор в качестве аргумента. Он представляет отношение между insertion point в shadow tree и элементом, перенесенным в insertion point.

Реализация (chrome canary only):

<html> 	<head> 		<script> 			function onLoad() { 				var shadowHost = document.querySelector("#shadow-host"), 					shadowRoot = shadowHost.webkitCreateShadowRoot(); 				shadowRoot.innerHTML = document.querySelector("template").innerHTML; 			} 		</script> 	</head> 	<body onload="onLoad()"> 		<div id="shadow-host"> 			<span>Hi shadow DOM!</span> 		</div> 		<template> 			  <style> 			    content::-webkit-distributed(span) { 			      color: red !important; 			    } 		    </style> 		    <content></content> 		</template> 	</body> </html> 

Один shadow host может вмещать в себя несколько shadow tree — они будут отображены в порядке их добавления. Такой набор деревьев называется shadow stack. Более «старый» shadow tree так же можно переносить в другой shadow tree посредством shadow insertion point.

<html> 	<head> 		<script> 			function onLoad() { 				var shadowHost = document.querySelector("#shadow-host"), 					firstShadowRoot = shadowHost.webkitCreateShadowRoot(), 					secondShadowRoot = shadowHost.webkitCreateShadowRoot();  				firstShadowRoot.innerHTML = document.querySelector("#template-1").innerHTML; 				secondShadowRoot.innerHTML = document.querySelector("#template-2").innerHTML; 			} 		</script> 	</head> 	<body onload="onLoad()"> 		<div id="shadow-host"> 			<span>Hi shadow DOM!</span> 		</div> 		<template id="template-1"> 		    <div>root 1</div> 		</template> 		<template id="template-2"> 		    <div>root 2</div> 			<shadow></shadow> 		</template> 	</body> </html>  
Reprojection (перепроецирование)

Перепроецирование это ситуация, при которой первое shadow tree уже имеет insertion point, а второй shadow tree имеет shadow insetion point, при этом контент, взятый из shadow host сначала проецируется в первом shadow tree, а затем во втором.

<html> 	<head> 		<script> 			function onLoad() { 				var shadowHost = document.querySelector("#shadow-host"), 					firstShadowRoot = shadowHost.webkitCreateShadowRoot(), 					secondShadowRoot = shadowHost.webkitCreateShadowRoot();  				firstShadowRoot.innerHTML = document.querySelector("#template-1").innerHTML; 				secondShadowRoot.innerHTML = document.querySelector("#template-2").innerHTML; 			} 		</script> 	</head> 	<body onload="onLoad()"> 		<div id="shadow-host"> 			<span>Hi shadow DOM!</span> 		</div> 		<template id="template-1"> 		    <div>root 1</div> 			<content select="span"></content> 		</template> 		<template id="template-2"> 		    <div>root 2</div> 			<shadow></shadow> 		</template> 	</body> </html> 
Псевдо-элементы (в контексте shadow DOM)

Автор стандарта пишет:

In certain situations, the author of a shadow tree may wish to designate one or more elements from that tree as a structural abstraction that provides additional information about the contents of the shadow tree.

В определенных ситуациях, автору shadow tree захочется назначить один или несколько элементов из shadow tree как стукртурную абстракцию, дающую дополнительную информацию о контенте shadow tree.

Что я понимаю как возможность использовать css селекторы вне shadow tree для доступа к элементам внутри него:

<html> 	<head> 		<script> 			function onLoad() { 				var shadowHost = document.querySelector("#shadow-host"), 					shadowRoot = shadowHost.webkitCreateShadowRoot(); 				shadowRoot.innerHTML = document.querySelector("template").innerHTML; 			} 		</script> 		<style> 			div::x-thumb { 				width: 10px; 				height: 10px; 				background: black; 			} 		</style> 	</head> 	<body onload="onLoad()"> 		<div id="shadow-host"></div> 		<template> 			<div pseudo="x-thumb"></div> 		</template> 	</body> </html> 
События

Некоторые события пропускаются через shadow boundary, некоторые нет. Исключение составляют mutation events — они вообще не должны возникать в shadow tree. При прохождении события через shadow boundary у него меняется event.target для поддержания инкапсуляции.
Вот интересный пример:

<html> 	<head> 		<script> 			function onLoad() { 				var shadowHost = document.querySelector("#shadow-host"), 					shadowRoot = shadowHost.webkitCreateShadowRoot(); 				shadowRoot.innerHTML = document.querySelector("template").innerHTML; 				shadowHost.addEventListener("mouseout", function(e) { 					console.log("mouse out", e.target); 				}); 			} 		</script> 		<style> 			#shadow-host { 				width: 100px; 				height: 100px; 				background: blue; 			} 			#outer-element { 				width: 100%; 				height: 20px; 				background: red; 			} 		</style> 	</head> 	<body onload="onLoad()"> 		<div id="shadow-host"> 			<div id="outer-element"></div> 		</div> 		<template> 			<div id="first-inner-element"></div> 			<div id="second-inner-element"></div> 			<content></content> 			<style> 				#first-inner-element { 					width: 100px; 					height: 20px; 					background: green; 					position: absolute; 					top: 140px; 				} 				#second-inner-element { 					width: 100px; 					height: 20px; 					background: black; 					margin-bottom: 40px; 				} 			</style> 		</template> 	</body> </html> 

События спроецированного элемента всплывают в shadow host, как-будто он все еще находится непосредственно внутри shadow host. События first-inner-element не всплывают в shadow host, в отличие от second-inner-element, который абсолютно спозиционирован и вынесен за пределы shadow host (при этом event.target сменился).

Стили

Есть два метода, позволяющие манипулировать стилями shadow tree:

shadowRoot.resetStyleInheritance (false by default)
Сбрасывает наследование стилей для shadow tree (стили снаружи не применяются на shadow tree).

shadowRoot.applyAuthorStyles (false by default)
Применяет стили авторского (главного) документа.

<html> 	<head> 		<script> 			function onLoad() { 				var shadowHost = document.querySelector("#shadow-host"), 					firstShadowRoot = shadowHost.webkitCreateShadowRoot(); 				 				var secondShadowRoot = shadowHost.webkitCreateShadowRoot(); 				secondShadowRoot.resetStyleInheritance = true; 				secondShadowRoot.applyAuthorStyles = true;  				firstShadowRoot.innerHTML = document.querySelector("#template-1").innerHTML; 				secondShadowRoot.innerHTML = document.querySelector("#template-2").innerHTML; 			} 		</script> 		<style> 			* { 				font-style: italic; 			} 		</style> 	</head> 	<body onload="onLoad()"> 		<div id="shadow-host"></div> 		<template id="template-1"> 			<style> 				* { 					color: red; 					font-weight: bold; 				} 			</style> 		    <div>root 1</div> 		</template> 		<template id="template-2"> 		    <div>root 2</div> 			<shadow></shadow> 		</template> 	</body> </html> 

Итог

Можно сказать, что некоторой «инкапсуляции» для html не хватало. Это открывает большие возможности по созданию и шаблонизации различных, заранее подготовленных, виджетов на странице. Удивляет только отсутствие инкапсуляции JavaScript кода внутри виджетов, хотя мне казалось бы это довольно логичным.

ссылка на оригинал статьи http://habrahabr.ru/post/180377/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *