You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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;

问题原因与修正方案

核心问题分析

  1. 轮播项重复添加:connectedCallback中生成轮播项时未清空swiper-wrapper容器,若组件多次触发连接逻辑(如DOM树移动),会重复追加轮播项。
  2. 无效计数逻辑:checkItem初始值为null,执行checkItem++后变为NaN,导致setInterval停止条件永远不满足,可能重复初始化Swiper。
  3. 重复初始化Swiper:属性变化或组件重连时,未销毁原有Swiper实例就重新初始化,导致轮播逻辑混乱。
  4. 代码冗余与越界风险: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 04:45:33