Статьи

Управление контекстами стекирования CSS в «враждебной» среде

Примечание: это предложение, которое я недавно написал для Yahoo. Он был отредактирован для внешней аудитории.

Порядок, в котором дерево рендеринга нарисовано на холсте, описывается в терминах стековых контекстов.

Ситуация усложняется, когда авторы не знакомы с контекстами стекирования страницы или просто не замечают своего состояния.

TL: DR; ПредложениеДемо

Руководство по интерактивному рекламному бюро z-index

С точки зрения IAB все довольно просто. У них есть целая таблица, в которой указаны диапазоны z-indexпрактически для всех типов контента , а не только для рекламы.

Диапазон Z-индекса Тип содержимого Детали
<0 Фоновые элементы
0 — 4,999 Основное содержание, стандартные объявления Стандартные рекламные теги на месте с обычным содержанием. Включает сообщение саморегулирования OBA (объявление CLEAR)
5000 — 1 999 999 Расширение рекламы Весь расширяемый рекламный блок должен быть установлен в этом диапазоне
2 000 000 — 2 999 999 Плавающая реклама Над страницей объявлений (OTP’s)
3 000 000 — 3 999 999 Всплывающие элементы Окна чата, уведомления о сообщениях
4 000 000 — 4 999 999 Не закрепленные плавающие элементы Обзорные панели набора
5 000 000 — 5 999 999 Расширение элементов навигации по сайту Раскрывающаяся навигация, предупреждения сайта и т. Д. На этом уровне должна быть включена только расширяющаяся часть элементов навигации.
6,000,000+ Наложения на всю страницу Полноэкранные рекламные объявления (OTP) и межстраничные объявления, если они охватывают контент страницы

Источник: IAB Display Advertising Guidelines

Насколько большим может быть z-index

В спецификациях CSS не указан верхний предел для z-indexсуществует максимальное значение из-за типа переменной, используемой для хранения этого значения (32-разрядное целое число со знаком); таким образом, ограничение в современных браузерах составляет 2,147,483,647

Несмотря на это, есть рекомендации по рекламе, которые рекомендуют использовать 2,147,483,648

Что происходит, когда используются большие значения, например, то, что я однажды нашел на странице?

 <div id="fresco-thirdparty-tag" style="position: absolute;
     z-index: 10000000000; display: none; top: 0; left: 0;
     width: 970px; height: 250px;">

Современные браузеры рассматривают такие значения как верхний предел. Другими словами, использование 100,000,000,0002,147,483,647

Имеет ли это какое-либо значение?

Краткий ответ: нет, это не имеет значения, один бит .

И это потому, что контексты стека являются атомарными:

Контексты стека могут содержать дополнительные контексты стека. Контекст стекирования является атомарным с точки зрения его родительского контекста стекирования; ящики в других контекстах стекирования не могут находиться между любыми из них.

9.9 Многоуровневая презентация

Другими словами, никакое значение z-index . Вот почему невозможно управлять стеками предсказуемым образом, не зная о дереве документа и контекстах стека на странице.

Так для чего нужны директивы z-index

На чем основаны эти рекомендации? Порядок исходного кода, а также z-порядок предков играют слишком большую роль, когда дело доходит до рисования дерева рендеринга, поэтому простая установка диапазонов z-indexне может быть решением.

Например, если вы посмотрите на динамическую демонстрацию рекомендаций IAB Z-Index, вы увидите, что все работает хорошо, потому что они были разработаны с учетом определенного набора требований ; но на самом деле это довольно хрупкая конструкция, поскольку даже простые изменения могут потребовать переосмысления большинства стековых контекстов на этой странице.

Во-первых, представьте, что стилизуете «Заголовок» этой демонстрационной страницы с помощью position: fixed какое значение z-index Поскольку заголовок должен отображаться поверх «300 × 250 базовых объявлений», его z-index2,000,000 Но обратите внимание, что все, что выше этого значения, заставляет заголовок конкурировать с «плавающими объявлениями», для которых диапазон z-index2,000,000 - 2,999,999

Теперь представьте, что у нас есть окно поиска над левой / правой навигацией с выпадающим меню « Search Assist »; какое значение z-index Поскольку раскрывающийся список должен отображаться поверх nav, его значение z-index6,000,000 Но обратите внимание, что все, что выше этого значения, заставляет выпадающий список конкурировать с «Наложением полной страницы», для которого диапазон z-index6,000,000+

Конечно, есть и вопросы, связанные с «государством». Для любого данного ящика может потребоваться, чтобы он располагался по-разному в стеке в зависимости от его поведения или поведения окружающих ящиков (выпадающие списки, всплывающие подсказки, всплывающие подсказки, расширение рекламы и т. Д.). В таких случаях значения z-index

Рецепт катастрофы

Как показывает демонстрация IAB, ящики, которые не принадлежат стеку, могут быть расположены в любом месте относительно других стеков . По этой причине управление «гранулярными» стеками требует централизации значений z-indexобязательно приведет к сбою в многопрофильной среде, где разработчики могут не иметь доступа ни к данным, ни к инструменту ( т.е. переменные в файле препроцессора ).

Предложение

Речь идет о реализации защитного механизма для управления контекстами стекирования через страницу. Нам нужно решение, которое позволит нам позиционировать блок относительно других блоков в том же стеке или даже блоков вне стека, к которому он принадлежит.

Явные контексты

Идея состоит в том, чтобы стилизовать основные блоки страницы для создания явного порядка стеков «верхнего уровня» (см. Демонстрацию ). Это использует атомарную природу контекстов суммирования и обеспечивает сдерживание на самом высоком уровне. Затем мы можем предсказать поведение вложенных блоков независимо от их собственных значений z-index .

Динамические стеки

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

Возможность изменения контекста стека имеет решающее значение, потому что бывают случаи, когда блоки должны располагаться по-разному в стеке (см. «Подсказки» и «реклама» в демонстрационной версии ).

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

Декларативный переключатель

Мы используем атрибуты data-*z-indexПример кода ниже ).

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

Тот же механизм можно использовать для сброса z-indexдемонстрационной версии ).

Более явные контексты

Удаление блока из основного контекста стека позволяет его вложенным контекстам стекирования стать частью более высокого стека. Это означает, что z-index

Например, в демоверсии , когда запускается модальный режим, удаление правой направляющей из контекста наложения верхнего уровня позволяет объявлению конкурировать с другими основными блоками на странице. Другими словами, если бы у объявления был z-index10+в верхней части заголовка .

Из-за риска, связанного со сбросом z-indexz-index Это могут быть объявления или другие модули, которые не контролируются владельцами страниц. «Песочница» этих элементов предотвращает появление ящиков в любом месте на оси «z». Таким образом, мы все еще можем контролировать эти блоки, даже если их предок стилизован с помощью z-index: auto

Чтобы лучше понять эту проблему, посетите эту демонстрационную страницу, где ни песочница, ни правая рейка, ни ее модули. Прокрутите страницу немного и наведите указатель мыши на объявление. Вы должны увидеть объявление, перекрывающее заголовок и «кнопку» для модального окна, просматривающего объявление.

Обе проблемы связаны с идентичными значениями z-index Заголовок отображается за объявлением, потому что оно идет первым. Модальная кнопка отображается перед рекламой, потому что она идет последней.

Реализация

Это решение требует совместного использования простой настройки и общего словаря между сетками (когда задействованы различные сетки, как, например, для сайтов Yahoo — Финансы, Спорт, Домашняя страница, Ответы и т. Д.).

«Четырехступенчатая программа»:

  1. Все основные поля на странице должны располагаться и иметь стиль с z-indexauto
  2. Каждый из этих блоков должен быть идентифицирован с помощью общего класса: stacking-context
  3. Эти же блоки должны иметь data-zindex-maxdata-zindex-topz-index
  4. Класс inner-stackлюбого модуля, который может быть стилизован с высоким значением z-index

Например:

 <div id="right-rail" class="stacking-context" 
     data-zindex-max="5" data-zindex-top="10">
  <div class="inner-stack">
    <div id="fresco-thirdparty-tag"
         style="position: absolute; z-index: 10000000000;">

Данные-ZIndex-макс
Этот атрибут используется для объявления наивысшего z-порядка, с которым допускается стилизовать блок. Например, это может быть столбец, который должен всегда отображаться за заголовком.

Данные-ZIndex-топ
Этот атрибут используется для объявления самого высокого z-порядка, установленного на странице. Например, это будет позиция заголовка внутри стека. Любой элемент, стилизованный под это значение z-index

.inner-стек
Этот класс используется для контроля модулей, предотвращая вступление в действие их собственного z-index Это значение z-index

Преимущества этого решения

Снижает риск поломки
Используя атомарную природу контекстов стекирования, мы можем изолировать модули-песочницы, делая их значения z-index

Это предсказуемо
Нет больше угадайку; авторам не нужно ничего знать о дереве документа или его контекстах суммирования, чтобы иметь возможность перемещать блоки через стеки.

Централизует обязанности
Все находится в руках команды, ответственной за саму страницу — команды, которая знает все о документе — его дереве, его стековом контексте и т. Д.

Дальнейшее мышление

В проектах, которые используют одну сетку, а также в проектах, которые используют общую сетку, вещи можно сделать более декларативными с помощью предустановки правил для замены z-index Например:

  • .coverTheHeader {...}
  • .coverAllColumns {...}
  • .coverTheRightRail {...}
  • .coverTheLeftRail {...}
  • .coverTheMainContent {...}
  • и т.п.

Пример кода

Ниже приведен код JavaScript, используемый на главной демонстрационной странице . Этот скрипт отвечает за переключение значения z-index Он также «освобождает» эти элементы, сбрасывая значение z-index Именно сочетание обоих этих стилей позволяет этим элементам вести себя так, как ожидается, даже если к ним применяется очень высокий z-index

 (function(document) {
  function findAncestor(el, cls) {
    while ((el = el.parentElement) && !el.classList.contains(cls));
    return el;
  }

  // constants
  var AUTO = 'auto',
  CSS_MODAL_ON = 'modal-on',
  CSS_INNER_STACK = 'inner-stack',
  DOC = document,
  WIN = DOC.defaultView,
  HTML = DOC.documentElement,

  // elements
  theAd = DOC.getElementById('ad'),
  theAncestorStack = findAncestor(theAd, 'stacking-context'),
  theInnerStack = DOC.getElementsByClassName(CSS_INNER_STACK)[0],
  theModal = DOC.getElementById('modal'),
  theStackUpdate = DOC.getElementById('stackUpdate'),

  // globals
  theInnerStackAncestor,
  zIndexAncestor = WIN.getComputedStyle(theAncestorStack, null).zIndex,
  zIndexCurrent, // will be assigned
  zIndexInnerStack = WIN.getComputedStyle(theInnerStack, null).zIndex,
  zIndexMax = theAncestorStack.getAttribute('data-zindex-max'),
  zIndexTop = theAncestorStack.getAttribute('data-zindex-top');

  function switchStackingContext (e) {
    // we move the ancestor through the stack according to zIndexMax value (highest value allowed for that ancestor box)
    if (WIN.getComputedStyle(theAncestorStack, null).zIndex === zIndexMax) {
      theAncestorStack.style.zIndex = zIndexAncestor;
    } else {
      theAncestorStack.style.zIndex = zIndexMax;
    }

    // we get the ancestor that wraps boxes with potential high/crazy z-index
    theInnerStackAncestor = findAncestor(e.target, CSS_INNER_STACK);

    // we check for the actual value before we change it
    zIndexCurrent = WIN.getComputedStyle(theInnerStackAncestor, null).zIndex;

    // we reset the z-index from the parent ancestor allowing the box to move up (wherever it wants to)
    if (zIndexCurrent !== AUTO) {
      theInnerStackAncestor.style.zIndex = AUTO;
    } else {
      theInnerStackAncestor.style.zIndex = zIndexInnerStack;
    }

    // we update the value on the page
    theStackUpdate.innerText = theAncestorStack.style.zIndex;
  }

  function resetStackingContext (e) {
    // we reset the z-index of the ancestor to free the inner box
    if (WIN.getComputedStyle(theAncestorStack, null).zIndex === AUTO) {
      theAncestorStack.style.zIndex = zIndexAncestor;
    } else {
      theAncestorStack.style.zIndex = AUTO;
    }

    // we get the ancestor that wraps boxes with potential high/crazy z-index
    theInnerStackAncestor = findAncestor(e.target, CSS_INNER_STACK);

    // we check for the actual value before we change it
    zIndexCurrent = WIN.getComputedStyle(theInnerStackAncestor, null).zIndex;

    // we reset the z-index from the parent ancestor allowing the box to move up (wherever it wants to)
    if (zIndexCurrent !== AUTO) {
      theInnerStackAncestor.style.zIndex = AUTO;
    } else {
      theInnerStackAncestor.style.zIndex = zIndexInnerStack;
    }

    // we toggle this class to contextually style the modal (nothing to do with the solution per se)
    HTML.classList.toggle(CSS_MODAL_ON);

    // we update the value on the page
    theStackUpdate.innerHTML = theAncestorStack.style.zIndex;
  }

  theAd.addEventListener('mouseenter', switchStackingContext, false);
  theAd.addEventListener('mouseleave', switchStackingContext, false);
  theModal.addEventListener('click', resetStackingContext, false);

}(document));

Для справки ниже приведены две демонстрационные ссылки, упомянутые в статье. Первый (упоминаемый в статье несколько раз) — это JavaScript, который «управляет» стеком, когда пользователи взаимодействуют с рекламой или «модальным». Второй тоже самое без сценария. Он включен, чтобы показать проблемы, с которыми мы столкнулись бы, если бы полагались на третьих лиц для управления стеками.