Розробка табів і акордеонів на 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 днів |
Терміни визначаються кількістю джерел даних, вимогами до анімацій та складністю інтеграції з Бітрікс-компонентами.







