【原生js】—重写轮播图

432 阅读3分钟

轮播图的实现

结构搭建

Snipaste_2021-11-14_20-23-47.png

  .focus {
    position: relative;
    width: 721px;
    height: 455px;
    background-color: purple;
    overflow: hidden;
}

.focus ul {
    position: absolute;  // 必须添加定位,否则无法运动
    top: 0;
    left: 0;
    width: 600%;   // 这里的ul需要设置得尽量宽,才能让图片在处在一行上
}

.focus ul li {
    float: left;
}

.arrow-l,
.arrow-r {
    display: none;
    position: absolute;
    top: 50%;
    margin-top: -20px;
    width: 24px;
    height: 40px;
    background: rgba(0, 0, 0, .3);
    text-align: center;
    line-height: 40px;
    color: #fff;
    font-family: 'icomoon';
    font-size: 18px;
    z-index: 2;
}

.arrow-r {
    right: 0;
}

.circle {
    position: absolute;
    bottom: 10px;
    left: 50px;
}

.circle li {
    float: left;
    width: 8px;
    height: 8px;
    /*background-color: #fff;*/
    border: 2px solid rgba(255, 255, 255, 0.5);
    margin: 0 3px;
    border-radius: 50%;
    /*鼠标经过显示小手*/
    cursor: pointer;
}
  <div class="focus fl">
     <!-- 左侧按钮 -->
  <a href="javascript:;" class="arrow-l"> &lt; </a>
     <!-- 右侧按钮 -->
  <a href="javascript:;" class="arrow-r"> &gt; </a>
     <!-- 核心的滚动区域 -->
  <ul>
      <li>
          <a href="#"><img src="upload/focus.jpg" alt=""></a>
      </li>
      <li>
          <a href="#"><img src="upload/focus1.jpg" alt=""></a>
      </li>
      <li>
          <a href="#"><img src="upload/focus2.jpg" alt=""></a>
      </li>
      <li>
          <a href="#"><img src="upload/focus3.jpg" alt=""></a>
      </li>
  </ul>
  <!-- 底部小圆点 -->
  <ol class="circle">
     <li></li>
  </ol>

功能需求

1.左右按钮的显示与隐藏

  var arrow_l = document.querySelector('.arrow-l');
  var arrow_r = document.querySelector('.arrow-r');
  var focus = document.querySelector('.focus');

  // 1. 鼠标经过focus 就显示隐藏左右按钮
  focus.addEventListener('mouseenter', function() {
    arrow_l.style.display = 'block';
    arrow_r.style.display = 'block';
  });
focus.addEventListener('mouseleave', function() {
    arrow_l.style.display = 'none';
    arrow_r.style.display = 'none';
  });

2.根据图片张数动态生成小圆圈

思路:

1.首先得到ul里面图片的张数

2.利用for循环动态生成小圆圈,放入ol中

3.创建节点creatElement('li')

4.插入节点ol.appendChild(li)

5.第一个小圆圈需要添加current类

6.点击切换小圆圈

var ul = focus.querySelector('ul')
var ol = focus.querySelector('.circle')
// console.log(ul.children.length);  // 4
for(let i = 0; i < ul.children.length; i++) {
     // 创建一个li
     var li = document.createElement('li')
     // 把li插入到ol里面
     ol.appendChild(li)
     // 点击切换小圆圈底色:点击当前小圆圈,就添加current类,其余的小圆圈移除这个current类
       //在生成小圆点的同时直接绑定点击事件
     li.addEventListener('click', function() {
         // 先把所有的li清除current类名
        for(let i = 0; i < ol.children.length; i++) {
            ol.children[i].className = ''
        }
        // 把当前的点击的li 设置为current类名
        this.className = 'current'
     })
}
// 把ol里面的第一个li设置类名为current
ol.children[0].className = 'current'

3.点击小圆圈滚动图片

思路:

1.利用animate.js动画函数,可参考:juejin.cn/post/703032…

2.使用动画函数的前提,该元素必须有定位

3.注意是ul移动而不是li

4.核心算法:点击某个小圆圈,ul移动的的距离 = 小圆圈的索引号 * 图片的宽度

5.此时需要知道小圆圈的索引号,我们可以在生成小圆圈的时候,给它设置一个自定义属性,点击的时候获取这个自定义属性

Snipaste_2021-11-15_15-23-15.png

   var ul = focus.querySelector('ul')
   var ol = focus.querySelector('.circle')
  for(let i = 0; i < ul.children.length; i++) {
     var li = document.createElement('li') 
      // 记录当前小圆圈的索引号,通过自定义属性来做
     li.setAttribute('index', i)
     ol.appendChild(li)
     li.addEventListener('click', function() {     
        for(let i = 0; i < ol.children.length; i++) {
            ol.children[i].className = ''
        }     
        this.className = 'current'
        // 3.点击小圆圈,移动图片 移动的是ul
         // ul的移动距离 = 小圆圈索引号 * 图片的宽度 
         // 获取当前li的索引号
         var index = this.getAttribute('index') 
         // 图片的宽度 == foucus的宽度
         var focusWidth = focus.offsetWidth       
         animate(ul,-index * focusWidth, 15)
     })
}
ol.children[0].className = 'current'

4.点击右侧按钮图片滚动(无缝滚动)

思路:

1.声明一个变量num, 点击一次,自增1,ul移动的距离 = num * 图片的宽度

2.克隆第一张图片,放到ul最后面

  var first = ul.children[0].cloneNode(true)
  ul.appendChild(first)

3.无缝滚动原理;

(1)当图片滚动到克隆的最后一张图片时,让ul快速的,不做动画的跳到最左侧:left = 0

(2)同时num赋值为0,这时候num++, 变成1,所以显示第二张图片。

Snipaste_2021-11-15_16-15-59.png

// 4.点击左侧按钮,图片滚动一张
var num = 0
arrow_r.addEventListener('click', function() {
    // 如果走到了最后复制的一张图片,此时我们的ul要快速复原left = 0
    if(num = ul.children.length - 1) {
       ul.style.left = 0
       num = 0
    }
    num++
    var focusWidth = focus.offsetWidth
    animate(ul, -num * focusWidth, 15)
})

5.点击右侧按钮,小圆点跟着改变

思路:

1.再声明一个变量circle,每次点击自增1。

2.但是图片有5张,小圆圈只有4个少一个,必须加一个判断条件,如果走到了最后一张图片(克隆的),重新复原为0


  // 5.点击右侧按钮,小圆圈一起变化
    circle++
 // 如果circle == 4 说明走到了最后我们克隆的这张图片了, 我们就复原
    if(circle == ol.children.length) {
     circle = 0
  }
 
  // 先清除其余小圆圈的current类名
  for(var i = 0; i < ol.children.length; i++) {
     ol.children[i].className = ''
  }
  // 再留下当前小圆圈的current类名
    ol.children[circle].className = 'current'

6.两个bug

bug1. 点击小圆圈显示某张图片后,再点击右侧按钮,无法按照正常顺序显示下一张图片。

原因:控制图片的num 和 点击小圆圈的index没有联系起来

解决:让点击的小圆圈索引号 index = num

bug2. 点击小圆圈显示某张图片后,再点击右侧按钮,小圆圈没有正常跳到下一个。

原因: 控制小圆圈移动的circle 和 点击小圆圈的index没有联系起来

解决; 让点击的小圆圈索引号 index = circle

  li.addEventListener('click', function () {
   for (let i = 0; i < ol.children.length; i++) {
     ol.children[i].className = ''
   }
   this.className = 'current'
   var index = this.getAttribute('index')
    // bug1: index = num
       num = index
    // bug2: index = circle
       circle = index
    num = index
   var focusWidth = focus.offsetWidth
   animate(ul, -index * focusWidth, 15)
 })

7.左侧按钮的实现

左侧按钮的实现和右侧按钮实现原理一样,但需要判断图片和小圆圈的临界情况。

 arrow_l.addEventListener('click', function () {
  // 如果是第一张图片,点击左侧按钮后,我们需要ul快速跳到最后一张图片
  if (num == 0) {
    num = ul.children.length - 1
    ul.style.left = -num * focusWidth + 'px';
  }
  num--
  var focusWidth = focus.offsetWidth
  animate(ul, -num * focusWidth, 15)

  // 点击左侧按钮,小圆圈一起变化
  circle--
  // 如果circle < 0 说明走到了第一张图片, 则小圆圈要改为第4个小圆圈 索引值为3
  circle = circle < 0 ? ol.children.length - 1 : circle
  circleChange()
})

8.自动播放功能

思路:

1.利用定时器setInterval()

2.自动播放功能,类似于点击了右侧按钮

3.此时可以使用手动调用右侧按钮点击事件 arrow_r.click()

   var timer = setInterval(function() {
   // 手动调用点击事件
   arrow_r.click()
},2000)

4.鼠标经过focus就停止定时器

5.鼠标经过focus就开启定时器

  focus.addEventListener('mouseenter', function () {
   arrow_l.style.display = 'block'
   arrow_r.style.display = 'block'
   // 鼠标经过,关闭定时器
   clearInterval(timer)
   timer = null // 清除定时器变量
});
  focus.addEventListener('mouseleave', function () {
   arrow_l.style.display = 'none'
   arrow_r.style.display = 'none'
   // 鼠标离开,开启定时器
   timer = setInterval(function() {
    // 手动调用点击事件
    arrow_r.click()
 },2000)
});

9.节流阀

作用:防止轮播图连续点击左右按钮造成播放过快。

思路:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续点击

实现:利用回调函数,添加一个变量flag来控制,锁住函数和解锁函数

var flag = true
arrow_r.addEventListener('click', function () {
  if (flag) {
    flag = false  // 关闭节流阀
    if (num == ul.children.length - 1) {
      ul.style.left = 0
      num = 0
    }
    num++
    var focusWidth = focus.offsetWidth
    animate(ul, -num * focusWidth, 15,  ()=> {
       flag = true  // 利用回调函数,等函数执行完成后,打开节流阀
    })
    circle++
    circle = circle == ol.children.length ? circle = 0 : circle
    circleChange()
  }
})

总结

好了,以上就是实现轮播图的所有功能了,这个轮播图的功能算是比较完整的,大家通过学习可以复习到很多原生dom和bom的操作,包括定时器的使用,js的基础知识等等。可能对于刚接触前端的小伙伴会觉得难懂,可以按照上面的思路,把轮播图的功能拆分成一个个小模块来实现,自己多敲几遍,相信你一定可以学会滴,加油鸭! 以下附上完整代码:

var arrow_l = document.querySelector('.arrow-l');
var arrow_r = document.querySelector('.arrow-r');
var focus = document.querySelector('.focus')

// 1. 鼠标经过focus 就显示隐藏左右按钮
focus.addEventListener('mouseenter', function () {
  arrow_l.style.display = 'block'
  arrow_r.style.display = 'block'
  // 鼠标经过,关闭定时器
  clearInterval(timer)
  timer = null // 清除定时器变量
});
focus.addEventListener('mouseleave', function () {
  arrow_l.style.display = 'none'
  arrow_r.style.display = 'none'
  // 鼠标离开,开启定时器
  timer = setInterval(function () {
    // 手动调用点击事件
    arrow_r.click()
  }, 2000)
});

// 2.动态生成小圆圈,有几张图片,就生成几个小圆圈
var ul = focus.querySelector('ul')
var ol = focus.querySelector('.circle')
// console.log(ul.children.length);
for (let i = 0; i < ul.children.length; i++) {
  // 创建一个li
  var li = document.createElement('li')
  // 记录当前小圆圈的索引号,通过自定义属性来做
  li.setAttribute('index', i)
  // 把li插入到ol里面
  ol.appendChild(li)
  // 点击切换小圆圈底色:点击当前小圆圈,就添加current类,其余的小圆圈移除这个current类
  //在生成小圆点的同时直接绑定点击事件
  li.addEventListener('click', function () {
    // 先把所有的li清除current类名
    for (let i = 0; i < ol.children.length; i++) {
      ol.children[i].className = ''
    }
    // 把当前的点击的li 设置为current类名
    this.className = 'current'
    // 3.点击小圆圈,移动图片 移动的是ul
    // ul的移动距离 = 小圆圈索引号 * 图片的宽度 
    // 获取当前li的索引号
    var index = this.getAttribute('index')
    // bug1: index = num
    num = index
    // bug2: index = circle
    circle = index
    // 图片的宽度 == foucus的宽度
    var focusWidth = focus.offsetWidth
    animate(ul, -index * focusWidth, 15)
  })
}
  // 把ol里面的第一个li设置类名为current
    ol.children[0].className = 'current'

  // 克隆第一张图片,放到ul最后面
    var first = ul.children[0].cloneNode(true)
    ul.appendChild(first)
  // 4.点击左侧按钮,图片滚动一张
  // 控制图片的移动
    var num = 0
  // 控制小圆圈的移动
    var circle = 0
  // 节流阀
    var flag = true
  // 右侧按钮实现
 arrow_r.addEventListener('click', function () {
  if (flag) {
    flag = false  // 关闭节流阀
    // 如果走到了最后复制的一张图片,此时我们的ul要快速复原left = 0
    if (num == ul.children.length - 1) {
      ul.style.left = 0
      num = 0
    }
    num++
    var focusWidth = focus.offsetWidth
    animate(ul, -num * focusWidth, 15, () => {
      flag = true  // 利用回调函数,等函数执行完成后,打开节流阀
    })
    // 5.点击右侧按钮,小圆圈一起变化
    circle++
    // 如果circle == 4 说明走到了最后我们克隆的这张图片了, 我们就复原
    circle = circle == ol.children.length ? circle = 0 : circle
    circleChange()
  }
})


// 7. 左侧侧按钮实现
 arrow_l.addEventListener('click', function () {
  if (flag) {
    flag = false;
    // 如果是第一张图片,点击左侧按钮后,我们需要ul快速跳到最后一张图片
    if (num == 0) {
      num = ul.children.length - 1
      ul.style.left = -num * focusWidth + 'px';
    }
    num--
    var focusWidth = focus.offsetWidth
    animate(ul, -num * focusWidth, 15, () => {
      flag = true;
    })
    // 5.点击右侧按钮,小圆圈一起变化
    circle--
    // 如果circle < 0 说明走到了第一张图片, 则小圆圈要改为第4个小圆圈 索引值为3
    circle = circle < 0 ? ol.children.length - 1 : circle
    circleChange()
  }
})

// 将左右按钮相同的部分进行函数封装
function circleChange() {
  // 先清除其余小圆圈的current类名
  for (var i = 0; i < ol.children.length; i++) {
    ol.children[i].className = ''
  }
  // 再留下当前小圆圈的current类名
  ol.children[circle].className = 'current'
}

// 8.自动播放功能
var timer = setInterval(function () {
  // 手动调用点击事件
  arrow_r.click()
}, 2000)