# HTML 结构

  • HTML 结构搭建
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./index.css">
</head>
<body>
  <div class="swiper">
    <div class="swiper-items">
      <div class="swiper-item swiper-item1">
        <img src="./images/808.jpg" alt="">
      </div>
      <div class="swiper-item swiper-item2">
        <img src="./images/821.jpg" alt="">
      </div>
      <div class="swiper-item swiper-item3">
        <img src="./images/822.png" alt="">
      </div>
      <div class="swiper-item swiper-item4">
        <img src="./images/823.png" alt="">
      </div>
    </div>
    <div class="indicator-dots"></div>
    <div class="btn-left">Left</div>
    <div class="btn-right">Right</div>'
  </div>
  <script src="./animation.js"></script>
  <script src="./index.js"></script>
</body>
</html>

# Stylus 样式

  • Stylus : stylus -w index.styl -o index.css
* 
  margin 0
  padding 0
  
.active
  background-color skyblue !important
.swiper 
  position relative
  margin 120px auto
  width 800px
  height 500px
  overflow hidden 
  background-color pink
  box-sizing border-box
  .swiper-items
    position relative
    // width 3000px
    height 500px
    background-size cover
    img 
      width 100%
      height 100%
   .swiper-item
      float left
      width 800px
      height 500px
   .swiper-item1
     background-color red
  
   .swiper-item2
     background-color purple
  
   .swiper-item3
     background-color pink
  
   .swiper-item4
     background-color yellow
.indicator-dots 
  display flex
  justify-content center
  align-items  center
  position absolute
  bottom 20px
  left 50%
  transform translateX(-50%)
  width 100%
  height 30px
  &>div
    cursor pointer
    margin 0 5px
    width 10px
    height 10px
    border-radius 50%
    border 1px solid #ccc
    background-color #fff
    &hover
      background-color skyblue
.btn-left
.btn-right
  cursor pointer
  color #fff
  position absolute
  top 50%
  transform translateY(-50%)
  width 50px
  height 40px
  text-align center
  line-height 40px
  background-color rgba(255, 255, 255, 0.486)
  transition all .3s
  &hover
    background-color #ccc
.btn-left
  left 0
  border-top-right-radius 10%
  border-bottom-right-radius 10%
.btn-right
  right 0
  border-top-left-radius 10%
  border-bottom-left-radius 10%

# CSS 样式

  • 编译后的 CSS 样式
* {
  margin: 0;
  padding: 0;
}
.active {
  background-color: #87ceeb !important;
}
.swiper {
  position: relative;
  margin: 120px auto;
  width: 800px;
  height: 500px;
  overflow: hidden;
  background-color: #ffc0cb;
  box-sizing: border-box;
}
.swiper .swiper-items {
  position: relative;
  height: 500px;
  background-size: cover;
}
.swiper .swiper-items img {
  width: 100%;
  height: 100%;
}
.swiper .swiper-item {
  float: left;
  width: 800px;
  height: 500px;
}
.swiper .swiper-item1 {
  background-color: #f00;
}
.swiper .swiper-item2 {
  background-color: #800080;
}
.swiper .swiper-item3 {
  background-color: #ffc0cb;
}
.swiper .swiper-item4 {
  background-color: #ff0;
}
.indicator-dots {
  display: flex;
  justify-content: center;
  align-items: center;
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  height: 30px;
}
.indicator-dots>div {
  cursor: pointer;
  margin: 0 5px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 1px solid #ccc;
  background-color: #fff;
}
.indicator-dots>divhover {
  background-color: #87ceeb;
}
.btn-left,
.btn-right {
  cursor: pointer;
  color: #fff;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 50px;
  height: 40px;
  text-align: center;
  line-height: 40px;
  background-color: rgba(255,255,255,0.486);
  transition: all 0.3s;
}
.btn-left:hover,
.btn-right:hover {
  background-color: #ccc;
}
.btn-left {
  left: 0;
  border-top-right-radius: 10%;
  border-bottom-right-radius: 10%;
}
.btn-right {
  right: 0;
  border-top-left-radius: 10%;
  border-bottom-left-radius: 10%;
}

# animate 动画

  • 封装动画效果,支持左右上下滚动
function animate(obj, target, callback, offsetDirection = 'offsetLeft', delay = 10) {
  if (obj.timer) clearInterval(obj.timer)
  let timer = null
  // 实现 timing-function 效果
  function calcStep(target, x = 10) {
    let step = (target - obj[offsetDirection]) / x
    step = step > 0 ? Math.ceil(step) : Math.floor(step)
    return step
  }
  // 判断哪种滚动方式
  const offset = () => offsetDirection === 'offsetTop'
  
  //setInterval 执行的回调函数
  const handler = () => {
    // 如果到达目的地清除定时器且判断后是否执行 callback 函数 这里用于节流
    if (obj[offsetDirection] == target) { 
      clearInterval(obj.timer)
      callback && callback()
    } else {
      if (offset()) obj.style.top = obj[offsetDirection] + calcStep(target) + 'px'
      else obj.style.left = obj[offsetDirection] + calcStep(target) + 'px'
    }
  }
  
  obj.timer = setInterval(handler, delay);
}

# index.js

  • 操作元素实例
window.addEventListener('load', loadCallback)
function loadCallback ()  {
  // 将选择器以对象形式存储
  const options = {
    swiper: '.swiper',
    swiperItem: '.swiper-item',
    swiperItems: '.swiper-items',
    indicatorDot: '.indicator-dot',
    indicatorDots: '.indicator-dots',
    btnLeft: '.btn-left',
    btnRight: '.btn-right'
  }
  class OperatingElements {
    constructor(options) {
      const { swiper, swiperItems, indicatorDots, btnLeft, btnRight } = options
      this.flag = true // 控制节流阀开关
      this.options = options // 所有选择器
      this.swiper = this.getElement(swiper)  // 获取 class -> swiper 盒子 
      this.swiperItem = this.getElement(swiperItems) // 获取 class -> swiper-swiperItems 盒子
      this.indicatorDot = this.getElement(indicatorDots) // 获取指示器
      this.btnLeft = this.getElement(btnLeft) // 获取做左按钮
      this.btnRight = this.getElement(btnRight) // 获取做右按钮
      // 这里需要先等 this.swiperItems 全部获取图片父盒子元素到再执行获取第一张图片盒子宽度
      this.init(() => this.swiperItemWidth = this.getElWidth(this.swiperItems)) 
      this.appendChild(this.swiperItem, this.swiperItemFirst) // 追加克隆的图片到最后面
      this.getSwiperItems() // 再次获取所有图片父盒子宽度
      this.indicatorDots[0].className = 'indicator-dot active' // 默认第一个小圆圈加样式
    }
    // 再次初始化
    init(callback) {
      const { swiperItem, swiperItems, indicatorDot, indicatorDots } = this.options
      this.swiperItems = this.getElement(swiperItem, true) // 获取所有图片父盒子
      this.swiperItemFirst = this.cloneEl(this.swiperItems[0]) // 克隆第一张
      callback && callback() // 回调
      this.indicatorDots = this.getElement(indicatorDot, true) // 获取所有指示器
      this.createIndicatorDots(this.swiperItems) // 动态创建所有指示器
    }
    // 获取所有 calss 是 swiper-item 的元素 
    getSwiperItems() {
      this.swiperItems = this.getElement(this.options.swiperItem, true)
    }
    // 传入选择器获取元素
    getElement(selector, isAll = false) {
      if (isAll) return document.querySelectorAll(selector)
      return document.querySelector(selector)
    }
    // 获取元素宽度
    getElWidth(target) {
      if (Object.prototype.toString.call(this.swiperItems) === '[object NodeList]') {
        return target[0].offsetWidth
      }
      return target.offsetWidth
    }
    // 克隆元素
    cloneEl(targetEl, deep = true) {
      return targetEl?.cloneNode(deep)
    }
    // 插入元素 并再次给计算 class 是 swiper-item 盒子的宽度
    appendChild(parentEl, cihldEl, isExec) {
      parentEl && parentEl.appendChild(cihldEl)
      if (isExec) return
      this.setSwiperItemsWidth()
    }
    // 创建元素
    createEl(type) {
      return document.createElement(type)
    }
    // 设置属性
    setAttribute(target, key, value) {
      target.setAttribute(key, value)
    }
    // 设置 class 是 swiper-item 的盒子元素宽度
    setSwiperItemsWidth(swiperItems, targetEl) {
      targetEl ||= this.getElement(this.options.swiperItems)
      swiperItems ||= this.getElement(this.options.swiperItem, true)
      const width = swiperItems.length * this.getElWidth(swiperItems)
      targetEl.style.width = width + 'px'
    }
    // 动态追加小圆圈
    createIndicatorDots(swiperItems) {
      for (let i = 0; i < swiperItems.length; i++) {
        const indicatorDot = this.createEl('div')
        this.setAttribute(indicatorDot, 'class', 'indicator-dot')
        this.appendChild(this.indicatorDot, indicatorDot, true)
      }
      this.indicatorDots = this.getElement(this.options.indicatorDot, true)
    }
    // 清除 class 为 indicator-dot 的所有样式
    resetClassName() {
      for (let i = 0; i < this.indicatorDots.length; i++) {
        this.indicatorDots[i].className = 'indicator-dot'
      }
    }
    // 点击的小圆圈追加样式
    circleChange() {
      this.resetClassName()
      this.indicatorDots[circle].className = 'indicator-dot active'
    }
  }
  // 实例
  operatingElements = new OperatingElements(options)
  let index = 0
  // 所有小圆点事件
  for (let i = 0; i < operatingElements.indicatorDots.length; i++) {
    operatingElements.indicatorDots[i].addEventListener('click', function () {
      operatingElements.resetClassName()
      operatingElements.indicatorDots[i].className = 'indicator-dot active'
      index = i
      animate(operatingElements.swiperItem, -operatingElements.swiperItemWidth * i)
    })
  }
  let circle = 0
  // 左按钮
  function btnLeftClick() {
    if (operatingElements.flag) {
      operatingElements.flag = false
      if (index == 0) {
        index = operatingElements.indicatorDots.length
        operatingElements.swiperItem.style.left = -index * operatingElements.swiperItemWidth + 'px'
      }
  
      index--
      animate(operatingElements.swiperItem, operatingElements.swiperItemWidth * -index, () => operatingElements.flag = true)
  
      circle--
      if (circle < 0) circle = operatingElements.indicatorDots.length - 1
  
      operatingElements.circleChange()
    }
  }
  // 右按钮
  function btnRightClick() {
    if (operatingElements.flag) {
      operatingElements.flag = false
      if (index == operatingElements.indicatorDots.length) {
        operatingElements.swiperItem.style.left = 0
        index = 0
      }
  
      index++
      // 执行动画函数 计算动画的距离 传入回调函数进行节流阀操作
      animate(operatingElements.swiperItem, -operatingElements.swiperItemWidth * index, () => operatingElements.flag = true)
  
      circle++
      if (index == operatingElements.indicatorDots.length) circle = 0
  
      operatingElements.circleChange()
    }
  }
  // 左右按钮点击事件
  operatingElements.btnLeft.addEventListener('click', btnLeftClick)
  operatingElements.btnRight.addEventListener('click', btnRightClick)
  // 自动播放 间隔 3000ms
  let timer = null
  function autoplay() {
    timer = setInterval(() => {
      operatingElements.btnRight.click()
    }, 3000)
  }
  autoplay()
  // 进入隐藏按钮
  operatingElements.swiper.addEventListener('mouseenter', () => {
    operatingElements.btnLeft.style.display = 'block'
    operatingElements.btnRight.style.display = 'block'
    clearInterval(timer)
    timer = null
  })
  // 离开显示按钮
  operatingElements.swiper.addEventListener('mouseleave', () => {
    operatingElements.btnLeft.style.display = 'none'
    operatingElements.btnRight.style.display = 'none'
    autoplay()
  } )
  console.log(operatingElements); 
}