Аккордеон в Тильде: чем его делают и что он ломает в Zero Block
Отдельного блока-аккордеона в разобранном рантайме Тильды нет: слово `accordion` не встречается ни разу на 114 файлах, при том что `t396` в тех же файлах встречается 2561 раз. Раскрывающийся текст в Тильде всё же есть — но он инлайновый, живёт внутри абзаца, и на живой странице разворачивается только в одну сторону. Разберём его код целиком и посчитаем, что происходит с высотой Zero Block, когда текст внутри него становится длиннее.
Задача звучит просто: список вопросов, каждый раскрывается по нажатию. В интерфейсе Тильды ищется блок с таким названием, а в поиске — «аккордеон в тильде». Дальше начинается расхождение между тем, как задачу называют, и тем, из чего её на Тильде реально собирают. Разберём по разметке и по коду: что для раскрывающегося текста в Тильде уже есть, чем это не аккордеон, и почему внутри Zero Block вопрос из «как оформить» превращается в «что с высотой».
Короткий ответ
Блока-аккордеона в разобранном рантайме Тильды нет. Есть инлайновая подрезка
текста — ql-undercut, кнопка «Читать далее» прямо внутри абзаца, — и есть блок
табов T817 с готовой ARIA-разметкой. Самодельный аккордеон в Zero Block упирается
не в вёрстку, а в высоту артборда: она задана числом на каждый брейкпоинт.
Что в Тильде уже есть: подрезка текста
Первое, что стоит знать: у Тильды под это отведён отдельный файл рантайма,
tilda-undercut-1.0.min.js, 5383 байта. Он маленький, поэтому его удалось
прочитать целиком, а не по фрагментам, — всё, что ниже, взято из него и из живой
разметки, а не из документации.
Выглядит подрезка в готовой странице так (снято с сайта на Тильде, перенос строк мой):
<span class="ql-undercut"
data-text="Мы точно можем рекомендовать Тильду для создания сайтов…"
data-show-text="Читать далее"
data-hide-text=""
data-color="#000000"
data-style="solid">
<span contenteditable="false">Читать далее</span>
</span>
Ключевое здесь — тег. Это <span>, и в стилях, которые скрипт сам вставляет в
<head>, у него display: inline. Подрезка не блок и не секция: она живёт внутри
абзаца, в потоке текста. Аккордеон из неё не собрать по устройству — у неё нет ни
заголовка секции, ни второго уровня, ни группы, внутри которой открыт ровно один
элемент.
В 22 сохранённых страницах ql-undercut встретился 14 раз, и все 14 — внутри
записи типа 605, блока с отзывами. Ни одного вхождения в Zero Block.
Чем подрезка не аккордеон: четыре подробности из кода
Свернуть обратно нельзя, если поле «Скрыть» пустое. В живой разметке
data-hide-text="", и обработчик на это отвечает буквально:
var a = i.getAttribute("data-hide-text") || "";
…
if (!a) return; // кнопку «Скрыть» не создаём
То есть текст раскрывается один раз и остаётся раскрытым до перезагрузки страницы. Для отзыва это нормально, для аккордеона — нет: аккордеон обязан закрываться.
Надпись по умолчанию — [+]. Не «Читать далее», не «Подробнее»:
u.innerHTML = n || "[+]";
Если поле подписи оставить пустым, посетитель увидит на странице ровно два символа в квадратных скобках.
Многоточие — две точки, а не три. Скрипт дописывает их в предыдущий текстовый узел, а не в свой:
e.textContent = n.slice(0, r + 1) + ".." + n.slice(r + 1);
Обход идёт по previousSibling, а если не нашлось — вверх, на
parentNode.previousSibling. Из-за этого снятие многоточия при закрытии сложнее
установки: функция ищет lastIndexOf("..") и умеет заходить только в
разрешённый список инлайновых тегов — SPAN, EM, STRONG, S, U, SUP, SUB, A, I, B, Q. Вставили внутрь подрезки что-то не из этого списка — точки при закрытии
останутся.
Скрытый текст не спрятан. Он лежит в data-text в открытом виде, в том же
HTML. При нажатии скрипт достаёт его оттуда и разэкранирует:
var o = t_quillUndercut_unEscapeHtml(i.getAttribute("data-text") || "");
Практический вывод в две стороны. Подрезкой нельзя убрать текст из индекса — он в исходнике страницы. Зато и бояться за индексацию не нужно: и поисковик, и языковая модель прочитают скрытую часть, не нажимая кнопку.
Табы T817: то, что ближе всего по смыслу
Если задача формулируется как «одна секция открыта, остальные закрыты», то в Тильде это уже есть — блоком табов. Разметка из живой страницы, запись типа 817:
<ul class="t817__wrapper" role="tablist" data-tab-current="1">
<li class="t817__tab" role="presentation" data-tab-number="1">
<button type="button" class="t817__tab-name"
id="tab1_750744168" role="tab"
aria-selected="false"
aria-controls="content-tab1_750744168"
tabindex="-1">Годовой доступ</button>
</li>
…
</ul>
Здесь стоит отметить то, что в самодельных аккордеонах обычно забывают: роли
расставлены полностью — tablist на списке, presentation на <li>, tab на
настоящей кнопке, есть aria-selected, есть aria-controls, есть управление
tabindex. На узких экранах тот же блок печатает второй вариант интерфейса:
<select class="t817__select" data-tab-mobile-number="1">
Две оговорки. Первая: в статическом исходнике у всех кнопок
aria-selected="false" и tabindex="-1" — активную выбирает скрипт, поэтому без
JavaScript ни один таб не открыт. Вторая: определения t817_init в разобранных
114 файлах нет — оно приходит постраничным бандлом. Значит про разметку T817 можно
говорить уверенно, а про внутренности его скрипта — нет.
Аккордеон в Zero Block: спор не с вёрсткой, а с высотой
Здесь самое важное. Высота артборда в Zero Block — это записанное число, причём на каждый брейкпоинт своё. С живой страницы:
<div class="t396__artboard"
data-artboard-height="1310"
data-artboard-height-res-320="726"
data-artboard-height-res-480="799"
data-artboard-height-res-640="1220"
data-artboard-height-res-960="1274">
И те же числа продублированы в CSS страницы:
.t396__artboard { height: 1310px; … }
Элемент, который стал выше, этих чисел не меняет. Он либо вылезает за край артборда, либо перекрывает соседей — оба симптома описывают одну причину.
Дальше вопрос: а сама Тильда как пересчитывает высоту, когда текст
раскрывается? Ответ выписан из подрезки — она вызывает свой
t_quillUndercut__updateHeight(recId), и вот полный список того, что он трогает:
| Что нашлось в записи | Что вызывается |
|---|---|
.t-slds | t_slds_UpdateSliderHeight |
.t-cover | t_cover__recalcContentHeight |
.t-quiz | t_quiz__adjustStepToHeight |
| тип записи 1068, 1069, 1070, 1071, 1072, 822 | t<тип>_init(recId) заново |
| тип записи 517 | t517_unifyHeights |
| в конце — всем | событие tildaclamp:update |
Типа 396 в этом списке нет. И событие до Zero Block тоже не доходит: слово
tildaclamp в tilda-zero-1.1.min.js не встречается ни разу — при том, что
t396 в этом же файле встречается 498 раз. Слушают tildaclamp:update только
слайдер и обрезка текста по строкам.
То есть путь пересчёта у Zero Block есть, он просто подключён к другому событию — к переключению табов:
"yes" === e.getAttribute("data-connect-with-tab") &&
i.addEventListener("displayChanged", function () {
t396_allelems__renderView(r);
t396_allgroups__renderView(r);
t396_doResize(t, !0);
});
Исключение, о котором стоит знать: режим hug
Не всякий артборд живёт с фиксированным числом. У одного из разобранных стоит
data-artboard-heightmode="hug", и в коде это включает пересчёт:
var u = "hug" === r.getAttribute("data-artboard-heightmode");
if (u) { /* дроссель на t396__updateAutoHeight */ }
t396__updateAutoHeight не просто меняет стиль — он записывает новую высоту
обратно в атрибут и рассылает по блоку своё событие:
t.setAttribute("data-artboard-height", r); // или -res-<брейкпоинт>
t396_ab__renderView(t);
t.closest(".t396").dispatchEvent(new CustomEvent("zero:height-changed"));
Две подробности, которые пригодятся. Первая: измеряет он только прямых детей
артборда — :scope > .tn-group, :scope > .tn-elem, — так что выросший элемент
внутри группы внутри группы в расчёт может не попасть. Вторая:
zero:height-changed в разобранном корпусе слушает ровно один файл,
tilda-animation-ext-1.0.min.js, — то есть на это событие рассчитывать как на
общий сигнал «высота поменялась, пересчитайте себя» нельзя.
Что из этого следует делать
Три варианта, по возрастанию цены:
Один длинный текст, который надо укоротить. Штатная подрезка, и заполните поле «Скрыть» — иначе развернётся один раз и навсегда. Аккордеоном это не станет, но задачу «много текста на маленьком экране» решает без кода.
Несколько разделов, открыт один. Табы T817. Роли и клавиатура уже расставлены, на мобильных сам превратится в список выбора. Если внутри таба стоит Zero Block — включите ему связь с табом, иначе он не пересчитает себя при показе.
Настоящий аккордеон внутри Zero Block. Здесь придётся писать код, и главная
работа не в раскрытии, а в высоте: артборд надо либо перевести в hug, либо
пересчитывать высоту самому после каждого раскрытия. Про соседние элементы
помните: они позиционированы абсолютно, и «подвинуть всё, что ниже» само не
произойдёт.
Доступность: две вещи, которые Тильда не доделала
Обе видны прямо в коде подрезки, и обе стоит починить, если делаете своё.
aria-expanded до первого клика нет. Кнопка, которую скрипт создаёт при
разборе страницы, получает aria-label и aria-controls, но не aria-expanded.
Атрибут появляется только у кнопок, созданных позже, при нажатии. То есть в
исходном состоянии скринридер не знает, свёрнуто это или развёрнуто.
Перевод есть, но не там, где видно. В скрипте лежит словарь на 13 языков — для
русского «Показать» и «Скрыть», — и язык он определяет аккуратно: сначала
data-tilda-project-lang у #allrecords, потом navigator.language, иначе EN.
Но используется этот перевод только в aria-label. Видимая надпись всегда берётся
из поля или превращается в [+]. Получается расхождение: глаз видит [+],
скринридер произносит «Показать».
Откуда числа
Слово accordion искалось по 114 файлам рантайма Тильды: 0 вхождений. Контроль
замера в том же проходе — t396 в тех же файлах даёт 2561 вхождение, значит
файлы читались и ноль настоящий. Вхождения считались не построчно: файлы рантайма
минифицированы в одну строку, и grep -c вернул бы 1 независимо от их числа.
tilda-undercut-1.0.min.js прочитан целиком. ql-undercut и data-show-text —
по 14 вхождений в 22 страницах, контроль на тех же страницах:
data-record-type — 995 вхождений. Высоты артборда и разметка табов сняты с двух
разных живых страниц.
И оговорка, без которой перепись врёт. Отсутствие в этом корпусе не равно
отсутствию в Тильде: у Тильды отложенная привязка через t_onFuncLoad, поэтому
функция может вызываться в файле, который у нас есть, а определяться в
постраничном бандле, которого нет. Именно так вышло с t817_init — вызов виден,
определения в корпусе нет. Поэтому про механику подрезки и про высоту артборда
здесь сказано утвердительно, а про «блока-аккордеона не существует» — только то,
что его нет в разобранном.
Где проходит граница MOD-184
MOD-184 не заменяет штатный T817 и обычные аккордеонные блоки.
Он предназначен для более узкого случая: дизайнер собрал пары заголовок-панель
внутри flex Zero Block, на странице нужно несколько независимых групп, а
hug-артборд должен вырасти после раскрытия.
Мод не двигает элементы через top и left, связывает пары внутри каждой
группы, использует hidden и aria-expanded, а после изменения вызывает
t396__updateAutoHeight на hug-артборде и посылает zero:height-changed.
Сниппет, параметры и разметка ролей опубликованы на карточке MOD-184.