Разработка табов и аккордеонов на Vue.js для 1С-Битрикс
Таб-компоненты и аккордеоны кажутся простой задачей до момента, когда появляются требования: сохранять активный таб в URL, подгружать контент вкладки лениво при первом открытии, синхронизировать состояние нескольких аккордеонов на странице. Стандартные битриксовые шаблоны реализуют табы через jQuery .show()/.hide() — весь контент рендерится в DOM сразу, URL не обновляется, глубокая ссылка на вкладку невозможна. Vue решает эти задачи без лишнего кода.
Архитектура Vue-компонентов для Битрикс
Табы строятся на паре компонентов: TabGroup управляет активным состоянием, TabPanel рендерит контент активной вкладки. Ключевое решение — как хранить активный таб:
-
ref()внутри компонента — если URL не нужен -
useRouteиз Vue Router — для SPA-страниц с полным роутингом -
URLSearchParamsчерез нативныйhistory.pushState— для страниц Битрикса без Vue Router, когда нужна глубокая ссылка
// Сохранение активного таба в URL без Vue Router
const activeTab = ref(new URLSearchParams(location.search).get('tab') || 'description');
watch(activeTab, (tab) => {
const url = new URL(location.href);
url.searchParams.set('tab', tab);
history.replaceState({}, '', url);
});
Это позволяет пользователю скопировать ссылку на конкретную вкладку — критично для карточек товаров с вкладками «Характеристики», «Отзывы», «Доставка».
Аккордеоны требуют плавной анимации высоты, что нетривиально в CSS: height: auto не анимируется. Решение через Vue <Transition> с JavaScript hooks:
function onEnter(el) {
el.style.height = '0';
el.offsetHeight; // force reflow
el.style.height = el.scrollHeight + 'px';
}
function onAfterEnter(el) {
el.style.height = 'auto';
}
function onLeave(el) {
el.style.height = el.scrollHeight + 'px';
el.offsetHeight;
el.style.height = '0';
}
Это даёт нативную анимацию высоты без библиотек, которая работает при любом контенте внутри.
Интеграция с данными Битрикс
Контент вкладок из инфоблоков. Карточка товара с вкладками: «Описание» — DETAIL_TEXT из b_iblock_element, «Характеристики» — свойства из b_iblock_element_property, «Отзывы» — данные инфоблока отзывов. В result_modifier.php формируется массив:
$arResult['TABS'] = [
['id' => 'description', 'title' => 'Описание', 'content' => $arResult['DETAIL_TEXT']],
['id' => 'props', 'title' => 'Характеристики', 'props' => $arResult['DISPLAY_PROPERTIES']],
['id' => 'reviews', 'title' => 'Отзывы', 'lazy' => true, 'endpoint' => '/api/reviews/?id=' . $arResult['ID']],
];
Вкладка «Отзывы» помечена флагом lazy: true — контент подгружается через AJAX только при первом открытии. loading состояние компонента — спиннер на время запроса.
Аккордеон FAQ из инфоблока. Шаблон компонента bitrix:news.list выводит вопросы и ответы. Vue-компонент монтируется на контейнер, разбирает существующий HTML через querySelectorAll (прогрессивное улучшение) или получает данные через data-items JSON.
Кейс: табы на странице тарифов с динамическим переключением периода
SaaS-продукт на Битриксе, страница тарифов с табами «Месячная оплата» / «Годовая оплата». При переключении таба цены на всех карточках тарифов должны меняться без перезагрузки.
jQuery-решение требовало дублировать весь HTML с ценами — один набор для месячных цен, второй для годовых, и переключать видимость. При 6 тарифах — 12 блоков HTML, трудно поддерживать.
Vue-решение: один компонент PricingTabs, данные из window.BX_STATE.pricing:
const period = ref('monthly'); // 'monthly' | 'yearly'
const plans = computed(() =>
rawPlans.value.map(plan => ({
...plan,
price: period.value === 'yearly'
? Math.round(plan.yearlyPrice / 12)
: plan.monthlyPrice,
badge: period.value === 'yearly' ? `Экономия ${plan.yearlySaving}%` : null
}))
);
Цены хранятся в JSON, вычисляются реактивно при смене периода. Анимация смены цены — <Transition name="price-flip"> с CSS rotateX. Данные в PHP заполняются из инфоблока «Тарифы» с полями MONTHLY_PRICE, YEARLY_PRICE. Разработка — 2 дня.
Accessibility и SEO
Табы без ARIA — распространённая проблема. Правильная разметка:
<div role="tablist">
<button role="tab" :aria-selected="activeTab === 'desc'" :aria-controls="'panel-desc'">
Описание
</button>
</div>
<div role="tabpanel" id="panel-desc" :hidden="activeTab !== 'desc'">
<!-- контент -->
</div>
Атрибут hidden вместо v-show с display: none — screen readers корректно игнорируют скрытые панели. Клавиатурная навигация между вкладками — ArrowLeft/ArrowRight через onKeydown.
SEO-аспект: поисковики индексируют контент вкладок, если он присутствует в HTML. При v-if (ленивый рендер) — первая вкладка рендерится серверно через Bitrix-шаблон, остальные загружаются через AJAX. Для Googlebot это приемлемо — он выполняет JavaScript, но с задержкой. Для критичного SEO-контента (характеристики товара) — всегда рендерим в HTML, прячем через hidden.
Этапы работ
| Компонент | Ориентировочный срок |
|---|---|
| Табы со статичным контентом + URL-сохранение | 1-2 дня |
| Табы с ленивой загрузкой вкладок | 2-3 дня |
| Аккордеон с анимацией | 1 день |
| Связанные табы + динамические данные (как пример с ценами) | 3-5 дней |
Сроки определяются количеством источников данных, требованиями к анимациям и сложностью интеграции с Битрикс-компонентами.







