Статьи

Темная тень DOM

ОБНОВЛЕНИЕ 2015.03.17: Проблемы доступности, которые я высказал в этой статье, неверны и основаны на недоразумении. На самом деле, с Shadow DOM и программами чтения экрана таких проблем с доступностью нет.


Shadow DOM является частью спецификации веб-компонентов и предназначен для решения проблем инкапсуляции, которые мешают некоторым видам веб-разработки.

Вы знаете такую ​​вещь — если вы создаете собственный виджет, как избежать конфликтов имен с другим контентом на той же странице? Что наиболее важно, как вы предотвращаете влияние CSS страницы на ваш виджет?

Это легко, если вы управляете всей страницей, но часто это не так — если вы создаете виджеты для других людей. Проблема в том, что вы не представляете, какой другой CSS будет присутствовать. Конечно, вы можете уменьшить вероятность таких проблем, определив все ваши селекторы как потомки от чего-то с высокой специфичностью:

#mywidget > .mywidget-container
{
}
#mywidget > .mywidget-container > .mywidget-inner
{
}

Но это действует только до тех пор, пока сайт не определит правило с двумя селекторами идентификаторов. Или, может быть, вы могли бы использовать два, но потом три!

Недавно я занимался идеей определения динамических селекторов — скрипт виджета обходит DOM и записывает каждый идентификатор элемента между собой и корнем документа, а затем компилирует селекторы, которые включают все эти идентификаторы.

Но даже это не гарантировано. На самом деле мы ничего не можем сделать, чтобы полностью предотвратить эту проблему, кроме как использовать <iframe> Таким образом, по всем этим причинам, iframes лучше избегать.

В тень

Shadow DOM стремится решить эту проблему. Я не буду вдаваться в детали того, как это работает и как его использовать ( есть другие статьи, которые делают это ), но для целей этой статьи я кратко изложу это следующим образом — Shadow DOM инкапсулирует контент, создавая документ фрагменты . По сути, содержимое Shadow DOM — это другой документ , который объединяется с основным документом для создания общего визуализированного вывода.

Фактически некоторые браузеры уже используют это для визуализации некоторых своих родных виджетов. Если вы откроете Инструменты разработчика в Chrome, выберите « Показать Shadow DOM» на панели настроек (значок cog внизу справа), а затем осмотрите вход "range"

 <input type="range">
  #document-fragment
    <div>
      <div pseudo="-webkit-slider-runnable-track">
        <div></div>
      </div>
    </div>
</input>

Но вы не можете получить доступ к этим элементам через DOM , потому что они скрыты от него:

 alert(input.firstChild);		//alerts null

Теневое содержимое примерно аналогично документу iframe в другом домене — DOM может видеть iframe, но внутри него ничего не видно.

Таким образом, поскольку он изолирован, пользователи не могут случайно его разорвать, нет возможности назвать конфликты с какими-либо классами или идентификаторами, которые вы используете, и CSS на главной странице не повлияет на это вообще.

Звучит великолепно, не правда ли?

В темноту

Но подождите … если весь этот контент отсутствует в DOM , то не означает ли это, что он также не доступен для API доступа?

Да, это именно то, что это значит.

Все, что вы помещаете в Shadow DOM, недоступно для технологий доступа на основе браузера, таких как программы чтения с экрана . Это не доступно для поисковых систем, но это всегда имеет место со скриптовым контентом. Однако программы чтения с экрана отличаются — они являются устройствами с поддержкой сценариев — и поэтому у них есть доступ к содержимому сценариев.

Но не этот контент!

Конечно, спецификация не игнорирует это разделение. По сути, он предполагает различие между элементами, которые содержат текстовое содержимое или информационные атрибуты, и элементами, которые являются просто пустыми полями для создания визуальных частей, таких как большой палец ввода "range" Давайте назовем их элементами содержимого и вспомогательными элементами .

Так как часто виджеты имеют такое четкое различие между ними? Для примера ввода "range" Недавно я написал виджет слайдера для доступного видеоплеера, и его разметка выглядела так:

 <label for="slider-thumb">
  <button type="button" id="slider-thumb" 
    role="slider" aria-orientation="horizontal"
    aria-valuemin="0" aria-valuemax="120" 
    aria-valuenow="75" aria-valuetext="Time: 01:15">
    <span></span>
  </button>
</label>

Единственная часть этого слайдера, которую можно поместить в Shadow DOM, это <span><button> Сама <button>ARIA, которые предоставляют динамическую информацию для программ чтения с экрана и других технологий доступа.

Чтобы это работало с Shadow DOM, нам нужно переместить все атрибуты ARIA во внешний <label>tabindex Но это было бы менее доступно, потому что мы утратили бы нативную семантику (например, метка атрибута forменее полезно, потому что это означает, что виджет не может отправлять данные формы (поэтому нужен отдельный элемент управления формы, такой как скрытый ввод).

Но даже если бы это было хорошо — и даже если каждый создаваемый нами виджет имеет четкое и простое различие между содержимым и служебными элементами — контентная часть виджета все еще не инкапсулирована ; он по-прежнему уязвим к конфликтам имен и нежелательному наследованию CSS .

И все мы знаем, что некоторые люди не поймут и не будут уважать это различие. Люди будут использовать Shadow DOM для контента и использовать его для создания совершенно нового поколения недоступных веб-приложений.

Я читал ряд других статей о Shadow DOM, исследуя эту, и все они делают одно и то же — все они перестают говорить, что вы не должны помещать контент в Shadow DOM, а затем сразу же после этого говорят, но давай не будем об этом беспокоиться .

Brilliant! Целая группа пользователей уволена в одном простое предупреждение!

Но давайте будем добрее, эй. Допустим, примеры статей не могут быть оценены в этих терминах. Давайте предположим, что все, кто использует Shadow DOM, будут делать это с должным вниманием, следя за тем, чтобы они использовали его только для служебных элементов, а не для контента.

С этим требованием Shadow DOM предоставляет только половину решения ; и половина решения не является решением вообще.

В свет

Мне кажется, что вся концепция Shadow DOM неверна. Это слишком сложный подход, который на самом деле не решает проблему, и любой подход, использующий фрагменты документа, будет иметь тот же недостаток — при условии, что необходимо различать доступные и недоступные элементы.

Что нам действительно нужно, так это концептуальная противоположность — способ определения вложенных в стиль поддеревьев, которые все еще являются частью документа .

Другими словами, вместо нескольких документов, которые может просматривать только браузер, у нас есть один документ, который только браузер обрабатывает как несколько документов.

Это можно выразить с помощью простого атрибута элемента:

 <div encapsulated="encapsulated">

HTML DOM будет интерпретировать это по-другому — это просто элемент с неотрисованным атрибутом, как и любой другой. Однако CSS DOM интерпретирует его как некий фрагмент документа, фактически говоря, что элемент и все, что внутри него, не наследуется от более высоких областей .

И мы уже можем делать наоборот — стили области видимости для поддерева — либо с помощью селекторов-потомков, либо, если вам действительно нужно, с помощью <style scoped><link><style>

Чтобы использовать этот инкапсулированный атрибут, мы могли бы по-прежнему использовать лучший способ управления и шаблонирования служебных элементов, но HTML — это неправильное место для этого. На самом деле, нам вообще не нужно определять пустые элементы — это функциональная необходимость только потому, что у нас нет другого способа определения презентационных поддеревьев — поэтому эту возможность следует добавить в CSS .

Другими словами, один элемент должен иметь возможность определять любое количество псевдоэлементов , а сами псевдоэлементы также определять псевдоэлементы. Что-то вроде этого:

 #mywidget::after
{
}
#mywidget::after + ::element
{
}
#mywidget::after > ::element
{
}
#mywidget::after > ::element + ::element
{
}

Который создал бы виртуальное поддерево как это:

 <div id="mywidget" encapsulated="encapsulated">
  Text content
  <after>
    <element></element>
    <element></element>
  </after>
  <element></element>
</div>

Определение этого материала в CSS подразумевает четкое и врожденное различие, которое не может не понять ни один разработчик — контент идет в HTML , презентация в CSS , просто так, как и должно быть.

Еще неизвестно, получим ли мы когда-нибудь что-то похожее на то, что я предлагаю. Но пока я могу только убедить вас запомнить абсолютное различие — не используйте Shadow DOM ни для чего, кроме пустых элементов, которые не передают информацию . И если вы хотите мое лучшее предложение, не беспокойтесь об этом вообще.