基于Web Component集成Swiper.js出现重复项问题求助
自定义Web Component轮播项重复问题解决
问题描述
开发集成Swiper.js的自定义轮播Web Component,通过组件的sliderBgImage和sliderCopyImage属性动态生成轮播项,但出现轮播项重复的问题。

相关代码
index.html
<div style="width:320px;height:480px;"> <vertical sliderBgImage="banner1.png,banner2.png" sliderCopyImage="copy1.png,copy2.png" sliderlanding1="http://www.google.com" sliderlanding2="http://www.test.com" sliderlanding3="http://www.test.com" sliderlanding4="" sliderlanding5="" autoplay="true" speed="2000" pagingColor="3eff8a"></vertical> </div> <script src="vertical.js" type="module"></script> <script src="https://s0.2mdn.net/creatives/assets/4671853/swiper.min.js"></script> </body>
Vertical.js(修正前)
template.innerHTML = ` <link rel="stylesheet" href="./swiper.min.css"/><link rel="stylesheet" href="./vertical.css"/><div id="mainContainer"><div class="swiper-container"><div class="swiper-wrapper"></div><div class="swiper-pagination"></div></div></div>`; class Vertical extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.shadowRoot.appendChild(template.content.cloneNode(true)); } handleSliderClick = () => { let slides = this.shadowRoot.querySelectorAll(".swiper-slide"); for (let i = 0; i < slides.length; i++) { const slide = slides[i]; slide.addEventListener("click", () => { const getslide = slide.getAttribute("data"); const landing = getslide.slice(-1); Enabler.exitOverride(getslide, this.getAttribute(landing)); }); } }; setPaginateColor = () => { const activeBullet = this.shadowRoot.querySelector( ".swiper-pagination-bullet-active" ); if (activeBullet) { activeBullet.style.backgroundColor = `#${this.getAttribute( "pagingColor" )}`; } }; darkNav = () => { var slides = this.shadowRoot.querySelectorAll(".swiper-slide"); var copies = this.shadowRoot.querySelectorAll(".copies"); for (let i = 0, c = 0; i < slides.length, c < copies.length; i++, c++) { const slide = slides[i]; const copy = copies[c]; if ( slide.classList.contains("swiper-slide-active") || slide.classList.contains("swiper-slide-duplicate-active") ) { slide.classList.add("bubbleslide"); this.setPaginateColor(); setTimeout(() => { copy.classList.add("slide-top"); }, 200); } else { slide.classList.remove("bubbleslide"); copy.classList.remove("slide-top"); } } }; setSwiper = (ele) => { const setAutoplay = this.getAttribute("autoplay"); const setSpeed = this.getAttribute("speed"); var swiper = new Swiper(ele, { autoplay: { delay: setSpeed, }, direction: "vertical", pagination: { el: this.shadowRoot.querySelector(".swiper-pagination"), clickable: true, }, loop: true, on: { slideChangeTransitionStart: this.darkNav, }, }); this.setPaginateColor(); if (setAutoplay === "true" || setAutoplay === "TRUE") { swiper.autoplay.start(); } else { swiper.autoplay.stop(); } this.handleSliderClick(); setTimeout(() => { swiper.autoplay.stop(); }, 30000); }; static get observedAttributes() { return [ "brandLogo", "sliderBgImage", "sliderCopyImage", "pagingColor", "autoplay", "speed", ]; } attributeChangedCallback(name, oldValue, newValue) { var bgImgs = this.getAttribute("sliderBgImage").split(","); var copyImgs = this.getAttribute("sliderCopyImage").split(","); var items = []; // clean up each item for (let i = 0; i < bgImgs.length; i++) { let val1 = bgImgs[i]; let val2 = copyImgs[i]; let obj = { bg: val1, tag: val2, }; items.push(obj); } } connectedCallback() { var bgImgs = this.getAttribute("sliderBgImage").split(","); var copyImgs = this.getAttribute("sliderCopyImage").split(","); var items = []; // clean up each item for (let i = 0; i < bgImgs.length; i++) { let val1 = bgImgs[i]; let val2 = copyImgs[i]; let obj = { bg: val1, tag: val2, }; items.push(obj); } let checkItem = null; const swiperContainer = this.shadowRoot.querySelector(".swiper-wrapper"); const swiperMain = this.shadowRoot.querySelector(".swiper-container"); items.map((v, k) => { let html = `<div class="swiper-slide slide${k + 1}" data="slide${ k + 1 }" style="background-image:url('${v.bg}')"><div class="copies copy${ k + 1 }" style="background-image:url('${v.tag}')"></div></div>`; checkItem++; return (swiperContainer.innerHTML += html); }); let check = setInterval(() => { if (checkItem === items.length) { this.setSwiper(swiperMain); clearInterval(check); } }, 500); } } export default Vertical;
问题原因与修正方案
核心问题分析
- 轮播项重复添加:
connectedCallback中生成轮播项时未清空swiper-wrapper容器,若组件多次触发连接逻辑(如DOM树移动),会重复追加轮播项。 - 无效计数逻辑:
checkItem初始值为null,执行checkItem++后变为NaN,导致setInterval停止条件永远不满足,可能重复初始化Swiper。 - 重复初始化Swiper:属性变化或组件重连时,未销毁原有Swiper实例就重新初始化,导致轮播逻辑混乱。
- 代码冗余与越界风险:
connectedCallback和attributeChangedCallback重复编写生成轮播项的逻辑,且未处理两个属性数组长度不一致的情况。
修正后代码
template.innerHTML = ` <link rel="stylesheet" href="./swiper.min.css"/><link rel="stylesheet" href="./vertical.css"/><div id="mainContainer"><div class="swiper-container"><div class="swiper-wrapper"></div><div class="swiper-pagination"></div></div></div>`; class Vertical extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.shadowRoot.appendChild(template.content.cloneNode(true)); this.swiperInstance = null; // 保存Swiper实例,方便销毁/重初始化 } handleSliderClick = () => { let slides = this.shadowRoot.querySelectorAll(".swiper-slide"); slides.forEach(slide => { slide.addEventListener("click", () => { const getslide = slide.getAttribute("data"); const landing = getslide.slice(-1); Enabler.exitOverride(getslide, this.getAttribute(`sliderlanding${landing}`)); }); }); }; setPaginateColor = () => { const activeBullet = this.shadowRoot.querySelector(".swiper-pagination-bullet-active"); if (activeBullet) { activeBullet.style.backgroundColor = `#${this.getAttribute("pagingColor")}`; } }; darkNav = () => { const slides = this.shadowRoot.querySelectorAll(".swiper-slide"); const copies = this.shadowRoot.querySelectorAll(".copies"); slides.forEach((slide, index) => { const copy = copies[index]; if (!copy) return; if (slide.classList.contains("swiper-slide-active") || slide.classList.contains("swiper-slide-duplicate-active")) { slide.classList.add("bubbleslide"); this.setPaginateColor(); setTimeout(() => copy.classList.add("slide-top"), 200); } else { slide.classList.remove("bubbleslide"); copy.classList.remove("slide-top"); } }); }; setSwiper = (ele) => { // 销毁旧实例,避免重复初始化 if (this.swiperInstance) { this.swiperInstance.destroy(); } const setAutoplay = this.getAttribute("autoplay"); const setSpeed = parseInt(this.getAttribute("speed"), 10) || 2000; this.swiperInstance = new Swiper(ele, { autoplay: { delay: setSpeed }, direction: "vertical", pagination: { el: this.shadowRoot.querySelector(".swiper-pagination"), clickable: true, }, loop: true, on: { slideChangeTransitionStart: this.darkNav }, }); this.setPaginateColor(); setAutoplay.toLowerCase() === "true" ? this.swiperInstance.autoplay.start() : this.swiperInstance.autoplay.stop(); this.handleSliderClick(); setTimeout(() => this.swiperInstance.autoplay.stop(), 30000); }; // 统一渲染轮播项的方法 renderSlides = () => { const bgImgs = this.getAttribute("sliderBgImage")?.split(",") || []; const copyImgs = this.getAttribute("sliderCopyImage")?.split(",") || []; const items = []; // 处理数组长度不一致的情况,避免索引越界 const maxLength = Math.max(bgImgs.length, copyImgs.length); for (let i = 0; i < maxLength; i++) { items.push({ bg: bgImgs[i] || "", tag: copyImgs[i] || "" }); } const swiperContainer = this.shadowRoot.querySelector(".swiper-wrapper"); swiperContainer.innerHTML = ""; // 先清空容器,避免重复添加 items.forEach((v, k) => { const html = `<div class="swiper-slide slide${k + 1}" data="slide${k + 1}" style="background-image:url('${v.bg}')"><div class="copies copy${k + 1}" style="background-image:url('${v.tag}')"></div></div>`; swiperContainer.innerHTML += html; }); this.setSwiper(this.shadowRoot.querySelector(".swiper-container")); }; static get observedAttributes() { return ["brandLogo", "sliderBgImage", "sliderCopyImage", "pagingColor", "autoplay", "speed"]; } attributeChangedCallback(name, oldValue, newValue) { if (oldValue === newValue) return; // 属性值未变化时不处理 this.renderSlides(); } connectedCallback() { this.renderSlides(); } } export default Vertical;
内容的提问来源于stack exchange,提问作者carol chaw
相关产品推荐
相关产品推荐

