JS基础 - DOM综合案例

384 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

关闭提示框

<!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>
  <style>
     .top-bar {
      display: flex;
      flex-direction: row;
      align-items: center;
      height: 45px;
      width: 375px;
      background-color: black;
      /* 关键 */
      overflow: hidden;
      transition: all .5s ease-out;
    }
    .delete {
      display: flex;
      flex-direction: row;
      justify-content: center;
      align-items: center;
      height: 100%;
      width: 30px;
      cursor: pointer;
    }
    .delete img {
      height: 10px;
      width: 10px;
    }
    .logo {
      height: 30px;
      width: 30px;
      margin-left:3px;
      margin-right: 30px;
      cursor: pointer;
    }
    span {
      color: white;
      font-size: 14px;
      flex: 1;

      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .btn {
      width: 94px;
      height: 100%;
      line-height: 45px;
      text-align: center;
      font-size: 14px;
      color: #fff;
      background-color: #F63515;
    }
  </style>
</head>
<body>
  <div class="top-bar">
    <div class="delete">
      <img src="//m.360buyimg.com/mobilecms/jfs/t19480/10/1439571805/820/787bec2c/5ac9d730N04e6d766.png" alt="">
    </div>
    <img class="logo" src="//img11.360buyimg.com/jdphoto/s80x80_jfs/t27847/91/107794072/6854/14716732/5b850ecaN644d2983.png" alt="">
    <span>打开京东App,购物更轻松</span>
    <div class="btn">立即打开</div>
  </div>

  <script>
    const topBarEl = document.querySelector('.top-bar')
    const delEl = topBarEl.querySelector('.delete')
		
    // 如果直接使用remove方法,移除div.top-bar将没有过渡动画
    // 因为在执行过渡动画前,对应元素已经被移除,无法再去执行对应的动画
    // 解决方法 1. 使用定时器 在动画结束完毕后在移除元素 -- 过分依赖定时器的时间 - 不推荐
    // 解决方法 2. 监听transitionend事件,在transition事件结束完毕后,再去移除对于的元素
    delEl.addEventListener('click',() => topBarEl.style.opacity = 0)

    topBarEl.addEventListener('transitionend', () => topBarEl.remove())
  </script>
</body>
</html>

侧边栏展示

image.png

<!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>
  <style>
    .tool-bar {
      position: fixed;
      top: 30%;
      right: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      width: 35px;
    }

    .item {
      position: relative;
      width: 35px;
      height: 35px;
      margin-bottom: 1px;
      background-color: #7a6e6e;
      border-radius: 3px 0 0 3px;
    }

    .icon {
      display: inline-block;
      width: 100%;
      height: 100%;
      cursor: pointer;
      background-image: url(https://static.360buyimg.com/devfe/toolbar/1.0.0/css/i/toolbars-png8.png);
    }

    .name {
      position: absolute;
      /*
        width为0的时候,不显示元素,但是元素的文本内容是不会隐藏的,会溢出
        所以需要使用某种方式去隐藏溢出的文本内容
        1. 使用z-index -- 确保其在正常布局流元素的下边,从而被覆盖
        2. 使用overflow -- 设置属性值为hidden,从而溢出隐藏
      */
      /* z-index: -1; */
      overflow: hidden;
      /*
      	使用right进行定位,目的是为了宽度在执行渐变动画的时候,宽度是向左递增,而不是向右递增
      */
      right: 35px;
      top: 0;
      width: 0;
      height: 35px;
      line-height: 35px;
      color: #fff;
      text-align: center;
      font-size: 12px;
      background-color: #7a6e6e;
      cursor: pointer;
      border-radius: 3px 0 0 3px;
      transition: width .2s ease;
    }

    .item:hover,
    .item:hover .name {
      background-color: #cd1926;
    }
  </style>
</head>
<body>
  <div class="tool-bar">
    <div class="item">
      <i class="icon icon01"></i>
      <div class="name">购物车</div>
    </div>
    <div class="item">
      <i class="icon icon02"></i>
      <div class="name">收藏</div>
    </div>
    <div class="item">
      <i class="icon icon03"></i>
      <div class="name">限时活动</div>
    </div>
    <div class="item">
      <i class="icon icon04"></i>
      <div class="name">大礼包</div>
    </div>
  </div>

  <script src="./index.js"></script>
</body>
</html>

不使用事件委托

const toolbarEl = document.querySelector('.tool-bar')

// 要获取多个div.item 所以需要使用querySelectorAll方法
// querySelectorAll方法返回的数据类型为NodeList 所以其可以使用forEach遍历
const itemList = toolbarEl.querySelectorAll('.item')

itemList.forEach((item, index) => {
  const iconEl  = item.querySelector('.icon')
  const nameEl = item.querySelector('.name')

  // 因为精灵图的位置是存在一定规律的,所以使用js来设置对应的精灵图背景位置
  iconEl.style.backgroundPosition = `-48px ${-50 * index}px`

  // 需要监听的是item的事件,以便于移动到div.name上的时候,对应的效果依旧存在
  item.addEventListener('mouseover', () => {
    // 因为设置了width的 css transition 属性
    // 所以只需要通过修改对应元素的宽度就可以实现对应的动效
    nameEl.style.width = '60px'
  })

  // 因为这里是实际获取到对应的nameEl元素去添加对应的事件
  // 所以这里使用mouseenter/mouseleave 或 mouseover/mouseout都是一样的
  // 但是这样会导致绑定了8个对应的事件出来了函数,不推荐
  item.addEventListener('mouseout', () => {
    // 使用初始设置值 --- 在这里就是0px
    nameEl.style.width = ''
  })
})

使用事件委托

const toolbarEl = document.querySelector('.tool-bar')
const itemList = toolbarEl.querySelectorAll('.item')

itemList.forEach((item, index) => {
  const iconEl = item.querySelector('.icon')
  iconEl.style.backgroundPosition = `-48px ${-50 * index}px`
})

// 利用mouseover/mouseout事件具有冒泡的特性
// 将对应的事件全部绑定到toobarEl上,从而减少对应的事件处理函数
toolbarEl.addEventListener('mouseover', (e) => {
  // mouseover和mouseout事件会事件冒泡
  // 所以div.name div.item 和 i.icon都可能会因为冒泡而触发该回调函数
  // 所以为了避免事件多次触发,需要对e.target的类型进行判断
  if (!e.target.classList.contains('tool-bar')) {
    // e.target是实际触发事件的那个元素,在这里就是i元素
    // 所以需要使用nextElementSibling来获取对应的div.name
    const nameEl =
          e.target.tagName === 'I' ? e.target.nextElementSibling : e.target
    nameEl.style.width = '62px'
  }
})

toolbarEl.addEventListener('mouseout', (e) => {
  if (!e.target.classList.contains('tool-bar')) {
    // 如果鼠标悬浮在div.name上的时候,div.name也是需要呈现的
    // 所以e.target可能是div.name也可能是i元素,所以需要进行逻辑判断
    const nameEl =
          e.target.tagName === 'I' ? e.target.nextElementSibling : e.target
    nameEl.style.width = ''
  }
})

tab切换

<!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>
  <style>
    .tab-control {
      display: flex;
      width: 600px;
      text-align: center;
      margin: 100px auto 0;
    }

    .tab {
      flex: 1;
      cursor: pointer;
      padding-bottom: 10px;
    }

    .active {
      border-bottom: 3px solid skyblue;
    }
  </style>
</head>
<body>
  <div class="tab-control">
    <div class="tab active">tab1</div>
    <div class="tab">tab2</div>
    <div class="tab">tab3</div>
  </div>

  <script>
    const tabControl = document.querySelector('.tab-control')

    // 使用变量来记录div.active 避免多次遍历,多次查找
    let activeEl = document.querySelector('.active')

    // 使用事件委托绑定对应的事件处理函数
    tabControl.addEventListener('mouseover', e => {
      if (e.target.classList.contains('tab')) {
        // 存在activeEl === e.target 结果为 true的情况
        // 所以必须先将active类先移除,在添加active类,顺序不可以换
        activeEl.classList.remove('active')
        e.target.classList.add('active')

        activeEl = e.target
      }
    })
  </script>
</body>
</html>

无限轮播图

实现方式一

image.png

所有的轮播图都按照从左往右的顺序依次排好,每次只要移动一个slide的宽度即可

  1. 如果需要实现无限录播,需要在原有的slide数组的前面插入最后一张slide,在slide数组最后插入第一张slide

  2. 如果向左移动到第一张的时候,待其动画执行完毕后,瞬间移动到倒数第二张轮播图位置

    如果向右移动到最后一张的时候,待其动画执行完毕后,瞬间移动到第二张轮播图位置

    从而以便于通过overflow: hidden 来实现无限轮播

<!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>
    <style>
      ul,
      li {
        margin: 0;
        padding: 0px;
        list-style: none;
      }

      img {
        display: block;
      }

      .carousel {
        position: relative;
        width: 604px;
        height: 298px;
        overflow: hidden;
        margin: 100px auto 0;
        font-size: 25px;
      }

      .container {
        display: flex;
        transform: translateX(-604px);
        cursor: pointer;
      }

      .arrow {
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        background-color: gray;
        color: #fff;
        padding: 5px 10px;
        cursor: pointer;
      }

      .left {
        left: 15px;
      }

      .right {
        right: 15px;
      }

      .indicators {
        display: flex;
        justify-content: center;
        margin-top: 10px;
      }

      .indicator {
        width: 10px;
        height: 10px;
        border-radius: 50%;
        background-color: rgba(0, 0, 0, 0.3);
        margin-right: 5px;
        cursor: pointer;
      }

      .indicator.active {
        background-color: red;
      }
    </style>
  </head>
  <body>
    <div class="banner">
      <div class="carousel">
        <ul class="container"></ul>
        <div class="arrow left">&lt;</div>
        <div class="arrow right">&gt;</div>
      </div>

      <ul class="indicators"></ul>
    </div>

    <script src="./index.js"></script>
  </body>
</html>
const imgs = [
  'https://ossweb-img.qq.com/upload/adw/image/194/20220520/ee39b769db3955480511ef3bdf21a680.jpeg',
  'https://ossweb-img.qq.com/upload/adw/image/194/20220521/3c60ca3309eec7f245841594228ea8d9.jpeg',
  'https://ossweb-img.qq.com/upload/adw/image/194/20220521/369fe70c6091216724ee4f8e0ef4f022.jpeg',
  'https://ossweb-img.qq.com/upload/adw/image/194/20220518/5246f8fe0f16243ecfc513058b4afa8c.jpeg',
  'https://ossweb-img.qq.com/upload/adw/image/194/20220524/49071426740e7a0e07f77deaba5ab95f.jpeg'
]

const slides = [...imgs]
÷
const leftEl = document.querySelector('.left')
const rightEl = document.querySelector('.right')
const bannerEl = document.querySelector('.banner')
const containerEl = document.querySelector('.container')
const indicatorEl = document.querySelector('.indicators')

// 将可能在多处使用的动画执行时间,抽离为一个常量,便于后期维护和修改
const DURATION_TIME = 500

// 虽然定时器返回的是一个number类型的值
// 但是一般情况下,定时器的timer会被初始化为null
let timer = null
let activeIndex = 1
let liElList = []
let activeEl = null

// 用于对左右按钮进行防抖的标志位
let isTransitioning = false

// 界面初始化函数
function init() {
  // 添加轮播图片
  const fragment = document.createDocumentFragment()

  slides.unshift(imgs[imgs.length - 1])
  slides.push(imgs[0])

  for (const url of slides) {
    const liEl = document.createElement('li')
    liEl.classList.add('slide')

    // 对于可替换元素,既可以使用document.createElement来创建对应的元素
    // JS 也提供了对应的构造函数,来实现相同的功能
    const img = new Image()
    img.src = url

    liEl.append(img)
    fragment.append(liEl)
  }

  containerEl.append(fragment)

  // 添加指示器
  const indicatorFragment = document.createDocumentFragment()

  imgs.forEach((_, i) => {
    const liEl = document.createElement('li')
    liElList.push(liEl)

    liEl.classList.add('indicator')

    if (i + 1 === activeIndex) {
      liEl.classList.add('active')
      activeEl = liEl
    }

    indicatorFragment.append(liEl)
  })

  indicatorEl.append(indicatorFragment)
}

// 轮播切换
const changeSlide = (step) => {
  if (isTransitioning) return
  isTransitioning = true

  let indicIndex = 0
  activeIndex -= step

  // 轮播滚动
  if (activeIndex < 0) {
    activeIndex = 0
  } else if (activeIndex >= slides.length) {
    activeIndex = slides.length - 1
  }

  // 指示器切换
  if (activeIndex <= 0) {
    indicIndex = imgs.length - 1
  } else if (activeIndex > imgs.length) {
    indicIndex = 0
  } else {
    indicIndex = activeIndex - 1
  }

  slideTransform(liElList[indicIndex])
}

// 实现无限轮播
containerEl.addEventListener('transitionend', () => {
  if (activeIndex === 0 || activeIndex === slides.length - 1) {
    if (activeIndex === 0) {
      activeIndex = slides.length - 2
    } else {
      activeIndex = 1
    }

    // translateX的百分比是基于自身的宽度值,所以直接位于对应的i * -100%即可
    containerEl.style.transform = `translateX(${activeIndex * -100}%)`
    containerEl.style.transition = ''
  }

  isTransitioning = false
})

indicatorEl.addEventListener('mouseover', (e) => {
  if (e.target.tagName !== 'UL') {
    // 使用原型和显示绑定的方式来使类数组对象可以调用数组的方法
    // const index = Array.prototype.findIndex.call(liElList, (liEl) => liEl === activeEl)

    // Array.from方法可以将一个可迭代对象 转换为 普通数组
    const index = Array.from(liElList).findIndex((liEl) => liEl === e.target)

    activeIndex = index + 1
    slideTransform(e.target)
  }
})

// 轮播图位移和activeEl更新
function slideTransform(target) {
  activeEl.classList.remove('active')
  target.classList.add('active')
  activeEl = target

  containerEl.style.transform = `translateX(${activeIndex * -100}%)`
  containerEl.style.transition = `transform ${DURATION_TIME}ms ease`
}

// 自动轮播
function startCarousel() {
  // 避免多次启动轮播
  // 如果多次启动对应的轮播动画,会导致动画效果叠加
  // 最终导致整体的动画速度变快
  if (timer) return

  timer = setInterval(() => changeSlide(-1), 3000)
}

function stopCarousel() {
  clearInterval(timer)
  // clearInterval会清楚定时器,但是不会重置timer
  // 所以推荐在清楚定时器后,重置timer标识符
  timer = null
}

// visibilitychange事件会在页面被隐藏或被完全遮盖或切换显示状态(如浏览器最小化)时触发
// 当用户离开页面的时候,绝大多数现代浏览器会停止挂起标签页的定时器行为,但其被激活后再进行执行
// 但是如果一些老版本浏览器 没有在挂起的时候停止对应的定时器 比较损耗性能 可以添加对应的处理逻辑

// 这里不可以使用focus/blur事件,因为focus/blur 事件只能在某个元素或整个文档失去或获取焦点的时候触发对应的事件,
// 但是如果直接在浏览器页签之间的切换,浏览器被遮挡 或 切换显示状态的时候, 并不会触发对应的focus/blur事件

// document.addEventListener('visibilitychange', () => {
//   // document.visibilityState有两个状态值
//   // visible --- 页面可见
//   // hidden --- 页面隐藏
//   if (document.visibilityState === 'visible') {
//     startCarousel()
//   } else {
//     stopCarousel()
//   }
// })

// 鼠标进入轮播图容器停止轮播,离开轮播图容器开始轮播
// 使用mouseenter/mouseleave判断是否在父容器中即可, 不需要使用mouseover/mouseleave
bannerEl.addEventListener('mouseenter', () => stopCarousel())
bannerEl.addEventListener('mouseleave', () => startCarousel())

leftEl.addEventListener('click', () => changeSlide(1))
rightEl.addEventListener('click', () => changeSlide(-1))

init()
startCarousel()

实现方式二

image.png

使用之前的思路去实现轮播图的时候,存在一个问题

即如果需要从第一张幻灯片移动到最后一张幻灯片的时候,

因为其动画时间不变但是其移动距离变大了,所以会导致动画的执行会相应的变快

所以为了解决这个问题,就存在另一种实现轮播图的思路

即当前需要展示的轮播图的索引记为currentIndex

将下一个需要进场的轮播图索引极为activeIndex

所有索引小于currentIndex的轮播图全部位于左侧

所有索引大于currentIndex的轮播图全部位于右侧

等到进行轮播的时候,只需要为currentIndex和activeIndex所对应的轮播slide执行对应为动画即可

其余的轮播slide直接安装之前的规则重新定位,并更新currentIndex和activeIndex

这样就可以保证无论从那个轮播图片移动到另一个轮播图片,其移动距离都是固定的

因此就不会出现因为轮播距离变大,但轮播时间不变,从而导致动画执行速度变快的情况

<!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>
    <style>
      ul,
      li {
        margin: 0;
        padding: 0px;
        list-style: none;
      }

      img {
        display: block;
      }

      .carousel {
        position: relative;
        width: 604px;
        height: 298px;
        overflow: hidden;
        margin: 100px auto 0;
        font-size: 25px;
      }

      .container {
        width: 100%;
        height: 100%;
        cursor: pointer;
      }

      .slide {
        position: absolute;
        top: 0;
        left: 100%;
        transition: left 1s ease;
      }

      /*
        为了实现无限轮播,所以第一张图实际上等于倒数第二张图
        而真正的第一张图 其实是数组中的第二张图
      */
      .slide:first-child {
        left: -100%;
      }

      .slide:nth-child(2) {
        left: 0;
      }

      .arrow {
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        background-color: gray;
        color: #fff;
        padding: 5px 10px;
        cursor: pointer;
      }

      .left {
        left: 15px;
      }

      .right {
        right: 15px;
      }

      .indicators {
        display: flex;
        justify-content: center;
        margin-top: 10px;
      }

      .indicator {
        width: 10px;
        height: 10px;
        border-radius: 50%;
        background-color: rgba(0, 0, 0, 0.3);
        margin-right: 5px;
        cursor: pointer;
      }

      .indicator.active {
        background-color: red;
      }
    </style>
  </head>
  <body>
    <div class="banner">
      <div class="carousel">
        <ul class="container"></ul>
        <div class="arrow left">&lt;</div>
        <div class="arrow right">&gt;</div>
      </div>

      <ul class="indicators"></ul>
    </div>

    <script src="./index.js"></script>
  </body>
</html>
const imgs = [
  'https://ossweb-img.qq.com/upload/adw/image/194/20220520/ee39b769db3955480511ef3bdf21a680.jpeg',
  'https://ossweb-img.qq.com/upload/adw/image/194/20220521/3c60ca3309eec7f245841594228ea8d9.jpeg',
  'https://ossweb-img.qq.com/upload/adw/image/194/20220521/369fe70c6091216724ee4f8e0ef4f022.jpeg',
  'https://ossweb-img.qq.com/upload/adw/image/194/20220518/5246f8fe0f16243ecfc513058b4afa8c.jpeg',
  'https://ossweb-img.qq.com/upload/adw/image/194/20220524/49071426740e7a0e07f77deaba5ab95f.jpeg'
]

// 在imgs前后都加上对应的图片,用于实现无限轮播
const slides = [imgs[imgs.length - 1], ...imgs, imgs[0]]
const liElList = []
const slideEls = []

const leftEl = document.querySelector('.left')
const rightEl = document.querySelector('.right')
const bannerEl = document.querySelector('.banner')
const containerEl = document.querySelector('.container')
const indicatorEl = document.querySelector('.indicators')

const width = containerEl.offsetWidth

let timer = null
let indicatorTimer = null
let leftTimer = null
let rightTimer = null

let activeIndex = 1
let preIndex = 1
let preIndicIndex = 0
let indicatorIndex = 0

// 界面初始化
function init() {
  const fragment = document.createDocumentFragment()

  for (const url of slides) {
    const liEl = document.createElement('li')
    liEl.classList.add('slide')
    const img = new Image()
    img.src = url

    liEl.append(img)
    fragment.append(liEl)
    slideEls.push(liEl)
  }

  containerEl.append(fragment)

  const indicatorFragment = document.createDocumentFragment()

  imgs.forEach((_, i) => {
    const liEl = document.createElement('li')
    liElList.push(liEl)

    liEl.classList.add('indicator')

    if (i === indicatorIndex) {
      liEl.classList.add('active')
    }

    indicatorFragment.append(liEl)
  })

  indicatorEl.append(indicatorFragment)

  // 界面初始化后,监听最后一张slide的transitionend事件
  // 即在展示最后一张的图的时候,直接进行瞬移,以使第二张图处于展示位(也在div.container中进行显示,即该slide的left属性为0)中

  // 各个slide的位置如下:
  // 1. 第二个slide 位于展示位中 即left为0
  // 2. 第一张图 位于展示位的左边
  // 3. 其余图 位于展示位的右边
  slideEls[slideEls.length - 1].addEventListener('transitionend', () => {
    for (let i = 0; i < slideEls.length; i++) {
      if (i === 1) {
        slideEls[i].style.left = '0'
        slideEls[i].style.transition = 'none'
      } else if (i >= 2) {
        slideEls[i].style.left = '100%'
        slideEls[i].style.transition = 'none'
      }
    }

    activeIndex = 1
  })

  // 界面初始化后,监听第一张slide的transitionend事件
  // 即在展示第一张的图的时候,直接进行瞬移,以使倒数第二张图处于展示位中

  // 各个slide的位置如下:
  // 1. 倒数第二个slide 位于展示位中 即left为0
  // 2. 最后一张图 位于展示位的右边
  // 3. 其余图 位于展示位的左边
  slideEls[0].addEventListener('transitionend', () => {
    for (let i = 0; i < slideEls.length; i++) {
      if (i === slideEls.length - 2) {
        slideEls[i].style.left = '0'
        slideEls[i].style.transition = 'none'
      } else if (i < slideEls.length - 2) {
        slideEls[i].style.left = '-100%'
        slideEls[i].style.transition = 'none'
      }
    }

    activeIndex = slideEls.length - 2
  })
}

// 切换slide展示
const changeSlide = (step) => {
  preIndex = activeIndex
  preIndicIndex = indicatorIndex

  activeIndex -= step
  indicatorIndex -= step

  if (activeIndex < 0) {
    activeIndex = slides.length - 1
  } else if (activeIndex > slides.length) {
    activeIndex = 0
  }

  if (indicatorIndex < 0) {
    indicatorIndex = liElList.length - 1
  } else if (indicatorIndex > liElList.length - 1) {
    indicatorIndex = 0
  }

  changeSlidePosition(preIndex, activeIndex)
}

indicatorEl.addEventListener('mouseover', (e) => {
  if (e.target.tagName !== 'UL') {
    if (indicatorTimer) clearTimeout(indicatorTimer)

    indicatorTimer = setTimeout(() => {
      // 找到用户所选中的那个indicator所对应的索引
      const index = Array.from(liElList).findIndex(liEl => liEl === e.target)

      preIndicIndex = indicatorIndex
      indicatorIndex = index

      preIndex = activeIndex
      // index是对应的指示器索引
      // 而activeIndex应该是对应的slide所对应的索引值
      // 因为我们在原本的imgs的前后都插入了一张图片以实现无限轮播
      // 所以 activeIndex 实际上为 index + 1
      activeIndex = index + 1

      changeSlidePosition(preIndex, activeIndex)
    }, 300)
  }
})

// 实际slide进行切换的函数
function changeSlidePosition(preIndex, activeIndex) {
  // 指示器高亮的更新
  liElList[preIndicIndex].classList.remove('active')
  liElList[indicatorIndex].classList.add('active')

  // 更新各个slide的位置
  // 1. 当前activeIndex对应的slide处于展示位中
  // 2. index 小于 activeIndex的slide处于展示位的左边
  // 3. index 大于 activeIndex的slide处于展示位的右边

  // 当切换slide的时候
  // 1. 当前需要展示的slide(即当前需要入场的slide)需要添加移动动画
  // 2. 上一个处于展示位的slide(即当前需要离场的slide)需要添加移动动画
  // 3. 其余的slide 如果index小于activeIndex 则放置在展示位的左边,不需要移动动画
  // 4. 其余的slide 如果index大于activeIndex 则放置在展示位的右边,不需要移动动画

  // 这样slide的切换 就类似于抽卡并移动
  // 就可以避免将slide放入一个大的容器中进行左右移动 造成的移动位置不固定的问题
  for (let i = 0; i < slideEls.length; i++) {
    const slideEl = slideEls[i]

    // 如果是当期需要展示的slide
    // 则移动到展示位进行显示,并为位移过程添加对应的过渡动画
    if (i === activeIndex) {
      slideEl.style.left = '0px'
      slideEl.style.transition = 'left 1s ease'
    } else if (i < activeIndex) {
      // index小于activeIndex的slide 放置到展示位的左边
      slideEl.style.left = '-100%'
      if (i !== preIndex) {
        // 如果不是需要离场的那个slide
        // 直接改变位置 并且不需要对应的过渡动画
        slideEl.style.transition = 'none'
      } else {
        // 如果是需要离场的slide,那么就需要显示的添加对应的过渡动画
        // 因为之前为了实现无限轮播,有显示清除过每一个slide的过渡动画
        slideEl.style.transition = 'left 1s ease'
      }
    } else {
      // index大于activeIndex的slide 放置到展示位的右边
      slideEl.style.left = '100%'
      if (i !== preIndex) {
        // 如果不是需要离场的那个slide
        // 直接改变位置 并且不需要对应的过渡动画
        slideEl.style.transition = 'none'
      } else {
        // 如果是需要离场的slide,那么就需要显示的添加对应的过渡动画
        // 因为之前为了实现无限轮播,有显示清除过每一个slide的过渡动画
        slideEl.style.transition = 'left 1s ease'
      }
    }
  }
}

// 开启自动轮播
function startCarousel() {
  timer = setInterval(() => {
    changeSlide(-1)
  }, 3000)
}

bannerEl.addEventListener('mouseenter', () => clearInterval(timer))
bannerEl.addEventListener('mouseleave', () => startCarousel())

leftEl.addEventListener('click', () => {
  if (leftTimer) clearTimeout(leftTimer)
  leftTimer = setTimeout(() => changeSlide(1), 300)
})

rightEl.addEventListener('click', () => {
  if (rightTimer) clearTimeout(rightTimer)
  rightTimer = setTimeout(() => changeSlide(-1), 300)
})

init()
startCarousel()