Zero Block: одинаковый набор элементов во всех адаптивах
В Zero Block набор элементов один на все разрешения: элемент — это один узел в разметке, а адаптив задаёт ему другие числа, а не другой элемент. Отсюда общий приём: ненужное на этом разрешении увозят координатами на (5000,5000). Полосы прокрутки не появляется, и кажется, что элемента больше нет. Замер показывает обратное: он остаётся видимым для браузера, картинка качается, скринридер его читает, а табуляция до его ссылки уносит с экрана всё остальное.
Короткий ответ
Набор элементов один потому, что элемент — один узел разметки, а адаптив меняет ему
только числа. Вынос на (5000,5000) элемент не убирает: замер показывает
display: block, запрошенную картинку, заголовок со ссылкой в дереве доступности и
прокрутку артборда при табуляции. Убирает — display: none классами видимости
Тильды на блок.
Почему требование «одинаковый набор» вообще существует
Это не ограничение редактора, а следствие того, как Zero Block устроен в разметке. Вот один текстовый элемент со снятой страницы, как он лежит в HTML:
<div class='t396__elem tn-elem tn-elem__10245705061738663644970'
data-elem-id='1738663644970'
data-elem-type='text'
data-field-top-value="312"
data-field-left-value="272"
data-field-height-value="112"
data-field-width-value="896"
data-field-axisy-value="top"
data-field-container-value="grid">
Дальше у того же узла идут наборы на каждое разрешение —
data-field-top-res-960-value, data-field-left-res-640-value,
data-group-width-res-480-value и так далее. Всё, что различается между
адаптивами, — числа: координаты, размеры, единицы, выравнивание. Ни одного
атрибута вида «а на 320 этого элемента нет» в схеме не предусмотрено.
Числа на снятой странице:
| Что | Сколько |
|---|---|
| элементов всего | 18 |
| из них текст / картинка / кнопка / видео | 12 / 4 / 1 / 1 |
| артбордов | 2 |
| разрешений сверх базового | 4 (960, 640, 480, 320) |
атрибутов …-res-320-… | 92 |
атрибутов …-res-480-… | 93 |
атрибутов …-res-640-… | 69 |
атрибутов …-res-960-… | 81 |
Восемнадцать элементов — восемнадцать узлов, и на каждом до пяти наборов координат. Требование «одинаковый набор во всех адаптациях» — это просто другой способ сказать «узел один».
Что остаётся от элемента, увезённого на (5000,5000)
Стенд повторяет условия Тильды буквально: артборд объявлен так же, как в её
стилях, — position: relative; width: 100%; height: 100vh; overflow: hidden, —
элемент внутри стоит на top: 5000px; left: 5000px и содержит заголовок, ссылку и
картинку. Рядом — тот же набор, но убранный display: none классом самой Тильды.
Замер в Chromium, окно 1280×800:
| Что мерим | Увезён на (5000,5000) | display: none |
|---|---|---|
вычисленный display | block | none |
размер по getBoundingClientRect | 128 × 196 | 0 × 0 |
| картинка запрошена сетью | да | да |
| заголовок в дереве доступности | да | нет |
| ссылка в дереве доступности | да | нет |
| текст остался в разметке | да | да |
| горизонтальная прокрутка у страницы | нет | нет |
Читается из таблицы три вещи, и две из них против общего ожидания.
Увезённый элемент для браузера полностью существует. Он занимает 128×196 пикселей, у него честная геометрия, он участвует в раскладке. Единственное, чего он не делает, — не попадает на экран, потому что артборд его обрезает.
Вес страницы не экономит ни один из двух способов. Картинка запрошена и там, и
там. display: none не отменяет загрузку изображения, объявленного в разметке
тегом <img>, — браузер решает про загрузку раньше, чем применяет стиль
видимости. Значит аргумент «увезённый элемент тяжелит страницу, а скрытый нет»
неверен: тяжелит их наличие, а не способ сокрытия.
Разница между способами — в дереве доступности. И она принципиальная: то, что
попало в дерево, скринридер прочитает как часть страницы. У увезённого элемента
там оказываются и заголовок, и ссылка, и картинка с её alt. Посетитель,
слушающий страницу, услышит два заголовка вместо одного и не поймёт, почему.
Почему поломку не видно глазами
scrollWidth документа в замере — 1280 при innerWidth 1280. Полосы прокрутки
нет, хотя элемент стоит на пять тысяч пикселей правее. Причина — в стилях самой
Тильды:
.t396__artboard { position: relative; width: 100%; height: 100vh; overflow: hidden; }
Артборд обрезает всё, что вышло за его границы, и наружу это не прорастает. Именно поэтому приём и живёт: в браузере он выглядит идеально чисто. Ни полосы прокрутки, ни сдвига раскладки, ни предупреждения в консоли — молча.
Что делает табуляция до увезённой ссылки
Это самое неприятное последствие, и его стоит воспроизвести у себя. Ссылка,
увезённая за экран, остаётся в порядке обхода клавиатурой. Браузер, ведя по нему
фокус, обязан показать элемент — а показать он его может единственным способом:
прокрутив тот самый контейнер с overflow: hidden. Замер:
| Момент | Прокрутка артборда | Позиция видимого заголовка |
|---|---|---|
| до фокуса | left: 0, top: 0 | left: 40 |
| фокус на увезённой ссылке | left: 3848, top: 4396 | left: −3808 |
То есть посетитель нажал Tab несколько раз — и содержимое первого экрана уехало на 3848 пикселей влево, а на его месте оказалось пустое место. Вернуть его обратно нечем: полосы прокрутки у артборда нет, а фокус стоит на элементе, которого не видно.
С клавиатуры по сайтам ходят не только незрячие: любой, у кого не работает мышь, любой, кто заполняет форму быстро, и любой браузер в режиме чтения. Это не редкий случай.
Чем это заменить: то, что делает сама Тильда
У Тильды для этого есть готовые классы, и их видно в её стилях — по пять брейкпоинтов в каждую сторону:
.t-screenmax-320px { display: none; } /* спрятать на 321 и шире */
.t-screenmax-480px { display: none; }
.t-screenmax-640px { display: none; }
.t-screenmin-320px { display: none; } /* спрятать на 320 и уже */
.t-screenmin-480px { display: none; }
.t-screenmin-640px { display: none; }
.t-screenmin-980px { display: none; }
.t-screenmin-1200px { display: none; }
Ставятся они на блок, а не на элемент внутри артборда: в настройках блока есть поле «Показывать только на» / «Скрывать на». Значит порядок такой:
- Сначала попробовать не дублировать. Один элемент, у которого на 320 другие координаты и другой размер, — это то, для чего адаптивы Zero Block и сделаны. Двойник нужен, когда меняется не геометрия, а содержание: другой текст, другая картинка, другая кнопка.
- Если двойник нужен — два блока, а не два элемента в одном артборде.
Широкий вариант в одном Zero Block со «скрывать на 480 и уже», мобильный — в
другом со «скрывать на 481 и шире».
display: noneубирает узел из дерева доступности и из порядка обхода клавиатурой — единственный способ это сделать. - Если увезти всё-таки приходится (например, элемент нужен внутри того же
артборда из-за анимации), увезённому нужны
aria-hidden="true"иtabindex="-1"на всём фокусируемом внутри. Это лечение симптома: узел, разметка и запрос за картинкой останутся, но скринридер его пропустит и табуляция не прокрутит артборд.
Так делает и сама Тильда: на снятой образовательной странице мобильный и настольный варианты одного экрана — два разных блока с классами видимости, а не один блок с увезёнными элементами.
Что это меняет для индексации
Обычный вопрос: «а поисковик не решит, что у меня дублируется текст?» Ответ ровный
для обоих способов: текст остаётся в разметке и там, и там. Ни display: none,
ни координаты его не убирают, краулер его скачивает. Разница только в том, что
скрытый через display: none текст поисковик обычно понижает в вес, а увезённый
координатами выглядит как обычный видимый текст.
Практический вывод не про санкции, а про смысл: если у вас на странице два
заголовка H1 — один для широкого экрана, один для мобильного, — то в разметке их
два независимо от способа сокрытия. Мобильному двойнику лучше быть не <h1>, а
<div> с теми же стилями.
Проверка за минуту на своей странице
В консоли на опубликованной странице:
// 1. Кто уехал за пределы артборда. Пустой список — приёма на странице нет.
[...document.querySelectorAll('.t396__artboard .tn-elem')].filter((el) => {
const board = el.closest('.t396__artboard').getBoundingClientRect();
const r = el.getBoundingClientRect();
return r.left > board.right || r.top > board.bottom;
}).map((el) => el.getAttribute('data-elem-type') + ' ' + el.getAttribute('data-elem-id'));
// 2. Что из увезённого попадёт под фокус клавиатурой.
[...document.querySelectorAll('.t396__artboard a, .t396__artboard button')].filter((el) => {
const board = el.closest('.t396__artboard').getBoundingClientRect();
return el.getBoundingClientRect().left > board.right;
}).map((el) => el.textContent.trim());
Если первый список не пуст, а второй пуст — приём используется, но по клавиатуре до увезённого не добраться, и это уже терпимо. Если не пуст второй — прокрутка артборда при табуляции у вас есть, и проверить это можно вживую: поставить фокус в адресную строку и нажимать Tab, глядя на первый экран.
Моды из каталога с этим приёмом уживаются: ядро проверяет, попал ли элемент в область видимости, по его настоящей геометрии, а не по факту наличия в разметке. Если нужен эффект, у которого мобильный и настольный варианты ведут себя по-разному, — это работа под задачу, и делается она двумя блоками, а не увезёнными элементами.