Как сделать цикличный слайдер из табов?
Всем доброго дня!
Есть семь табов, по каждому из них контент идет простым скриншотом. Задача: табы сделать цикличным (бесконечным) слайдером и чтобы активный таб всегда был по центру. Я пробовала на swiper, но то ли еще не до конца понимаю его особенности, то ли туплю. Loop свойство работает криво, табы не центруются, а вечно уезжают влево.
Собственно, пример блока, как надо сделать (на первом экране): clickup.com
Я уже два дня с этим бьюсь и даже не понимаю с чего начать и как лучше подойти к этой задаче. Поэтому прошу совета: какие инструменты использовать? Подходит ли вообще swiper для этой задачи? Читала, у него с loop часто проблемы случаются.
Заранее спасибо!
Ответы (1 шт):
Можно и на ванильке, например так. Правда там еще есть над чем подумать в плане оптимизации.
class TabSlider {
constructor (templates) {
this.elem = templates.tabSlider.cloneNode(true);
this.tabContainer = this.elem.querySelector('.tab-container');
this.contentContainer = this.elem.querySelector('.content-container');
this.tTab = templates.tab;
this.tImg = templates.contentImg;
this.tabs = [];
this.contents = [];
this.selectedTabIndex = null;
this.tabContainer.addEventListener('click', evt => {
if (evt.target.classList.contains('tab') && !evt.target.classList.contains('tab-selected')) {
const tab = evt.target;
tab.scrollIntoView({
behavior: 'smooth',
inline: 'center'
});
setTimeout(() => {
this.changeTab(evt.target.appIndex);
this.changeContent(evt.target.appIndex);
}, 400);
}
});
new ResizeObserver(() => {
if (this.selectedTabIndex === null) return;
this.changeTab(this.selectedTabIndex);
}).observe(this.tabContainer);
}
setData (data) {
data.forEach(obj => {
const tab = this.tTab.cloneNode(true);
const img = this.tImg.cloneNode(true);
tab.textContent = obj.name;
img.src = obj.imgSrc;
this.tabs.push(tab);
this.contents.push(img);
});
this.changeTab(0);
this.changeContent(0);
}
changeTab (appIndex) {
this.selectedTabIndex = appIndex;
const selectedTab = this.tabs[appIndex].cloneNode(true);
selectedTab.classList.add('tab-selected');
const tbs = [selectedTab];
const stockCount = this.tabs.length + 1;
let index = appIndex;
for (let i = 1; i < stockCount; i++) {
index++;
if (index >= this.tabs.length) index = 0;
const tab = this.tabs[index].cloneNode(true);
tab.appIndex = index;
tbs.push(tab);
}
index = appIndex;
for (let i = stockCount - 2; i >= 0; i--) {
index--;
if (index < 0) index = this.tabs.length - 1;
const tab = this.tabs[index].cloneNode(true);
tab.appIndex = index;
tbs.unshift(tab);
}
this.tabContainer.replaceChildren(...tbs);
setTimeout(() => {
selectedTab.scrollIntoView({
inline: 'center'
});
});
}
changeContent (appIndex) {
this.contentContainer.replaceChildren(this.contents[appIndex]);
}
render () {
return this.elem;
}
}
const data = [
{
name: 'tab 1',
imgSrc: 'https://avatars.mds.yandex.net/i?id=ec714219a44b1338e90f1d70b7d52fa0_l-5233919-images-thumbs&n=13'
},
{
name: 'tab 2',
imgSrc: 'https://sun9-70.userapi.com/impf/c836232/v836232101/535eb/qJln4fQrF0c.jpg?size=1280x800&quality=96&sign=da46d0c434bc60cfebb78f4a135c01a8&c_uniq_tag=sKTXim4XymLflTB1CYcwPkyuTsT1pdrvaqmOTQnd0pA&type=album'
},
{
name: 'tab 3',
imgSrc: 'https://i.pinimg.com/originals/f0/d4/66/f0d4669bf8ac56992e8e69bc3744c1b1.jpg'
},
{
name: 'tab 4',
imgSrc: 'https://main-cdn.sbermegamarket.ru/big2/hlr-system/138/279/616/352/134/1/600011876299b0.jpeg'
},
{
name: 'tab 5',
imgSrc: 'https://avatars.mds.yandex.net/get-mpic/3986638/img_id1570444521473320416.jpeg/orig'
},
{
name: 'tab 6',
imgSrc: 'https://avatars.mds.yandex.net/get-mpic/11722550/2a0000018b4389164775c48cbec8c88dc526/orig'
},
{
name: 'tab 7',
imgSrc: 'https://zvetnoe.ru/upload/catalog/2019/10/MZ2730.jpg'
},
];
(function () {
const t = document.querySelector('#temp-slider').content;
const tElems = {
tabSlider: t.querySelector('.tab-slider'),
tab: t.querySelector('.tab'),
contentImg: t.querySelector('.content-img')
};
const slider = new TabSlider(tElems);
slider.setData(data);
document.querySelector('.main').append(slider.render());
})();
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.page {
margin: auto;
background-color: #fff;
}
.page-title {
margin: 20px 0;
color: rgb(134, 27, 255);
font-family: monospace;
text-align: center;
}
.tab-slider {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
max-width: 1024px;
width: 80%;
margin: auto;
overflow: hidden;
}
.wrapper {
display: grid;
grid-template-columns: 1fr 60% 1fr;
grid-template-rows: 1fr;
width: 100%;
}
.shadow {
pointer-events: none;
z-index: 1;
&._left {
grid-row: 1/2;
grid-column: 1/2;
background: linear-gradient(90deg, rgba(255, 255, 255, 1.0) 10%, rgba(255, 255, 255, 0.8));
}
&._right {
grid-row: 1/2;
grid-column: 3/4;
background: linear-gradient(270deg, rgba(255, 255, 255, 1.0) 10%, rgba(255, 255, 255, 0.8));
}
}
.radial-shadow {
grid-row: 1/2;
grid-column: 2/3;
width: 100%;
height: 100%;
background: radial-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
pointer-events: none;
z-index: 1;
}
.tab-container {
grid-row: 1/2;
grid-column: 1/4;
display: flex;
flex-direction: row;
gap: 60px;
padding: 10px;
height: 80px;
overflow-x: scroll;
&::-webkit-scrollbar {
display: none;
}
}
.tab {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
aspect-ratio: 1.0;
border-radius: 35%;
background-color: #afa;
user-select: none;
cursor: pointer;
transition: transform .2s linear;
&:hover {
transform: scale(1.08) translate(0, -4px);
}
}
.tab-selected {
background-color: rgb(245, 170, 255);
}
.content-img {
display: block;
width: 100%;
aspect-ratio: 1.6;
object-position: center;
object-fit: cover;
border-radius: 14px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tab Slider</title>
<link rel="stylesheet" href="index.css">
</head>
<body class="page">
<h1 class="page-title">tab slider</h1>
<main class="main"></main>
<template id="temp-slider">
<div class="tab-slider">
<div class="wrapper">
<div class="shadow _left"></div>
<div class="tab-container"></div>
<div class="radial-shadow"></div>
<div class="shadow _right"></div>
</div>
<div class="content-container"></div>
</div>
<div class="tab"></div>
<img class="content-img" src="" alt="">
</template>
<script src="index.js"></script>
</body>
</html>