Проблема позиционирования div в карусели
Я не кодер и никогда им не был, поэтому весь код был навайбкоден смесью гпт, дипсик и клауди...
На сайте в Webflow есть CMS карусель, созданная через Swiper, по клику открывается модалка внутри которой лежит еще одна карусель (уже кастомная, за которую и отвечает код) с подобием историй из инсты/телеги, внутри каждой истории еще одна карусель с фотками. Отдельно от всего этого лежит div размером с историю, в котором находится навигация и пагинация.
Проблема в том, что после перехода в модалку все нормально, но если перейти на следующую историю (скорее всего) происходит неправильное вычисление позиции для активной истории, из-за чего она находится не по центру экрана.
document.addEventListener('DOMContentLoaded', () => {
const SLIDE_DURATION = 5000;
const eventSlides = Array.from(document.querySelectorAll('.swiper-slide.events'));
const modal = document.querySelector('.eventsmodalwindowswrapper');
const storiesWrapper = modal.querySelector('.storieswrapper .swiper-wrapper.stories');
const storySlides = Array.from(storiesWrapper.querySelectorAll('.swiper-slide.story-slide'));
const btnPrev = modal.querySelector('.inner-prev');
const btnNext = modal.querySelector('.inner-next');
const paginationWrapper = modal.querySelector('.pagination');
const btnClose = modal.querySelector('.closemodal');
const navWrapper = modal.querySelector('.eventsmodalnavigation');
let currentStoryIndex = 0;
let currentInnerIndex = 0;
let autoProgressTimeout;
let progressStartTime = 0;
let progressRemaining = SLIDE_DURATION;
let isPaused = false;
const targetEm = 18.75;
const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
const targetPx = targetEm * rootFontSize;
Object.assign(navWrapper.style, {
position: 'absolute',
left: '50%',
transform: 'translateX(-50%)',
transition: 'opacity 0.5s ease'
});
function resetStyles() {
clearTimeout(autoProgressTimeout);
storiesWrapper.style.transition = 'none';
storiesWrapper.style.transform = 'translateX(0)';
storySlides.forEach(s => {
s.style.width = '';
const inner = s.querySelector('.inner-swiper .swiper-wrapper');
if (inner) {
inner.style.transition = 'none';
inner.style.transform = 'translateX(0)';
}
});
}
function openStory(idx) {
currentStoryIndex = idx;
currentInnerIndex = 0;
resetStyles();
modal.style.display = 'flex';
updateSlidesStyle();
requestAnimationFrame(() => requestAnimationFrame(centerStory));
}
eventSlides.forEach((slide, idx) => {
slide.addEventListener('click', () => openStory(idx));
});
function updateSlidesStyle() {
storySlides.forEach((s, idx) => {
const overlay = s.querySelector('.stories-overlay');
if (overlay) {
Object.assign(overlay.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
pointerEvents: 'none',
transition: 'opacity 0.5s ease',
display: idx === currentStoryIndex ? 'none' : 'block',
opacity: idx === currentStoryIndex ? '0' : '1'
});
}
s.style.transition = 'width 0.5s ease';
s.style.width = idx === currentStoryIndex ? '' : `${targetPx}px`;
});
}
function centerStory() {
// Сброс трансформа для точных вычислений
storiesWrapper.style.transition = 'none';
storiesWrapper.style.transform = 'translateX(0)';
storySlides.forEach(s => {
const inner = s.querySelector('.inner-swiper .swiper-wrapper');
if (inner) {
inner.style.transition = 'none';
inner.style.transform = 'translateX(0)';
}
});
storiesWrapper.offsetHeight; // reflow
// Границы контейнера и активного слайда
const container = storiesWrapper.parentElement;
const cRect = container.getBoundingClientRect();
const active = storySlides[currentStoryIndex];
const aRect = active.getBoundingClientRect();
// Центры
const containerCenter = cRect.left + cRect.width / 2;
const activeCenter = aRect.left + aRect.width / 2;
const shift = containerCenter - activeCenter;
storiesWrapper.style.transition = 'transform 0.5s ease';
storiesWrapper.style.transform = `translateX(${shift}px)`;
updateInnerSlide();
navWrapper.style.opacity = '1';
}
function updateInnerSlide() {
clearTimeout(autoProgressTimeout);
const story = storySlides[currentStoryIndex];
const innerWrapper = story.querySelector('.inner-swiper .swiper-wrapper');
const innerSlides = Array.from(innerWrapper.querySelectorAll('.inner-slide'));
if (currentInnerIndex >= innerSlides.length) currentInnerIndex = 0;
storySlides.forEach((storySlide, idx) => {
const wrapper = storySlide.querySelector('.inner-swiper .swiper-wrapper');
if (wrapper && idx !== currentStoryIndex) {
wrapper.style.transition = 'none';
wrapper.style.transform = 'translateX(0)';
}
});
const w = innerSlides[0]?.offsetWidth || 0;
innerWrapper.style.transition = 'none';
innerWrapper.style.transform = `translateX(-${currentInnerIndex * w}px)`;
innerWrapper.offsetHeight;
innerWrapper.style.transition = 'transform 0.5s ease';
btnPrev.classList.toggle('swiper-button-disabled', currentStoryIndex === 0 && currentInnerIndex === 0);
btnNext.classList.toggle('swiper-button-disabled',
currentStoryIndex === storySlides.length - 1 &&
currentInnerIndex === innerSlides.length - 1
);
paginationWrapper.innerHTML = '';
innerSlides.forEach((_, i) => {
const bullet = document.createElement('div');
bullet.className = 'pagination-bullet';
bullet.style.borderRadius = '0.25em';
const prog = document.createElement('div');
prog.className = 'progress-bar';
prog.style.borderRadius = '0.25em';
prog.style.width = i < currentInnerIndex ? '100%' : '0%';
prog.style.transition = 'none';
bullet.appendChild(prog);
bullet.addEventListener('click', () => {
clearTimeout(autoProgressTimeout);
currentInnerIndex = i;
updateInnerSlide();
});
paginationWrapper.appendChild(bullet);
});
progressRemaining = SLIDE_DURATION;
progressStartTime = Date.now();
isPaused = false;
const activeProg = paginationWrapper.children[currentInnerIndex]?.querySelector('.progress-bar');
if (activeProg) {
activeProg.style.transition = 'none';
activeProg.style.width = '0%';
activeProg.offsetWidth;
activeProg.style.transition = `width ${SLIDE_DURATION}ms linear`;
activeProg.style.width = '100%';
}
autoProgressTimeout = setTimeout(goNext, SLIDE_DURATION);
}
function pauseProgress() {
if (!isPaused) {
clearTimeout(autoProgressTimeout);
const elapsed = Date.now() - progressStartTime;
progressRemaining = Math.max(0, SLIDE_DURATION - elapsed);
const activeProg = paginationWrapper.children[currentInnerIndex]?.querySelector('.progress-bar');
if (activeProg) {
activeProg.style.transition = 'none';
activeProg.style.width = `${(elapsed / SLIDE_DURATION) * 100}%`;
}
isPaused = true;
}
}
function resumeProgress() {
if (isPaused) {
const activeProg = paginationWrapper.children[currentInnerIndex]?.querySelector('.progress-bar');
if (activeProg) {
activeProg.style.transition = `width ${progressRemaining}ms linear`;
activeProg.offsetWidth;
activeProg.style.width = '100%';
}
progressStartTime = Date.now();
autoProgressTimeout = setTimeout(goNext, progressRemaining);
isPaused = false;
}
}
storiesWrapper.addEventListener('pointerdown', e => {
if (storySlides[currentStoryIndex].contains(e.target)) pauseProgress();
});
document.addEventListener('pointerup', resumeProgress);
function goNext() {
const innerCount = storySlides[currentStoryIndex].querySelectorAll('.inner-slide').length;
if (currentInnerIndex < innerCount - 1) {
currentInnerIndex++;
updateInnerSlide();
} else if (currentStoryIndex < storySlides.length - 1) {
currentStoryIndex++;
currentInnerIndex = 0;
updateSlidesStyle();
requestAnimationFrame(() => requestAnimationFrame(centerStory));
}
}
function goPrev() {
if (currentInnerIndex > 0) {
currentInnerIndex--;
updateInnerSlide();
} else if (currentStoryIndex > 0) {
currentStoryIndex--;
const prevCount = storySlides[currentStoryIndex].querySelectorAll('.inner-slide').length;
currentInnerIndex = prevCount - 1;
updateSlidesStyle();
requestAnimationFrame(() => requestAnimationFrame(centerStory));
}
}
btnNext.addEventListener('click', () => {
clearTimeout(autoProgressTimeout);
goNext();
});
btnPrev.addEventListener('click', () => {
clearTimeout(autoProgressTimeout);
goPrev();
});
btnClose.addEventListener('click', () => {
clearTimeout(autoProgressTimeout);
modal.style.display = 'none';
});
});
Вот скрипт, он огромный и наверное уродливый, простите.
Ссылка на прод сайта: https://catering-market.webflow.io/meropriyatiya-v-restoranah
Чтобы открыть модалку нужно пролистать до карусели и тыкнуть на любое из мероприятий.