布局

image-20210113105953068

<div id="app">
  <div id="swiper-container">
    <!-- 幻灯片的结构 -->
    <div class="swiper-wrapper">
      <div class="swiper-slide"><img src="./img/1.jpg"></div>
      <div class="swiper-slide"><img src="./img/2.jpg"></div>
      <div class="swiper-slide"><img src="./img/3.jpg"></div>
      <div class="swiper-slide"><img src="./img/4.jpg"></div>
      <div class="swiper-slide"><img src="./img/5.jpg"></div>
    </div>
    <!-- 导航点 -->
    <div class="swiper-pagination">
      <span class="active"></span>
      <span></span>
      <span></span>
      <span></span>
      <span></span>
    </div>
  </div>
</div>
* {
  margin: 0;
  padding: 0;
}

ul {
  list-style: none;
}
html,
body,
#app {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
#swiper-container{
  width: 100%;
  height: 176px; /* no */
  position: relative;

  .swiper-wrapper{
    width: 500%;
    overflow: hidden;

    .swiper-slide{
      width: 20%;
      float: left;
      img{
        width: 100%;
        display: block;
      }
    }
  }
  //  导航点
  .swiper-pagination{
    position: absolute;
    width: 100%;
    height: 10px; /* no */
    left:0;
    bottom: 10px; /* no */
    line-height: 10px;
    text-align: center;
    span{
      display: inline-block;
      width: 10px; /* no */
      height: 10px; /* no */
      background: #fff;
      border-radius: 50%;
      margin-right: 4px;
    }
    .active{
      background: darkorange;
    }
  }
}

可拖拽

b754c5cd-ce9c-4868-a5dd-c87a03ae434a

为包裹图片的容器开启定位

.swiper-wrapper {
  position: absolute;
}

接下来使用js动态改变这个容器的位置即可。

const app = document.querySelector('#app')

const container = document.querySelector('#swiper-container')
const wrapper = container.querySelector('.swiper-wrapper')

app.addEventListener('touchstart', function (e) {
  e.preventDefault()
})

// 触摸开始事件
container.addEventListener('touchstart', function (e) {
  // 获取触摸开始时的触点位置
  this.x = e.touches[0].clientX

  // 获取包裹元素的水平偏移量
  this.left = wrapper.offsetLeft
})

// 触摸滑动时事件
container.addEventListener('touchmove', function (e) {
  this._x = e.touches[0].clientX
  // 计算新的left值
  const newLeft = this._x - this.x + this.left
  // 设置left的值
  wrapper.style.left = newLeft + 'px'
})

拖拽切换

db41cda1-3c5b-4580-bdd4-7a78af88e011

向左划动切换右边的一张图片,向右滑动切换左边的一张图片。

// 获取图片的数量
const len = container.querySelectorAll('.swiper-slide')
// 当前显示的图片的下标
let index = 0
// 获取所有小圆点
const dots = container.querySelectorAll('.swiper-pagination span')


// 触摸结束事件
container.addEventListener('touchend', function (e) {
  // 获取触点结束时触点位置
  this._x = e.changedTouches[0].clientX
  // 向左滑动
  if (this._x < this.x) {
    index++
  }
  // 向右滑动
  if (this._x > this.x) {
    index--
  }
  // 检测是否越界
  if (index < 0) {
    index = 0
  }
  if (index > len.length - 1) {
    index = len.length - 1
  }

  /**
   * index => left
   *   0   =>  0
   *   1   => index * -375
   */
  // 计算新的left的值
  const newLeft = -index * container.offsetWidth
  // 设置left的样式
  wrapper.style.left = newLeft + 'px'

  // 点的切换
  // 移除所有导航点的active类
  dots.forEach(function (dot) {
    dot.classList.remove('active')
  })
  // 当前索引的导航点 增加 active 类
  dots[index].classList.add('active')
})

优化-过渡效果

当按下时移除过度样式,抬起时添加样式即可。

container.addEventListener('touchstart', function (e) {
  // 移除过度效果
  wrapper.style.transition = 'none'
})
container.addEventListener('touchend', function (e) {
  // 增加过度
  wrapper.style.transition = 'all .5s'
}

元素初始化设置

为元素设置初始高度宽度以及动态创建导航点的个数

// 初始化函数
function init() {
  // 容器设置 高度
  window.onload = function () {
    // 获取第一个图片的高度
    const h = slides[0].offsetHeight
    container.style.height = h + 'px'
  }
  // 包裹元素的宽度
  wrapper.style.width = slides.length * 100 + '%'
  // 设置子元素的宽度
  slides.forEach((item) => {
    item.style.width = 100 / slides.length + '%'
  })
  // 根据图片数量动态创建导航点的个数
  for (var i = 0; i < slides.length; i++) {
    const span = document.createElement('span')
    if (i == 0) {
      span.className = 'active'
    }
    pagination.appendChild(span)
  }
  dots = container.querySelectorAll('.swiper-pagination span')
}
init()

无缝滚动

无缝滚动采用的方案是新插入一组一模一样的图片,当向左滑动(第一张)时迅速切换到第二组的第一张。

// 无缝滚动
wrapper.innerHTML += wrapper.innerHTML
const slidesNew = container.querySelectorAll('.swiper-slide')

container.addEventListener('touchstart', function (e) {
  // 移除过度效果
  wrapper.style.transition = 'none'
  // 获取按下时的时间点
  this.touchStartTime = Date.now()
  // 判断索引下标
  if (index == 0) {
    index = slides.length
    const newLeft = -index * container.offsetWidth
    // 设置left的样式
    wrapper.style.left = newLeft + 'px'
  }
  // 右侧边界
  if (index == slidesNew.length - 1) {
    index = slides.length - 1
    const newLeft = -index * container.offsetWidth
    // 设置left的样式
    wrapper.style.left = newLeft + 'px'
  }
  // 获取触摸开始时的触点位置
  this.x = e.touches[0].clientX

  // 获取包裹元素的水平偏移量
  this.left = wrapper.offsetLeft
})

自动播放

// 自动播放
function autoRun() {
  // 防止定时器重复
  clearInterval(timer)
  // 启动定时器
  timer = setInterval(function () {
    // 索引自增 切换幻灯片
    index++
    // 增加过度
    wrapper.style.transition = 'all .5s'
    const newLeft = -index * container.offsetWidth
    // 设置left的样式
    wrapper.style.left = newLeft + 'px'

    // 点的切换
    // 移除所有导航点的active类
    dots.forEach(function (dot) {
      dot.classList.remove('active')
    })
    // 当前索引的导航点 增加 active 类
    dots[index % slides.length].classList.add('active')
  }, 1000)
}
autoRun()

// 动画过渡完毕时间
wrapper.addEventListener('transitionend', (e) => {
  // 是不是最后一张
  if (index === slidesNew.length - 1) {
    // 移除过渡
    wrapper.style.transition = 'none'
    index = slides.length - 1
    const newLeft = -index * container.offsetWidth
    // 设置left的样式
    wrapper.style.left = newLeft + 'px'
  }
})

封装及优化后的JavaScript代码

import '../style/index.less'

const app = document.querySelector('#app')

const container = document.querySelector('#swiper-container')
const wrapper = container.querySelector('.swiper-wrapper')
// 获取所有图片
const slides = container.querySelectorAll('.swiper-slide')
// 当前显示的图片的下标
var index = 0
// 获取所有小圆点
let dots = null
// 获取小圆点的容器
const pagination = container.querySelector('.swiper-pagination')
// 定时器
let timer = null
// 无缝滚动
wrapper.innerHTML += wrapper.innerHTML
const slidesNew = container.querySelectorAll('.swiper-slide')

// 标识变量
let isHori = true
let isFirst = true

// 触摸开始事件
container.addEventListener('touchstart', function (e) {
  wrapper.style.transition = 'none'
  // 获取按下时的时间点
  this.touchStartTime = Date.now()
  // 判断索引下标
  if (index == 0) {
    index = slides.length
  }
  // 右侧边界
  if (index == slidesNew.length - 1) {
    index = slides.length - 1
  }
  container.switchSlide(index, false)
  // 获取触摸开始时的触点位置
  this.x = e.touches[0].clientX
  this.y = e.touches[0].clientY

  // 获取包裹元素的水平偏移量
  this.left = wrapper.offsetLeft
  // 停止定时器
  clearInterval(timer)
})

// 触摸滑动时事件
container.addEventListener('touchmove', function (e) {
  this._x = e.touches[0].clientX
  this._y = e.touches[0].clientY

  // 垂直方向上的偏移
  const disY = Math.abs(this._y - this.y)
  // 水平方向上的偏移
  const disX = Math.abs(this._x - this.x)
  if (isFirst) {
    // 标识变量赋值为 假
    isFirst = false
    if (disY > disX) {
      isHori = false
    }
    if (disY < disX) {
      isHori = true
    }
  }
  if (isHori) {
    e.preventDefault()
  } else {
    return
  }
  // 计算新的left值
  const newLeft = this._x - this.x + this.left
  // 设置left的值
  wrapper.style.left = newLeft + 'px'
})

// 触摸结束事件
container.addEventListener('touchend', function (e) {
  isFirst = true

  if (!isHori) return
  // 获取触点结束时触点位置
  this._x = e.changedTouches[0].clientX
  // 判断距离
  const disX = Math.abs(this._x - this.x)
  // 判断时间
  this.touchEndTime = Date.now()

  if (
    disX > container.offsetWidth / 2 ||
    this.touchEndTime - this.touchStartTime <= 300
  ) {
    // 向左滑动
    if (this._x < this.x) {
      index++
    }
    // 向右滑动
    if (this._x > this.x) {
      index--
    }
  }

  // 检测是否越界
  if (index < 0) {
    index = 0
  }
  if (index > slidesNew.length - 1) {
    index = slidesNew.length - 1
  }
  this.switchSlide(index)
  // 启动定时器
  this.autoRun()
})
// 动画过渡完毕时间
wrapper.addEventListener('transitionend', (e) => {
  // 是不是最后一张
  if (index === slidesNew.length - 1) {
    index = slides.length - 1
    container.switchSlide(index, false)
  }
})

// 初始化函数
container.init = function () {
  // 容器设置 高度
  window.onload = function () {
    // 获取第一个图片的高度
    const h = slidesNew[0].offsetHeight
    container.style.height = h + 'px'
  }
  // 包裹元素的宽度
  wrapper.style.width = slidesNew.length * 100 + '%'
  // 设置子元素的宽度
  slidesNew.forEach((item) => {
    item.style.width = 100 / slidesNew.length + '%'
  })
  // 根据图片数量动态创建导航点的个数
  for (var i = 0; i < slides.length; i++) {
    const span = document.createElement('span')
    if (i == 0) {
      span.className = 'active'
    }
    pagination.appendChild(span)
  }
  dots = container.querySelectorAll('.swiper-pagination span')
}
container.init()
// 自动播放
container.autoRun = function () {
  // 防止定时器重复
  clearInterval(timer)
  // 启动定时器
  timer = setInterval(function () {
    // 索引自增 切换幻灯片
    index++
    container.switchSlide(index)
  }, 1000)
}
container.autoRun()

// 切换幻灯片
container.switchSlide = function (i, isTransition) {
  if (isTransition === undefined) {
    isTransition = true
  }
  if (isTransition) {
    // 增加过度
    wrapper.style.transition = 'all .5s'
  } else {
    // 增加过度
    wrapper.style.transition = 'none'
  }
  /**
   * index => left
   *   0   =>  0
   *   1   => index * -375
   */
  // 计算新的left的值
  const newLeft = -i * container.offsetWidth
  // 设置left的样式
  wrapper.style.left = newLeft + 'px'

  // 点的切换
  // 移除所有导航点的active类
  dots.forEach(function (dot) {
    dot.classList.remove('active')
  })
  // 当前索引的导航点 增加 active 类
  dots[i % slides.length].classList.add('active')
  index = i
}