Меня зовут София. Я - CSS инженер. Это мой блог.

Если вам очень больно без стилей, вы можете их.

.

Наследование счётчиков

Дисклеймер: Счётчики описаны в спецификации css-lists-3. Здесь я расскажу, как они наследуются. Остальные части этой спецификации, касающиеся их, я планирую разобрать в последующих заметках.

Каждый элемент имеет набор счетчиком, в котором есть как наследованные, так и определённые на нем самом.

Этот набор является кортежем, в котором для каждого счетчика, как элемента кортежа, есть:

  1. строка – имя счетчика
  2. элемент – элемент-создатель счетчика
  3. целое число – значение счетчика

Откуда этот набор берется?

Если вы не хотите читать формальные длинные определения, переходите сразу к примерам.

Элемент наследует свой начальный набор счетчиков (помимо тех, что объявленных на нём самом) от своего родителя и предыдущего родственного элемента. Затем он обновляет их значения из значений соответствующих по имени счетчиков его предыдущего элементе в порядке дерева.

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

Чтобы наследовать счетчики в элемент:

  1. Если это корневой элемент, то его начальный набор счетчиков пустой, потому что у него нет ни родителя, ни предыдущих элементов.
  2. Определяем счетчики элемента, представляющие его собственный набор счетчиков, как просто скопированный набор счетчиков родителя.
  3. Определяем одноуровневые счетчики, как набор счетчиков предыдущего элемента того же уровня вложенности, если таковой есть. Если это первый ребенок своего родителя, то одноуровневые счетчики будут пустые. Для каждого счетчика из одноуровневых счетчиков, если в счетчиках элемента еще не содержится счетчика с таким же именем и создателем, добавляем копию этого счетчика к счетчикам элемента.
  4. Определяем источник значения как набор счетчиков элемента, непосредственно предшествующего элементу в порядке дерева. Для каждого счетчика из источника значения, если счетчики элемента содержат счетчик с тем же именем и создателем, то устанавливаем значение этого счетчика равным значению из исходного значения.

Это был буквальный перевод фрагмента спецификации. Более простым языком:

  1. Копируем набор счетчиков родителя.
  2. Если есть предшествующие элементы на том же уровне, то берем ближайший и копируем оттуда счетчики, которых у нас еще нет.
  3. Берем ближайший предшествующий в порядке дерева элемент, выбираем там по имени счетчики, которые у нас уже есть в списке, и забираем свежие значения.

Выводы из этого алгоритма: на значения счетчиков могут влиять предки и дети предков, а на наличие самих счетчиков на этом элементе только предки.

Наглядные примеры

Возьмем вот такую разметку:

<ul class='outer'>
  <li class='outer__elem'></li>
  <li class='outer__elem'></li>
  <li class='outer__elem'>
    <ul class='inner'>
      <li class='inner__elem'></li>
      <li class='inner__elem'></li>
      <li class='inner__elem'></li>
    </ul>
  </li>
  <li class='outer__elem'></li>
</ul>

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

/* задаем счетчик на внешнем списке */
ul.outer {
  counter-reset: global;
}

/* прибавляем его на каждом элементе списка */
li {
  counter-increment: global;
}

/* выведем его на экран */
li::before {
  content: counter(global);
}

Сейчас счетчик прибавляется на 1 перед каждым отображением и выглядит это так:

Отложим пока глобальные счетчик и посмотрим на локальные:

/* задаем счетчик на внешнем списке */
ul.outer {
  counter-reset: outer;
}

/* задаем счетчик на внутреннем списке */
ul.inner {
  counter-reset: inner;
}

/* прибавляем внешний счетчик на каждом элементе внешнего списка */
li.outer__elem {
  counter-increment: outer;
}

/* то же для внутреннего */
li.inner__elem {
  counter-increment: inner;
}

/* выведем их на экран */
li.outer__elem::before {
  content: counter(outer);
}

li.inner__elem::before {
  content: counter(inner);
}

Сейчас мы видим такую картину:

А что будет, если попробовать вынести внутренний счетчик во внешнем спике?

/* выведем оба счетчика на экран */
li::before {
  content: counter(outer) ', ' counter(inner);
}

Нули отобразились, потому что когда функция counter() не имеет в области видимости счетчика с таким именем, она возвращает 0 как дефолтное значение. То есть внутренний счетчик недоступен во внешнем списке. Это важный для нас вывод, который сначала был сденал из спецификации, теперь же из практики.

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

li::before {
  content: counter(global) ' - ' counter(outer) ', ' counter(inner);
}

Обратимся к алгоритму из спецификации из начала статьи и разберем почему счетчик inner в конце не доступен как это в целом работает изнутри.

Наследованный набор счетчиков для первого элемента внешнего списка выглядит как:

создатель имя значение
ul.outer global 0
ul.outer outer 0

Чтобы вас не смутило значение 0, напомню, что это именно наследованный набор, перед отображением элемент делает с ним все указанные операции, в данном случае counter-increment: outer;, то есть прибавляет 1, поэтому на экране мы увидим именно 1.

Поэтому наследованный набор для второго элемента внешнего списка будет выглядеть так:

создатель имя значение
ul.outer global 1
ul.outer outer 1

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

создатель имя значение
ul.outer global 4
ul.outer outer 3
ul.inner inner 0

Первый элемент внешнего списка, сразу после элемента с внутренним списком:

создатель имя значение
ul.outer global 6
ul.outer outer 3

Почему здесь нет внутреннего счетчика? Как будто это очевидно, но давайте разберем по алгоритму.

  1. Берем набор родителя и копируем. Это будет ul.outer и его набор выглядит так:

    создатель имя значение
    ul.outer global 0
    ul.outer outer 0
  2. Берем предыдущий элемент того же уровня вложенности и копируем оттуда счетчики, имени которых у нас еще нет. Это будет li.outer__elem:nth-child(3) и его набор выглядит так:

    создатель имя значение
    ul.outer global 6
    ul.outer outer 2

    Здесь есть счетчики outer и global, которые у нас уже есть, значит ничего не делаем.

  3. Берем предыдущий элемент в порядке дерева и копируем оттуда значения счетчиков, имена которых у нас уже есть и создатель совпадает. Это будет li.inner__elem:nth-child(3) и его набор выглядит так:

    создатель имя значение
    ul.outer global 6
    ul.outer outer 3
    ul.inner inner 2

    Из счетчиков, что уже есть в наборе элемента после пункта 2, здесь есть только здесь есть счетчики outer и global, значит копируем их значения.

Итого: наследованный набор счетчиков для элемента li.outer__elem:nth-child(4) будет выглядеть так:

создатель имя значение
ul.outer global 6
ul.outer outer 3

Как видите, внутреннему счетчику просто неоткуда просочится сюда.

В конце можно еще раз сделать вывод, что на значения счетчиков могут влиять предков и дети предков, то есть всё дерево сверху, а на наличие самих счетчиков на этом элементе только предки этого уровня вложенности и высших.

Источники