一步一步教你手写轮播图

2,386 阅读4分钟

效果 : Rock Slider

效果图

结构

以上图为例,整个轮播图我们把他放在一个classsliderdiv里,可以看到整个轮播由三个部分构成:

  • 图片部分
  • 前进、后退按钮
  • 小圆点

几张图片就放几个img标签,前进后退按钮可以用a标签,注意把默认事件去掉,小圆点可以用个无序列表,根据以上构想,可以写出如下html代码

<div class="slider">
      <img class="fade" src="./img/1.jpg" alt="" />
      <img class="fade" src="./img/2.jpg" alt="" />
      <img class="fade" src="./img/3.jpg" alt="" />
      <img class="fade" src="./img/4.jpg" alt="" />

      <a class="prev" onclick="prev()">&lt;</a>
      <a class="next" onclick="next()">&gt;</a>

      <ul class="nav">
        <li class="dot selected" onclick="turnToSlide(0)"></li>
        <li class="dot" onclick="turnToSlide(1)"></li>
        <li class="dot" onclick="turnToSlide(2)"></li>
        <li class="dot" onclick="turnToSlide(3)"></li>
      </ul>
</div>

解释一下类名,imgfade是用来写切换时的过度动画,prevnext是写前进后退的样式,nav是控制无序列表排列方式,dot是每个小圆点未选中的样式,selected是选中后的样式。

布局

个人感觉轮播图的逻辑不是很难,难就难在布局上

这里要用到一个思想,叫“子绝父相”,意思是父亲的position属性为relative,儿子的positionabsolute,然后再来调整儿子的位置就很方便了,所以我们将sliderposition设为relative,其直系子元素的position设为absolute

很容易看出,前进后退的按钮是靠边缘垂直居中,所以把left/right设为0,然后再垂直居中,给个top属性50%,但是这里的top的距离是以元素的左上角为参考物,所以为了真正的居中我们还要将元素向上平移自身高度的50%,可以使用transform: translateY(-50%);,具体的一些样式细节我就不赘述了,代码都很容易看懂。

然后再来看导航的小圆点,是靠底部垂直居中,那么我们将bottom设一个较小的值,留点距离,然后left:50;,transform: translateX(-50%);,具体的样式可以根据自己的喜好调整。

代码如下:

    .slider {
        top: 50%;
        left: 50%;
        transform: translate(-50%);
        width: 60vw;
        position: relative;
      }

      @media screen and (max-width: 900px) {
        .slider {
          width: 100vw;
        }
      }

      .slider img {
        display: none;
        width: 100%;
        border-radius: 4px;
      }
      .prev,
      .next {
        position: absolute;
        top: 50%;
        width: 20px;
        height: 30px;
        transform: translateY(-50%);
        color: white;
        font-size: 24px;
        font-weight: bold;
        cursor: pointer;
        padding: 10px;
        user-select: none;
        transition: 0.6s ease;
      }
      .prev {
        border-radius: 0 3px 3px 0;
        left: 0;
      }
      .next {
        right: 0;
        border-radius: 3px 0 0 3px;
      }

      .prev:hover,
      .next:hover {
        background-color: rgba(0, 0, 0, 0.8);
      }

      .nav {
        position: absolute;
        width: 12vmax;
        left: 50%;
        transform: translateX(-50%);
        display: flex;
        justify-content: space-evenly;
        bottom: 1vmin;
      }
      .dot {
        list-style-type: none;
        cursor: pointer;
        background-color: #fff;
        width: 1.5vmax;
        height: 1.5vmax;
        border-radius: 50%;
        transition: background-color 0.6s ease;
      }
      .selected {
        background-color: #ff5000;
      }

      .fade {
        animation-name: fade;
        animation-duration: 1.5s;
        -webkit-animation-name: fade;
        -webkit-animation-duration: 1.5s;
      }

      @keyframes fade {
        from {
          opacity: 0.4;
        }
        to {
          opacity: 1;
        }
      }

      @-webkit-keyframes fade {
        from {
          opacity: 0.4;
        }
        to {
          opacity: 1;
        }
      }

这里值得一提的是:为了手机用户的体验,做了简单的移动端适配,用了媒体查询和响应式单位。

至于@keyframes动画,有不理解的可以去MDN上看看文档。

动作

最后来看看逻辑的部分。

初始化的时候,将每个图片的display属性设为none,然后播放到哪一张图片将其display设为block。页面载入的时候播放第一张图片。

要实现前进、后退、点击小圆点显示指定图片这三个功能,我们至少要实现nextprevturnToSlide这三个函数,这三个的逻辑都是点击后播放指定的图片,那么我们可以考虑实现一个播放的函数showSlide,根据图片的index来播放,然后因为轮播图是有自动播放的功能,我们要考虑在里面加个定时器(使用了定时器要注意清空),实现定时播放。

根据以上设计,我们写出如下代码

    function Slider () {
        let slideIndex = 0
        let timer,
          flag = 0

        function deleteTimer () {
          clearTimeout(timer)
          timer = null
        }

        function next () {
          slideIndex++
          showSlide()
        }

        function turnToSlide (index) {
          if (index === slideIndex) return
          slideIndex = index
          showSlide()
        }
        function prev () {
          slideIndex--
          showSlide()
        }

        function showSlide () {
          deleteTimer()
          const slides = document.querySelectorAll('img')
          const dots = document.querySelectorAll('.dot')
          if (slideIndex >= slides.length) {
            slideIndex = 0
          }
          if (slideIndex < 0) {
            slideIndex = slides.length - 1
          }
		  
          slides[flag].style.display = 'none'
          dots[flag].classList.remove('selected')

          slides[slideIndex].style.display = 'block'
          dots[slideIndex].classList.add('selected')

          flag = slideIndex

          timer = setTimeout(next, 5000)
        }
        return {
          prev,
          next,
          showSlide,
          turnToSlide
        }
      }
      const slider = Slider()
      slider.showSlide()

为了防止全局变量污染,我把整个轮播图相关的方法和变量放在了一个Slider的的函数里,然后只暴露四个要被调用的方法。

在图片切换方面,目前主流写法都是使用for循环将所有的图片的display设为none再将要播放的图片的display设为block。可以这样做,但没有必要。我们可以借助一个标记变量flag来记录上次播放的图片的index,这样我在播放新图片的时候将indexflag的图片的display设为none,要播放的图片的display设为block就行了。

源码地址: 7sings/Slider: a simple slider writen with html,css and javascript (github.com)

后记

2021年9月7日:优化了移动端用户体验,在触摸屏设备上,隐藏了前进后退的点击按钮,通过左滑(加载下一张),右滑(加载前一张)实现了前进后退功能。由于本人比较懒,代码就没有在文章里更新,更新后的代码见上方仓库地址。

To be continued...