柚子青年。

【手撕代码】轻量轮播图
柚子青年。
2022-09-27

轮播图的几个关键点:翻页按钮,分页器,悬停静止,无限轮播,清理动画叠加。

文中使用了构造函数的方式把所有细节解构去耦合,欢迎大家点评

html

<div class="swiper swiper1">
    <div class="wrapper">
        <div class="slider">1</div>
        <div class="slider">2</div>
        <div class="slider">3</div>
        <div class="slider">4</div>
        <div class="slider">5</div>
    </div>
    <div class="btn">
        <span class="prev"><</span>
        <span class="next">></span>
    </div>
</div>

css

.swiper { position: relative; width: 700px; height: 400px; background: #efefef; overflow: hidden; margin-bottom: 10px; }

.wrapper { position: relative; left: 0; display: flex; width: 9999999%; }

.slider { display: flex; align-items: center; justify-content: center; }

.btn { position: absolute; top: 50%; left: 0; width: 100%; z-index: 1; }

.btn span { position: absolute; top: 0; font-size: 25px; color: #666; height: 70px; width: 50px; line-height: 70px; text-align: center; margin-top: -35px; cursor: pointer; background: rgba(102, 102, 102, .2); transition: all .3s ease; }

.btn span:hover { background: rgba(102, 102, 102, .6); }

.btn span.next { right: 0; }

.pagination { position: absolute; left: 0; bottom: 10px; display: flex; justify-content: center; width: 100%; }

.pagination span { width: 8px; height: 8px; margin: 0 5px; border-radius: 50%; background: #fff; cursor: pointer;}

.pagination span.active { background: #00f; }

js

定义一个构造函数 Swiper

const Swiper = function ({el, pagination, delay}) {
    this._el = el;  // 最外层容器 swiper1
    this._prev = el.querySelector(".prev"); // 上一页按钮
    this._next = el.querySelector(".next"); // 下一页按钮
    this._slider = el.querySelectorAll(".slider"); // 获取 swiper1 下的所有 slider
    this._wrapper = el.querySelector(".wrapper"); // 获取 swiper1 下的 wrapper
    this._width = el.offsetWidth; // 获取 swiper1 宽度
    this._height = el.offsetHeight; // 获取 swiper1 高度
    this._size = this._slider.length;
    this._index = 0;  // 记录当前展示slider的下标
    this._timer = null; // 轮播定时器
    this._animate = null; // 动画定时器
    this._isHover = false; // 是否悬浮 swiper1 上
    this._pagination = pagination || false; // 是否启用分页器
    this._delay = delay || 3000; // 轮播间隔时长
    this.init();
};

初始化 this.init

Swiper.prototype.init = function () {
  this.style();
  this.auto();
  this.mouseEnter();
};

设置基础样式 复制头尾实现无限轮播 this.style

Swiper.prototype.style = function () {
  this._slider.forEach(item => {
    item.style.width = this._width + "px";
    item.style.height = this._height + "px";
  });

  const firstDom = this._slider[0].cloneNode(true);
  const lastDom = this._slider[this._size - 1].cloneNode(true);

  this._wrapper.appendChild(firstDom);
  this._wrapper.insertBefore(lastDom, this._el.querySelector(".slider"));
  this._wrapper.style.left = -this._width + "px"; // 归位到第一张

  if (this._pagination) {
    const pagination = document.createElement("div");
    pagination.className = "pagination";

    this._slider.forEach((item, i) => {
      const span = document.createElement("span");
      if (i === this._index) span.className = "active";

      span.onclick = function () {
        this.index(i);
      }.bind(this)
      pagination.appendChild(span);
    });

    this._el.appendChild(pagination);
  }
}

设置当前active 调整分页选中 this.active

Swiper.prototype.active = function () {
  this._el.querySelector(`.pagination span.active`).className = "";

  const index = this._index < 0 ? this._size : this._index >= this._size ? 1 : this._index + 1;

  this._el.querySelector(`.pagination span:nth-child(${index})`).className = "active";
};

自动轮播 this.auto 

Swiper.prototype.auto = function () {
  if (this._timer) clearTimeout(this._timer);

  this._timer = setTimeout(() => {
    if (this._isHover) {
      clearTimeout(this._timer);
      this.auto();
      return;
    }
    this.next();
  }, this._delay)
};

轮播图移动核心处理 this.move

Swiper.prototype.move = function () {
  if (this._index > this._size) {
    this._index = 1;
    this.styleLeft(-1 * this._width);
  }

  if (this._index < -1) {
    this._index = this._size - 2;
    this.styleLeft(-(this._size) * this._width);
  }

  this.start((-this._index - 1) * this._width); 
  this.active();
  this.auto();
};

轮播动画 this.animate

Swiper.prototype.animate = function (start, end) {
  const step = (start - end > 0 ? -1 : 1) * (Math.abs(start - end) / 10); // 设置一个滚动基数

  if (this._animate) clearInterval(this._animate);

  this._animate = setInterval(() => {
    start += step;

    if (step < 0 && end - start > step || step > 0 && end - start < step) {
      clearInterval(this._animate);
      this.styleLeft(end);
    } else {
      this.styleLeft(start);
    }
  }, 40)
};

计算动画开始时 left偏移量 this.start

Swiper.prototype.start = function (end) {
  const start = parseFloat(this._wrapper.style.left);

  this.animate(start, end);
};

设置 Swiper left偏移量 this.styleLeft

Swiper.prototype.styleLeft = function (left) {
  this._wrapper.style.left = left + "px";
};

上一页 this.prev

Swiper.prototype.prev = function () {
  this._index--;
  this.move();
}

下一页 this.next

Swiper.prototype.next = function () {
  this._index++;
  this.move();
};

分页器切换 this.index

Swiper.prototype.index = function (index) {
  this._index = index;
  this.move();
};

添加鼠标事件 this.mouseEnter

Swiper.prototype.mouseEnter = function () {
  this._el.onmouseenter = () => {
    this._isHover = true;
  };

  this._el.onmouseleave = () => {
    this._isHover = false;
  };

  if (this._prev) {
    this._prev.onclick = () => {
      this.prev();
    }
  }

  if (this._next) {
    this._next.onclick = () => {
      this.next();
    }
  }
};

调用

 new Swiper({
  el: document.querySelector(".swiper1"),
  pagination: true,
  delay: 2000
});