Почему у нас нет селектора по родителю — Веб-стандарты
Относительно регулярно я вижу дискуссии о том, должен ли CSS включать селектор по родителю и, пока я не выяснил, как работает движок браузера, сам строил предположения по этому поводу.
Вкратце: производительность.
Как работает CSSСкопировать ссылку
В связи с моей работой я делаю много тестов производительности. Для определения «узких мест» мы используем массу приложений. Например, Google Page Speed, который дает рекомендации по улучшению производительности JavaScript и рендеринга. Прежде чем я перейду к рассмотрению этих рекомендаций, нам нужно разобраться как браузеры работают с CSS
Стиль элемента применяется в момент его созданияСкопировать ссылку
Мы часто рассматриваем веб-страницы как полноценные документы, наполненные элементами и содержимым. Между тем, браузеры разработаны так, чтобы обрабатывать документ потоково. Они начинают получать документ с сервера и могут начать его отображать до момента полной загрузки. Каждый узел анализируется и отображается в окне по мере получения.
Взгляните на этот документ:
<body> <div> <div> <p>Lorem Ipsum</p> </div> <div> <p>Lorem Ipsum</p> <p>Lorem Ipsum</p> <p>Lorem Ipsum <span>Test</span></p> </div> </div> </body>
Браузер начинает сначала и видит элемент <body>
. В этот момент времени считается, что этот узел не имеет дочерних узлов. Ничего более не рассматривается. Браузер определяет соответствующий ему обобщенный набор стилей и применяет его к элементу. Какой шрифт, цвет, интерлиньяж? После того, как это все будет выяснено, элемент отображается на экране.
Далее браузер видит элемент <div>
со значением атрибута ID content
. И снова в этот момент времени браузер считает его пустым. Он не рассматривает другие элементы. Как только браузер рассчитает стиль, элемент отображается на экране. Затем браузер определяет нужно ли перерисовать <body>
— стал ли элемент шире или выше? Я подозреваю, что там есть масса других проверок, но изменение ширины и высоты — самый распространенный способ повлиять на отображение родительского узла.
Процесс продолжается, пока браузер не достигнет корневого узла документа.
Вот как выглядит визуализация процессов перерисовки в Firefox:
CSS-селекторы анализируются справа налевоСкопировать ссылку
Чтобы определить, применяется ли CSS-правило к определенному элементу, браузер рассматривает селектор справа налево.
Если у вас есть селектор body div#content p { color: #003366; }
, то, когда каждый элемент появляется на странице, браузер проверяет, является ли он параграфом. Если да, он начинает подниматься вверх по DOM и ищет <div>
со значением атрибута ID равным content
. Если он его находит, то продолжает подниматься по DOM пока не найдет <body>
.
Таким образом, читая селектор справа налево, браузер значительно быстрее может определить применяется ли правило к элементу. Чтобы определить, какой из селекторов обладает большей производительностью, нужно выяснить, как много узлов придется рассмотреть, чтобы определить, можно ли применить правило к элементу.
ПравилаСкопировать ссылку
Возвращаясь к Page Speed, давайте рассмотрим несколько его рекомендаций:
- Избегайте селектора по потомку:
.content .sidebar
; - Избегайте селектора по дочернему элементу:
.content > .sidebar
и селектора по следующему элементу:.content + .sidebar
.
Конечно, селекторы по ID — самые быстрые. Проверить применимость селектора #content
к рассматриваемому элементу можно очень быстро. Есть у него этот ID или нет? Селекторы по классу практически такие же быстрые, так как нет никаких связанных элементов, которые надо проверять.
Селекторы по потомкам, такие как .content .sidebar
— более ресурсоемкие, так как, чтобы определить надо ли применять правило к .sidebar
, браузер должен найти .content
. Cелектор по дочернему элементу, например, .content > .sidebar
, лучше селектора по потомку, так как браузер должен проверить только один элемент вместо множества. (К сожалению, селекторы +
и >
не поддерживаются IE6. Так что если его поддержка актуальна для вас, то про них придется забыть — прим. переводчика).
Селектор по тегу и универсальный селекторСкопировать ссылку
В рамках рекомендации избегать селекторов по потомку, дочернему или следующему элементам, рекомендуется избегать универсального селектора и селектора по тегу.Рассмотрим следующий пример:
#content * { color: #039; }
Так как в селекторе присутствует ID, то можно подумать, что этот селектор обрабатывается очень быстро. Проблема в том, что браузер обрабатывает селектор справа налево и сперва проверяется универсальный селектор. Для того чтобы браузер мог определить, должен ли цвет текста элемента быть тёмно-синим, надо проверить каждый предок элемента, пока не будет найден предок с атрибутом ID равным content
или не будет достигнут корень документа.
И это должно быть сделано для каждого элемента на странице.
Теперь, когда мы понимаем, как элемент обрабатывается, как определяется применимость правил к элементу, давайте рассмотрим пример.
Почему IE долго не поддерживал
:last-child
Скопировать ссылкуВсе жаловались: у всех браузеров, кроме IE, есть поддержка :last-child
(она появилась только в IE9!) Некоторые могли подумать насколько же сложнее сделать
:last-child
, если уже реализован :first-child
?
Давайте представим, что мы — браузер и мы парсим документ-пример, который я приводил ранее.
. module > p:first-child { color: red; } /* Первое правило */ .module > p:last-child { color: blue; } /* Второе правило */
Когда мы рассматриваем внутренности первого <div>
, мы видим, что там есть параграф. Браузер видит что-то вроде этого:
<div> <p>Lorem Ipsum</p>
Нужно ли применить первое правило к параграфу? Да, это параграф; да, это первый дочерний узел; и, да, это непосредственный потомок элемента с классом module
.
Нужно ли применить к этому параграфу второе правило? На данный момент это последний элемент. Но мы могли ещё не загрузить все элементы и не можем быть уверены, что он останется последним.
Вне зависимости от того, как решать эту дилемму, у нас возникает необходимость повторного анализа стилей двух узлов для каждого нового узла, который мы добавляем в DOM. Если я добавлю ещё один параграф следом за первым, мы должны так же повторно перерассчитать стили, которые применяются к предыдущему параграфу.
Как на самом деле это делает браузер?Скопировать ссылку
Я не мог сказать с абсолютной уверенностью, как браузеры парсят :last-child
, так что я создал несколько тестов:
- Статическая HTML-страница;
- Рендеринг с паузами в одну секунду.
Первый пример весьма скучен. В любом браузере, включая IE9, всё отображается корректно. Внутри <div>
первый элемент красный, а последний синий. Но посмотрите на второй пример, и вы увидите интересные отличия в поведении браузеров.
Второй пример приостанавливается перед добавлением каждого параграфа в <div>
.
В Firefox первый параграф изначально отображается синим. Когда загружается второй параграф, первый становится красным и второй — синим. Когда загружается третий параграф, второй отображается цветом по умолчанию и третий отображается синим. Firefox рассматривает каждый элемент, который был загружен в качестве последнего элемента, пока не будет загружен ещё один.
В Safari, Chrome и Opera мы увидим другой подход. Первый параграф красный. Второй отображается чёрным. Последний параграф отображается чёрным, пока браузер не получит закрывающий тег </div>
. В этот момент последний параграф становится синим. Эти браузеры не рассматривают элемент как последний, пока не будет закрыт родительский.
В Internet Explorer 9 Beta я нашел интересный баг. В то время, как статическая страница отображается корректно, версия с паузами отрабатывает с любопытным побочным эффектом. Первый параграф синий, второй параграф синий и затем — третий. Когда закрывающий тег </div>
загружен, предпоследний параграф меняет цвет на чёрный. IE9 пытается обрабатывать селектор как WebKit и Opera, но… м-м… не выходит. Надо бы отправить багрепорт в Microsoft.
Почему у нас нет селектора по родителю?Скопировать ссылку
Уже дано достаточно пояснений, чтобы можно было вернуться к оригинальному вопросу. Проблема не в том, что у нас не может быть селектора по родителю. Проблема в том, что мы столкнемся с проблемами быстродействия, когда дело дойдет до определения того, какие CSS-правила применимы к данному элементу. Если Google Page Speed не рекомендует использование универсальных селекторов, то можно гарантировать, что селектор по родителю будет первым в списке ресурсоемких селекторов, намного опережая все проблемы с производительностью, которые могут быть вызваны использованием универсального селектора.
Давайте посмотрим почему. Первым делом давайте приведём пример синтаксиса для селектора по родителю.
div.module:has(span) { color: green; }
Проблема в том, что мы не можем определить применимость правила до тех пор, либо пока мы не найдем соответствия, либо пока все элементы, дочерние по отношению к родителю данного, не будут загружены. Ввиду этого мы должны оценивать правило и все остальные (в случае наличия специфических особенностей), применяющиеся к элементу, для каждого дочернего элемента, который мы загружаем.
Посмотрите на часть нашего документа:
<div> <p>Lorem Ipsum</p> <p>Lorem Ipsum</p> <p>Lorem Ipsum <span>Test</span></p> </div>
Исходя из того, что мы видим, .module
будет отображён без использования правила, применяемого селектором по родителю. Когда будет загружен первый элемент <p>
, нужно повторно оценить применимость селектора по родителю к <div>
. Нужно сделать это снова для следующего параграфа. И снова, для следующего. Наконец, когда <span>
загружен, селектор по родителю будет применен к родительскому <div>
, и элемент нужно будет повторно перерисовать.
И что теперь? Теперь, если изменится любое наследуемое CSS-свойство, каждый потомок должен будет повторно анализироваться и перерисовываться. Ох…
Почему проблему можно решить с помощью JavaScript?Скопировать ссылку
Это только кажется, что JavaScript решает проблему. В общем случае JavaScript-заплатки (заплатки — polyfills — части кода, обеспечивающие функциональность, которую должен обеспечивать браузер — прим. переводчика). Или регрессивное усовершенствование (или как там вы, молодежь, это сейчас называете) запускаются только один раз, после полной загрузки DOM.
Для того чтобы действительно имитировать поведение CSS, любой скрипт, решающий эту проблему, должен запускаться после отображения каждого элемента на странице, чтобы определить, нужно ли применить нашу «заплатку». Помните CSS-expressions в Internet Explorer? Именно по этой причине они вызывали такие проблемы с производительностью.
Не невозможноСкопировать ссылку
Появится ли когда-нибудь селектор по родителю? Возможно. То, что я описал, не невозможно. На самом деле — наоборот. Это значит только то, что нам придётся иметь дело с ухудшением производительности из-за использования этого селектора.
Основы CSS-селекторов на примере котиков
Вам проще воспринимать информацию визуально? При попытке написать CSS-селектор для стилизации конкретных элементов вы так же озадачиваетесь, как и наш мохнатый друг над этим абзацем? Если всё это про вас — не расстраивайтесь, скоро мы это исправим. Если вы только начинаете изучать синтаксис CSS-селекторов, то наверняка вы испытываете трудности с запоминанием того, какой селектор за что отвечает, поэтому я написал для вас это руководство с котиками. Наслаждайтесь!
Выбираем родителей
Чтобы выбрать родительский элемент, достаточно использовать в качестве селектора класс, id или имя элемента. Добавьте после селектора блок объявлений, и этого будет достаточно! Вот пример разметки и стилей:
<div> <div></div> <div></div> </div> .parent { border: 10px solid cyan; }
Вот как стили повлияют на DOM (голубым подсвечен элемент, выбранный нашим селектором):
Выбираем потомков
Потомки бывают двух видов. Первый вид — прямые потомки — это те элементы, которые непосредственно вложены в родителя без всяких промежуточных элементов. Второй — косвенные потомки, они включают в себя как прямых потомков, так и вложенные в них элементы, и так далее вглубь DOM-дерева.
Прямые потомки
Знак >
между селекторами говорит браузеру сперва найти элемент по первому
селектору, а затем найти всех прямых потомков этого элемента, соответствующих
второму селектору. В следующем примере мы выбираем только тех потомков .child
,
которые лежат непосредственно внутри .parent
:
<div> <div> <div></div> </div> <div></div> </div> .parent > .child { border: 10px solid cyan; }
Эти селекторы выберут следующие элементы DOM:
Косвенные потомки
Селектор для выбора косвенных потомков менее специфичен, чем селектор для выбора
прямых потомков. Он выберет все элементы .child
, вложенные в элемент .parent
, независимо от того, насколько глубоко они находятся в DOM-дереве.
Чтобы получить такой селектор, нужно разделить два селектора обычным пробелом.
В этом примере мы выберем все дочерние .child
, вложенные в . parent
:
<div> <div> <div></div> </div> <div></div> </div> .parent .child { border: 10px solid cyan; }
Вот как это будет работать:
Выбираем соседние элементы
Соседним называется элемент, следующий сразу за выбранным элементом, причём они
оба располагаются на одном уровне DOM-дерева. Выбирать соседние элементы просто:
для этого нужно объединить два селектора знаком +
. Взгляните на пример:
<div> <div></div> <div></div> </div> <div> <div></div> </div> .parent + .sibling { border: 10px solid cyan; }
Как это будет работать на DOM:
Применяем несколько селекторов
Иногда вам может потребоваться применить стили к множеству разных элементов. Как это сделать? Ответ прост: нужно записать селекторы через запятую:
<div> <div></div> <div></div> </div> . parent, .child { border: 10px solid cyan; }
Вот как это сработает на DOM:
Финишируем
Для дальнейшего изучения CSS-селекторов попробуйте ресурс CSS Diner (но, к сожалению, там нет котиков). Он последовательно научит вас использовать как простые, так и сложные селекторы на примере столовых приборов и вкусной еды.
Права
header-cat | parent-cat-one | kitten-one | kitten-two full-cat-one | full-cat-two | goofy-cat | sibling-cat
html — Как использовать чистый CSS для выбора родительского узла, у которого есть специальный дочерний элемент?
У меня есть такой html:
01 <а>02а> <а>03а> 001 002 003
Теперь я хочу выбрать элемент 002
:
002
Я использовал a#fixed:has(>span)
для получения последних трех элементов a
, но я не могу продолжать использовать :nth-child(2)
для выбора второго из них.
Использование кода javascript selectAll()[1].parentNode
является практичным способом.
Но мне нужно использовать его в XPath, поэтому я ищу селектор на чистом CSS, чтобы получить его.
Это потому, что :has()
нельзя использовать с :nth-child()
или :nth-of-type()
?
Как выбрать один из нескольких родительских элементов с такими же характеристиками дочерних элементов?
PS: Используйте только чистый CSS.
Жду ответа, спасибо 🙂
Спасибо за комментарии.
id
действительно должен быть уникальным при использовании. Это используется в приведенном выше коде, потому что этот HTML-код именно такой.:has()
плохо поддерживается, буду следить.Я упомянул XPath, потому что на самом деле вопрос исходил от моего друга, и аспект XPath — это именно то, что он упомянул, я этого не понимаю.
После того, как мы обсудили этот вопрос в автономном режиме, пришли к выводу, что в настоящее время это невозможно.
Всем спасибо.
обновлено: 30 сентября 2022 г. Невозможно с чистым CSS.
- html
- css
- xpath
- css-селекторы
3
Короткий ответ: Нет, вы не можете этого сделать .
Pure CSS не поддерживает это.
0
Зарегистрируйтесь или войдите в систему
Зарегистрируйтесь с помощью Google
Зарегистрироваться через Facebook
Зарегистрируйтесь, используя электронную почту и пароль
Опубликовать как гость
Электронная почта
Обязательно, но не отображается
Опубликовать как гость
Электронная почта
Требуется, но не отображается
Родительский выбор в CSS как работает селектор CSS
CSS означает каскадные таблицы стилей , что в основном означает, что более поздние элементы на странице имеют приоритет над более ранними (с некоторыми важными оговорками). Это также относится к тому, как мы выбираем элементы — от родителя к дочернему, без возможности выбора родителей, до настоящего момента .
В спецификации CSS Selectors 4 CSS представляет новый селектор под названием :has()
, который, наконец, позволяет нам выбирать родителей. Это означает, что мы сможем настроить таргетинг на элемент CSS, который имеет
конкретных дочерних элементов. Это уже поддерживается в Safari , а также в Chrome 105 . Полная таблица поддержки показана ниже:
Поскольку поддержка расширяется, в этой статье я сосредоточусь на том, как работает выбор родительского элемента CSS, и как это можно сделать сегодня, если поддержка доступна. Тем временем, если вам требуется полная поддержка во всех браузерах, вы также можете реализовать этот полифилл, пока не будет доступна собственная поддержка CSS.
Как работают родительские селекторы в CSS
В CSS, если мы хотим что-то выбрать, мы используем селекторы, которые нисходят в DOM. Например, выбор тега p
в теге div
выглядит следующим образом:
div p { красный цвет; }
До сих пор мы не могли выбрать тегов div
, в которых было тегов p
, и это означало, что нам пришлось прибегнуть к Javascript. Основная причина, по которой это не было реализовано в CSS, заключается в том, что это довольно дорогая операция. CSS относительно быстро анализируется, но выбор родительских тегов требует значительно большего объема обработки.
Используя селектор :has
, мы теперь можем выбрать элементов div
, которые имеют p
дочерних элементов, или любую обычную комбинацию селекторов. Например, выбор элемента div
с дочерним элементом p
теперь выглядит так:
/* Цвет элемента div: красный; */ div: имеет (р) { красный цвет; }
Это сделает любой div
с дочерним p
красным.
Объединение родительского выбора с другими селекторами
Как и любой другой селектор CSS, мы можем комбинировать его для определенных обстоятельств. Например, если вы хотите выбрать только теги div
, которые имеют прямые span
дочерние элементы:
div:has(> span) { красный цвет; }
Как следует из словаря :has
, он не ограничивается только родительским выбором. Например, ниже мы можем выбрать диапазон
, который :has
родственный div
:
span:has(+ div) { красный цвет; }
Или даже, выбрав элемент, у которого есть дочерний элемент , а не , с помощью селектора :not()
. Например, следующее выберет любой div, который делает не есть p
дочерний элемент:
div:not(:has(p)) { красный цвет; }
Выбор элементов, содержащих только текст в CSS
Одна очень распространенная проблема в CSS заключается в том, что тег :empty
не выбирает элементы, содержащие какой-либо текст, поэтому иногда элемент может содержать один пробел, и :empty
будет не применять. Селектор :has
дает нам возможность выбирать элементы, которые содержат только текстовые узлы и не содержат других дочерних элементов.
Хотя это не идеальное решение для простых элементов :empty
с пробелами (поскольку это выберет любой элемент только с текстом и без дополнительных элементов HTML DOM) — это дает нам возможность выбирать элементы DOM только с текстовыми узлами. , что раньше было невозможно. Мы можем добиться этого с помощью следующего кода:
div:not(:has(*)) { фон: зеленый; }
Заключение
С добавлением поддержки селектора :has()
в Chrome 105 родительский выбор быстро становится реальностью, которую мы скоро сможем использовать в реальных проектах. На данный момент с поддержкой Safari легко протестировать и посмотреть, как он будет работать в будущем. Это дает дополнительное преимущество, позволяя сократить использование решений Javascript для выбора родительских элементов, что довольно часто встречается во многих приложениях и продуктах.