|
|
/**
|
|
|
* Simple Carousel Plugin - Swiper替代方案
|
|
|
* 轻量级轮播插件,避免与平滑滚动库冲突
|
|
|
*/
|
|
|
|
|
|
(function() {
|
|
|
'use strict';
|
|
|
|
|
|
class SimpleCarousel {
|
|
|
constructor(container, options = {}) {
|
|
|
this.container = container;
|
|
|
this.options = {
|
|
|
autoplay: true,
|
|
|
autoplayDelay: 5000,
|
|
|
loop: false,
|
|
|
navigation: true,
|
|
|
pagination: true,
|
|
|
touchEnabled: true,
|
|
|
...options
|
|
|
};
|
|
|
|
|
|
this.currentIndex = 0;
|
|
|
this.slides = [];
|
|
|
this.isAnimating = false;
|
|
|
this.autoplayTimer = null;
|
|
|
this.touchStartX = 0;
|
|
|
this.touchEndX = 0;
|
|
|
|
|
|
this.init();
|
|
|
}
|
|
|
|
|
|
init() {
|
|
|
this.setupSlides();
|
|
|
this.setupNavigation();
|
|
|
this.setupPagination();
|
|
|
this.setupTouch();
|
|
|
this.setupAutoplay();
|
|
|
this.updateSlides();
|
|
|
|
|
|
// 触发初始化完成回调
|
|
|
if (this.options.onInit) {
|
|
|
this.options.onInit();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
setupSlides() {
|
|
|
const wrapper = this.container.querySelector('.carousel-wrapper');
|
|
|
if (!wrapper) return;
|
|
|
|
|
|
this.wrapper = wrapper; // 保存wrapper引用
|
|
|
this.slides = Array.from(wrapper.children);
|
|
|
this.slidesPerView = this.options.slidesPerView || 5;
|
|
|
this.totalGroups = Math.ceil(this.slides.length / this.slidesPerView);
|
|
|
|
|
|
// 设置轮播容器样式
|
|
|
wrapper.style.position = 'relative';
|
|
|
wrapper.style.overflow = 'hidden';
|
|
|
wrapper.style.display = 'flex';
|
|
|
wrapper.style.alignItems = 'center';
|
|
|
wrapper.style.transition = 'transform 0.5s ease';
|
|
|
|
|
|
// 为每个幻灯片设置样式
|
|
|
this.slides.forEach((slide, index) => {
|
|
|
if (this.slidesPerView === 1) {
|
|
|
// 单个幻灯片模式:每个幻灯片占100%宽度
|
|
|
slide.style.flex = '0 0 100%';
|
|
|
} else {
|
|
|
// 多个幻灯片模式:根据slidesPerView计算宽度
|
|
|
const slideWidth = 100 / this.slidesPerView;
|
|
|
slide.style.flex = `0 0 ${slideWidth}%`;
|
|
|
}
|
|
|
slide.style.transition = 'all 0.3s ease';
|
|
|
});
|
|
|
|
|
|
// 初始化显示第一组
|
|
|
this.updateSlides();
|
|
|
}
|
|
|
|
|
|
setupNavigation() {
|
|
|
if (!this.options.navigation) return;
|
|
|
|
|
|
const nextBtn = this.container.querySelector('.carousel-button-next');
|
|
|
const prevBtn = this.container.querySelector('.carousel-button-prev');
|
|
|
|
|
|
if (nextBtn) {
|
|
|
nextBtn.addEventListener('click', (e) => {
|
|
|
e.preventDefault();
|
|
|
this.next();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
if (prevBtn) {
|
|
|
prevBtn.addEventListener('click', (e) => {
|
|
|
e.preventDefault();
|
|
|
this.prev();
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
setupPagination() {
|
|
|
if (!this.options.pagination) return;
|
|
|
|
|
|
const pagination = this.container.querySelector('.carousel-pagination');
|
|
|
if (!pagination) return;
|
|
|
|
|
|
// 使用现有的分页器按钮,只需要绑定事件
|
|
|
const bullets = pagination.querySelectorAll('.carousel-pagination-bullet');
|
|
|
bullets.forEach((bullet, index) => {
|
|
|
bullet.addEventListener('click', () => this.goTo(index));
|
|
|
});
|
|
|
}
|
|
|
|
|
|
setupTouch() {
|
|
|
if (!this.options.touchEnabled) return;
|
|
|
|
|
|
const wrapper = this.container.querySelector('.carousel-wrapper');
|
|
|
if (!wrapper) return;
|
|
|
|
|
|
wrapper.addEventListener('touchstart', (e) => {
|
|
|
this.touchStartX = e.touches[0].clientX;
|
|
|
this.pauseAutoplay();
|
|
|
}, { passive: true });
|
|
|
|
|
|
wrapper.addEventListener('touchend', (e) => {
|
|
|
this.touchEndX = e.changedTouches[0].clientX;
|
|
|
this.handleSwipe();
|
|
|
this.resumeAutoplay();
|
|
|
}, { passive: true });
|
|
|
}
|
|
|
|
|
|
setupAutoplay() {
|
|
|
if (!this.options.autoplay) return;
|
|
|
|
|
|
this.autoplayTimer = setInterval(() => {
|
|
|
this.next();
|
|
|
}, this.options.autoplayDelay);
|
|
|
}
|
|
|
|
|
|
handleSwipe() {
|
|
|
const swipeThreshold = 50;
|
|
|
const diff = this.touchStartX - this.touchEndX;
|
|
|
|
|
|
if (Math.abs(diff) > swipeThreshold) {
|
|
|
if (diff > 0) {
|
|
|
this.next();
|
|
|
} else {
|
|
|
this.prev();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
next() {
|
|
|
if (this.isAnimating) return;
|
|
|
|
|
|
if (this.slidesPerView === 1) {
|
|
|
// 单个幻灯片模式:直接切换到下一个
|
|
|
let nextIndex = this.currentIndex + 1;
|
|
|
if (nextIndex >= this.slides.length) {
|
|
|
nextIndex = this.options.loop ? 0 : this.currentIndex;
|
|
|
}
|
|
|
if (nextIndex !== this.currentIndex) {
|
|
|
this.goTo(nextIndex);
|
|
|
}
|
|
|
} else {
|
|
|
// 多个幻灯片模式:按组切换
|
|
|
const currentGroup = Math.floor(this.currentIndex / this.slidesPerView);
|
|
|
let nextGroup = currentGroup + 1;
|
|
|
|
|
|
if (nextGroup >= this.totalGroups) {
|
|
|
nextGroup = this.options.loop ? 0 : currentGroup;
|
|
|
}
|
|
|
|
|
|
const nextIndex = nextGroup * this.slidesPerView;
|
|
|
if (nextIndex !== this.currentIndex) {
|
|
|
this.goTo(nextIndex);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
prev() {
|
|
|
if (this.isAnimating) return;
|
|
|
|
|
|
if (this.slidesPerView === 1) {
|
|
|
// 单个幻灯片模式:直接切换到上一个
|
|
|
let prevIndex = this.currentIndex - 1;
|
|
|
if (prevIndex < 0) {
|
|
|
prevIndex = this.options.loop ? this.slides.length - 1 : this.currentIndex;
|
|
|
}
|
|
|
if (prevIndex !== this.currentIndex) {
|
|
|
this.goTo(prevIndex);
|
|
|
}
|
|
|
} else {
|
|
|
// 多个幻灯片模式:按组切换
|
|
|
const currentGroup = Math.floor(this.currentIndex / this.slidesPerView);
|
|
|
let prevGroup = currentGroup - 1;
|
|
|
|
|
|
if (prevGroup < 0) {
|
|
|
prevGroup = this.options.loop ? this.totalGroups - 1 : currentGroup;
|
|
|
}
|
|
|
|
|
|
const prevIndex = prevGroup * this.slidesPerView;
|
|
|
if (prevIndex !== this.currentIndex) {
|
|
|
this.goTo(prevIndex);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
goTo(index) {
|
|
|
console.log('SimpleCarousel: goTo 被调用,目标索引:', index, '当前索引:', this.currentIndex);
|
|
|
|
|
|
if (this.isAnimating || index === this.currentIndex || index < 0 || index >= this.slides.length) {
|
|
|
console.log('SimpleCarousel: goTo 被阻止 - 动画中:', this.isAnimating, '相同索引:', index === this.currentIndex, '索引范围:', index < 0 || index >= this.slides.length);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
this.isAnimating = true;
|
|
|
const previousIndex = this.currentIndex;
|
|
|
this.currentIndex = index;
|
|
|
|
|
|
console.log('SimpleCarousel: 切换从索引', previousIndex, '到', this.currentIndex);
|
|
|
|
|
|
// 更新幻灯片
|
|
|
this.updateSlides();
|
|
|
|
|
|
// 触发回调
|
|
|
if (this.options.onSlideChange) {
|
|
|
console.log('SimpleCarousel: 触发 onSlideChange 回调');
|
|
|
this.options.onSlideChange(this.currentIndex, previousIndex);
|
|
|
}
|
|
|
|
|
|
setTimeout(() => {
|
|
|
this.isAnimating = false;
|
|
|
}, 500);
|
|
|
}
|
|
|
|
|
|
updateSlides() {
|
|
|
let translateX;
|
|
|
|
|
|
if (this.slidesPerView === 1) {
|
|
|
// 单个幻灯片模式:直接按索引移动
|
|
|
translateX = -this.currentIndex * 100;
|
|
|
} else {
|
|
|
// 多个幻灯片模式:按组移动
|
|
|
const currentGroup = Math.floor(this.currentIndex / this.slidesPerView);
|
|
|
translateX = -currentGroup * 100;
|
|
|
}
|
|
|
|
|
|
// 移动整个轮播容器
|
|
|
this.wrapper.style.transform = `translateX(${translateX}%)`;
|
|
|
|
|
|
// 更新分页器(如果存在)
|
|
|
const bullets = this.container.querySelectorAll('.carousel-pagination-bullet');
|
|
|
bullets.forEach((bullet, index) => {
|
|
|
if (this.slidesPerView === 1) {
|
|
|
// 单个幻灯片模式:直接按索引激活
|
|
|
bullet.classList.toggle('active', index === this.currentIndex);
|
|
|
} else {
|
|
|
// 多个幻灯片模式:激活当前可见范围内的幻灯片
|
|
|
const currentGroup = Math.floor(this.currentIndex / this.slidesPerView);
|
|
|
const slideGroup = Math.floor(index / this.slidesPerView);
|
|
|
bullet.classList.toggle('active', slideGroup === currentGroup);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
pauseAutoplay() {
|
|
|
if (this.autoplayTimer) {
|
|
|
clearInterval(this.autoplayTimer);
|
|
|
this.autoplayTimer = null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
resumeAutoplay() {
|
|
|
if (this.options.autoplay && !this.autoplayTimer) {
|
|
|
this.autoplayTimer = setInterval(() => {
|
|
|
this.next();
|
|
|
}, this.options.autoplayDelay);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
destroy() {
|
|
|
this.pauseAutoplay();
|
|
|
|
|
|
// 移除事件监听器
|
|
|
const nextBtn = this.container.querySelector('.carousel-button-next');
|
|
|
const prevBtn = this.container.querySelector('.carousel-button-prev');
|
|
|
const bullets = this.container.querySelectorAll('.carousel-pagination-bullet');
|
|
|
|
|
|
if (nextBtn) nextBtn.replaceWith(nextBtn.cloneNode(true));
|
|
|
if (prevBtn) prevBtn.replaceWith(prevBtn.cloneNode(true));
|
|
|
bullets.forEach(bullet => bullet.replaceWith(bullet.cloneNode(true)));
|
|
|
}
|
|
|
|
|
|
// 获取当前索引
|
|
|
get activeIndex() {
|
|
|
return this.currentIndex;
|
|
|
}
|
|
|
|
|
|
// 获取幻灯片总数
|
|
|
get slidesLength() {
|
|
|
return this.slides.length;
|
|
|
}
|
|
|
|
|
|
// 销毁实例
|
|
|
destroy() {
|
|
|
// 停止自动播放
|
|
|
this.pauseAutoplay();
|
|
|
|
|
|
// 清除事件监听器
|
|
|
this.removeEventListeners();
|
|
|
|
|
|
// 重置样式
|
|
|
if (this.wrapper) {
|
|
|
this.wrapper.style.transform = '';
|
|
|
}
|
|
|
|
|
|
// 清除引用
|
|
|
this.container = null;
|
|
|
this.wrapper = null;
|
|
|
this.slides = [];
|
|
|
this.autoplayTimer = null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 导出到全局
|
|
|
window.SimpleCarousel = SimpleCarousel;
|
|
|
|
|
|
})(); |