JavaScript实战-华为商城轮播图

1,010 阅读2分钟

华为商城轮播图

image.png

1. 界面搭建

  • 通过js操作DOM来创建元素

  • 此处用两个按钮来实现左右图片的切换

    <!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="./css/reset.css">
      <style>
        .banner {
          position: relative;
        }
    
        .banner .images {
          position: relative;
          height: 550px;
        }
    
        .banner .images .item {
          position: absolute;
          left: 0;
          top: 0;
          width: 100%;
          overflow: hidden;
    
          opacity: 0;
        }
    
        .banner .images .item.active {
          opacity: 1;
        }
    
        .banner .images .item img {
          /* 让图片始终保持居中 */
          position: relative;
          transform: translate(-50%);
          left: 50%;
          width: 1920px;
          height: 550px;
        }
    
        .indicator {
          position: absolute;
          left: 0;
          right: 0;
          bottom: 42px;
          display: flex;
          justify-content: center;
        }
    
        .indicator .item {
          width: 8px;
          height: 8px;
          margin: 0 10px;
          border-radius: 50%;
          background-color: #aaa;
          cursor: pointer;
        }
    
        .indicator .item.active {
          background-color: #f00;
        }
      </style>
    </head>
    <body>
    
      <div class="banner">
        <ul class="images">
        </ul>
        <div class="control">
          <button>上一个</button>
          <button>下一个</button>
        </div>
        <div class="indicator"></div>
      </div>
    
      <script>
        // 数据
        var banners = [
          {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/C8902152690C16B8BCAA4E1B965874DA.png'},
          {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/BC1BC406447375648AAE4BCB440BEE05.jpg'},
          {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/F93536BA74BE3D8D06428DE5A2E211E4.jpg'},
          {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/5A1F2B90C856ED4B321CF05A9D9FA949.jpg'},
          {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/C7A58CDA269846F7E71AE5004C0C2B70.png'},
          {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/12ECC885513BF79F8C285DCCCD8AD68A.jpg'},
          {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/3D4E43516F506267A422146BFF5CDA34.jpg'},
          {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/D9939179EEBBC758CA6C14B2B10996B9.jpg'}
        ]
    
        // 动态添加轮播图图片数据
        var imagesEl = document.querySelector(".images")
        var activeItemEl = null
        for (var i = 0; i < banners.length; i++) {
          // 获取数据
          var banner = banners[i]
    
          // 创建li元素
          var itemEl = document.createElement("li")
          itemEl.classList.add("item")
          // 默认第一个为active
          if (i === 0) {
            itemEl.classList.add("active")
            activeItemEl = itemEl
          }
          imagesEl.append(itemEl)
    
          // 创建img元素
          var imgEl = document.createElement("img")
          imgEl.src = banner.imgUrl
          itemEl.append(imgEl)
        }
    
        // 动态添加指示器内容
        var indicatorEl = document.querySelector(".indicator")
        for (var i = 0; i < banners.length; i++) {
          var itemEl = document.createElement("div")
          itemEl.classList.add("item")
          if (i === 0) {
            itemEl.classList.add("active")
          }
          indicatorEl.append(itemEl)
        }
    
      </script>
    
    </body>
    </html>
    

2. 官网的效果

官网的效果其实是通过控制opacity,添加动画来实现

2.1 实现通过点击按钮切换图片

  • 获取按钮对应的dom元素,记录当前元素的索引
  • 切换时,当前元素的opacity变为0,上一张或下一张图片的opacity变为1,可定义一个prevIndex来记录上一张图片的索引
  • 点击下一个索引加1,等于图片数量时,重置为0,点击上一个索引减1,等于-1时,重置为最后一张图片的索引,即banners.length-1
    // 记录变量
    var prevIndex = 0
    var curIndex = 0
    var controlEl = document.querySelector(".control")
    var prevBtnEl = controlEl.querySelector(".prev")
    var nextBtnEl = controlEl.querySelector(".next")
    
    // 添加按钮的点击
    prevBtnEl.onclick = function() {
      prevIndex = curIndex
      curIndex --
    
      if(curIndex === -1) {
        curIndex = banners.length - 1
      }
    
      switchBannerItem()
    }
    
    nextBtnEl.onclick = function() {
      prevIndex = curIndex
      curIndex ++
      if(curIndex === banners.length) {
        curIndex = 0
      }
    
      switchBannerItem()
    }
    
    // 封装图片切换函数
    function switchBannerItem() {
      // 1.切换图片
      // 记录当前作用元素的dom
      var curItemEl = imagesEl.children[curIndex]
      var prevItemEl = imagesEl.children[prevIndex]
      // 移除上一张图片的active
      prevItemEl.classList.remove('active')
      curItemEl.classList.add('active')
    
      // 2.切换指示器
      var curInItemEl = indicatorEl.children[curIndex]
      var prevInItemEl = indicatorEl.children[prevIndex]
      prevInItemEl.classList.remove('active')
      curInItemEl.classList.add('active')
    }
    

2.2 实现自动轮播

  • 一般情况下,轮播图是从左往右轮播,所以自动轮播只需要将下一个按钮的点击方法执行即可

  • 每等待3s钟执行一次下一个图片的切换,为了代码阅读性,将其抽离成一个函数nextSwitch

  • 注意:鼠标移入banner时,是需要取消轮播的,即清除定时器,此处不理解可查阅我的上一篇文章JavaScript实战-王者荣耀轮播图

    function nextSwitch() {
      prevIndex = curIndex
      curIndex ++
      if(curIndex === banners.length) {
        curIndex = 0
      }
    
      switchBannerItem()
    }
    
    // 自动轮播效果
    var timer = null
    startTimer()
    
    
    // 取消自动轮播
    var bannerEl = document.querySelector('.banner')
    bannerEl.onmouseenter = function() {
      clearInterval(timer)
    }
    
    // 开启自动轮播
    bannerEl.onmouseleave = function() {
      startTimer()
    }
    
    function startTimer() {
      timer = setInterval(function() {
        nextSwitch()
      }, 3000)
    }
    

2.3 点击指示器切换轮播

  • 在创建指示器时监听指示器的点击事件,改变其class

    • 修改curIndex和prevIndex,如何拿到当前点击元素的index呢?
      • 使用立即执行函数,此处不过多介绍,后续会补充相关文章
      • 将var改为let,因为let有块级作用域,后续会补充相关文章
      • 在外面遍历的时候给每一个itemEl上添加一个index属性,然后在点击的时候通过this来取(本文采用此方法)
    • 调用切换图片的函数
    // 记录指示器的索引
    itemEl.index = i
    // 添加点击方法
    itemEl.onclick = function() {
        prevIndex = curIndex
        curIndex = this.index
        switchBannerItem()
    }
    

2.4 完整js代码

// 数据
var banners = [
  {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/C8902152690C16B8BCAA4E1B965874DA.png'},
  {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/BC1BC406447375648AAE4BCB440BEE05.jpg'},
  {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/F93536BA74BE3D8D06428DE5A2E211E4.jpg'},
  {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/5A1F2B90C856ED4B321CF05A9D9FA949.jpg'},
  {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/C7A58CDA269846F7E71AE5004C0C2B70.png'},
  {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/12ECC885513BF79F8C285DCCCD8AD68A.jpg'},
  {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/3D4E43516F506267A422146BFF5CDA34.jpg'},
  {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/D9939179EEBBC758CA6C14B2B10996B9.jpg'}
]

// 动态添加轮播图图片数据
var imagesEl = document.querySelector(".images")
var activeItemEl = null
for (var i = 0; i < banners.length; i++) {
  // 获取数据
  var banner = banners[i]

  // 创建li元素
  var itemEl = document.createElement("li")
  itemEl.classList.add("item")
  if (i === 0) {
    itemEl.classList.add("active")
    activeItemEl = itemEl
  }
  imagesEl.append(itemEl)

  // 创建img元素
  var imgEl = document.createElement("img")
  imgEl.src = banner.imgUrl
  itemEl.append(imgEl)
}

// 动态添加指示器内容
var indicatorEl = document.querySelector(".indicator")
for (var i = 0; i < banners.length; i++) {
  var itemEl = document.createElement("div")
  itemEl.classList.add("item")
  if (i === 0) {
    itemEl.classList.add("active")
  }
  indicatorEl.append(itemEl)

  // 记录指示器的索引
  itemEl.index = i

  // 添加点击方法
  itemEl.onclick = function() {
    prevIndex = curIndex
    curIndex = this.index
    switchBannerItem()
  }
}

// 添加按钮的点击
var prevIndex = 0
var curIndex = 0
var controlEl = document.querySelector(".control")
var prevBtnEl = controlEl.querySelector(".prev")
var nextBtnEl = controlEl.querySelector(".next")
prevBtnEl.onclick = function() {
  prevIndex = curIndex
  curIndex --

  if(curIndex === -1) {
    curIndex = banners.length - 1
  }

  switchBannerItem()
}

nextBtnEl.onclick = function() {
  nextSwitch()
}

// 封装图片切换函数
function switchBannerItem() {
  // 1.切换图片
  // 记录当前作用元素的dom
  var curItemEl = imagesEl.children[curIndex]
  var prevItemEl = imagesEl.children[prevIndex]
  // 移除上一张图片的active
  prevItemEl.classList.remove('active')
  curItemEl.classList.add('active')

  // 2.切换指示器
  var curInItemEl = indicatorEl.children[curIndex]
  var prevInItemEl = indicatorEl.children[prevIndex]
  prevInItemEl.classList.remove('active')
  curInItemEl.classList.add('active')
}

function nextSwitch() {
  prevIndex = curIndex
  curIndex ++
  if(curIndex === banners.length) {
    curIndex = 0
  }

  switchBannerItem()
}

// 自动轮播效果
var timer = null
startTimer()

// 移除定时器和开启定时器
var bannerEl = document.querySelector('.banner')
bannerEl.onmouseenter = function() {
  clearInterval(timer)
}

bannerEl.onmouseleave = function() {
  startTimer()
}

function startTimer() {
  timer = setInterval(function() {
    nextSwitch()
  }, 3000)
}

3. 无限轮播

3.1 改变布局

  • 此处仍然使用定位来做,通过修改图片的left值实现轮播
    • 移除图片的opacity样式
    • 设置图片的left值
    // 在创建元素的代码块中修改left值
    // 设置itemEl的位置
    itemEl.style.left = `${i * 100}%`
    

3.2 位移轮播

  • 点击按钮,整体移动图片,即通过调整父元素ultransform

  • 给父元素ul添加动画效果transition: all 500ms ease;

    // 封装图片切换函数
    function switchBannerItem() {
      // 1.切换图片
      imagesEl.style.transform = `translateX(-${curIndex *100}%)`
    
      // 2.切换指示器
      var curInItemEl = indicatorEl.children[curIndex]
      var prevInItemEl = indicatorEl.children[prevIndex]
      prevInItemEl.classList.remove('active')
      curInItemEl.classList.add('active')
    }
    

3.3 无限轮播

  • 克隆first和last的Element

    • cloneNode
    • 追加到对应的位置:最后一个图片后加第一个图片,第一个图片后加最后一个图片
    • 修改left值
      • last.left = -100%
      • first.left = (100*bannerCount)%
    // 追加:(无限轮播), 最后和最前分别添加一个元素
    var firstItem = imagesEl.children[0].cloneNode(true) // true 指深度克隆
    var lastItem = imagesEl.children[bannersCount - 1].cloneNode(true)
    imagesEl.append(firstItem)
    imagesEl.prepend(lastItem)
    // 调整位置
    firstItem.style.left = `${bannersCount * 100}%`
    lastItem.style.left = '-100%'
    
  • 索引的判断放到滚动之后

    • 因为当图片滚到banners最后一个时,应该再向左移动一次,展示克隆的firstItem
    • 滚到第一个再向上一个时同理
  • 快速修正位置 - 不能有动画

    // 封装修复位置的函数
    function fixBannerPosition() {
      setTimeout(() => {
        imagesEl.style.transition = 'none'
        imagesEl.style.transform = `translateX(${-curIndex * 100}%)`
      }, animationDuration)
    }
    
    // 封装图片切换函数
    function switchBannerItem() {
      // 1.切换图片
      imagesEl.style.transition = `all ${animationDuration}ms ease`
      imagesEl.style.transform = `translateX(${-curIndex * 100}%)`
    
      if(curIndex === bannersCount) {
        curIndex = 0
    
        // 调整位置
        fixBannerPosition()
      } else if(curIndex === -1) {
        curIndex = bannersCount - 1
    
        fixBannerPosition()
      }
    
      // 2.切换指示器
      var curInItemEl = indicatorEl.children[curIndex]
      var prevInItemEl = indicatorEl.children[prevIndex]
      prevInItemEl.classList.remove('active')
      curInItemEl.classList.add('active')
    }
    

3.4 增加对定时器的处理

  • 离开界面/进入界面对定时器处理
    • 离开界面时清除定时器
    • 进入界面开始定时器
    // 通过window的焦点
    document.onvisibilitychange = function() {
      if (document.visibilityState === "visible") {
        startTimer()
      } else if (document.visibilityState === "hidden") {
        stopTimer()
      }
    }
    
  • timer更加严谨
    • 保证轮播图只开启一个定时器
    function startTimer() {
      if (timer) return
      timer = setInterval(function() {
        nextSwitch()
      }, 3000)
    }
    
    function stopTimer() {
      if (!timer) return
      clearInterval(timer)
      timer = null // 清除定时器之后, 必须timer赋值为null,否则下次的startTimer()直接return掉了
    }
    

3.5 完整代码

<!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="./css/reset.css">
  <style>
    .banner {
      position: relative;
      overflow: hidden;
    }

    .banner .images {
      position: relative;
      height: 550px;

      transition: all 500ms ease;
    }

    .banner .images .item {
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      overflow: hidden;
    }

    .banner .images .item img {
      /* 让图片始终保持居中 */
      position: relative;
      transform: translate(-50%);
      left: 50%;
      width: 1920px;
      height: 550px;
    }

    .indicator {
      position: absolute;
      left: 0;
      right: 0;
      bottom: 42px;
      display: flex;
      justify-content: center;
    }

    .indicator .item {
      width: 8px;
      height: 8px;
      margin: 0 10px;
      border-radius: 50%;
      background-color: #aaa;
      cursor: pointer;
    }

    .indicator .item.active {
      background-color: #f00;
    }
  </style>
</head>
<body>
  
  <div class="banner">
    <ul class="images">
    </ul>
    <div class="control">
      <button class="prev">上一个</button>
      <button class="next">下一个</button>
    </div>
    <div class="indicator"></div>
  </div>
  
  <script>
    // 数据
    var banners = [
      {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/C8902152690C16B8BCAA4E1B965874DA.png'},
      {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/BC1BC406447375648AAE4BCB440BEE05.jpg'},
      {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/F93536BA74BE3D8D06428DE5A2E211E4.jpg'},
      {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/5A1F2B90C856ED4B321CF05A9D9FA949.jpg'},
      {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/C7A58CDA269846F7E71AE5004C0C2B70.png'},
      {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/12ECC885513BF79F8C285DCCCD8AD68A.jpg'},
      {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/3D4E43516F506267A422146BFF5CDA34.jpg'},
      {imgUrl: 'https://res.vmallres.com/uomcdn/CN/cms/202205/D9939179EEBBC758CA6C14B2B10996B9.jpg'}
    ]

    var bannersCount = banners.length
    var animationDuration = 500

    // 动态添加轮播图图片数据
    var imagesEl = document.querySelector(".images")
    var activeItemEl = null
    for (var i = 0; i < bannersCount; i++) {
      // 获取数据
      var banner = banners[i]

      // 创建li元素
      var itemEl = document.createElement("li")
      itemEl.classList.add("item")
      if (i === 0) {
        itemEl.classList.add("active")
        activeItemEl = itemEl
      }
      imagesEl.append(itemEl)

      // 设置itemEl的位置
      itemEl.style.left = `${i * 100}%`

      // 创建img元素
      var imgEl = document.createElement("img")
      imgEl.src = banner.imgUrl
      itemEl.append(imgEl)
    }

    // 动态添加指示器内容
    var indicatorEl = document.querySelector(".indicator")
    for (var i = 0; i < bannersCount; i++) {
      var itemEl = document.createElement("div")
      itemEl.classList.add("item")
      if (i === 0) {
        itemEl.classList.add("active")
      }
      indicatorEl.append(itemEl)

      // 记录指示器的索引
      itemEl.index = i
      
      // 添加点击方法
      itemEl.onclick = function() {
        prevIndex = curIndex
        curIndex = this.index
        switchBannerItem()
      }
    }

    // 追加:(无限轮播), 最后和最前分别添加一个元素
    var firstItem = imagesEl.children[0].cloneNode(true) // true 指深度克隆
    var lastItem = imagesEl.children[bannersCount - 1].cloneNode(true)
    imagesEl.append(firstItem)
    imagesEl.prepend(lastItem)
    // 调整位置
    firstItem.style.left = `${bannersCount * 100}%`
    lastItem.style.left = '-100%'

    // 添加按钮的点击
    var prevIndex = 0
    var curIndex = 0
    var controlEl = document.querySelector(".control")
    var prevBtnEl = controlEl.querySelector(".prev")
    var nextBtnEl = controlEl.querySelector(".next")
    prevBtnEl.onclick = function() {
      prevIndex = curIndex
      curIndex --

      switchBannerItem()
    }

    nextBtnEl.onclick = function() {
      nextSwitch()
    }

    // 封装图片切换函数
    function switchBannerItem() {
      // 1.切换图片
      imagesEl.style.transition = `all ${animationDuration}ms ease`
      imagesEl.style.transform = `translateX(${-curIndex * 100}%)`

      if(curIndex === bannersCount) {
        curIndex = 0
        
        fixBannerPosition()
      } else if(curIndex === -1) {
        curIndex = bannersCount - 1
        
        fixBannerPosition()
      }

      // 2.切换指示器
      var curInItemEl = indicatorEl.children[curIndex]
      var prevInItemEl = indicatorEl.children[prevIndex]
      prevInItemEl.classList.remove('active')
      curInItemEl.classList.add('active')
    }

    function nextSwitch() {
      prevIndex = curIndex
      curIndex ++

      switchBannerItem()
    }

    // 自动轮播效果
    var timer = null
    startTimer()


    // 移除定时器和开启定时器
    var bannerEl = document.querySelector('.banner')
    bannerEl.onmouseenter = function() {
      stopTimer()
    }

    bannerEl.onmouseleave = function() {
      startTimer()
    }

    function startTimer() {
      if (timer) return;
      timer = setInterval(function() {
        nextSwitch()
      }, 3000)
    }

    function stopTimer() {
      if (!timer) return;
      clearInterval(timer)
      timer = null // 清除定时器之后, 必须timer赋值为null
    }

    // 封装修复位置的函数
    function fixBannerPosition() {
      setTimeout(() => {
        imagesEl.style.transition = 'none'
        imagesEl.style.transform = `translateX(${-curIndex * 100}%)`
      }, animationDuration)
    }

    // 通过window的焦点
    document.onvisibilitychange = function() {
      if (document.visibilityState === "visible") {
        startTimer()
      } else if (document.visibilityState === "hidden") {
        stopTimer()
      }
    }

  </script>

</body>
</html>

完美收官!!!