手撸一个Carousel 走马灯组件(一)

716 阅读3分钟

一、前言

手撸系列又来啦,这次我们实现的是carousel组件(又叫轮播图),这个组件的使用率非常的高,这次我们就来撸他。

二、效果

carousel1.gif

三、实现的功能清单

  • 手动切换
  • 自动切换(下篇再讲)

四、逐个击破

4.1、静态界面

在做切换的功能之前,我们先来分析一下它的静态界面:

carousel2.png

由上图我们可知,整个组件其实就分为2个部分:

  • 内容组件
  • 活动指示器

现在整体的框架已经出来了,接下来就是一步一步先把静态页面搭起来。

<style>
    .carousel-content-list {
        width: fit-content;
        height: fit-content;
        display: flex;
        box-sizing: border-box;
    }
    .carousel-box-item {
        width: 200px;
        height: 100px;
        display: flex;
        flex-shrink: 0;
        align-items: center;
        justify-content: center;
        background-color: rgb(54, 77, 121);
        color: rgb(255, 255, 255);
        box-sizing: border-box;
    }
</style>

<div class='carousel-box'>                  <!-- 组件最外围的box -->
    <div class="carousel-content-list">     <!-- 存放轮播图的box -->
        <div class="carousel-box-item carousel-box-one">1</div>
        <div class="carousel-box-item carousel-box-two">2</div>
        <div class="carousel-box-item carousel-box-three">3</div>
    </div>
</div>

此时的静态效果应该是这样的:

carousel1.png

接下来我们来处理活动指示器。活动指示器有个特点,就是轮播图无论如何切换,活动指示器的位置都不变

说到位置不变,我们可以使用绝对定位来做到这样的功能。

carousel3.png

代码如下:

<div class='carousel-box'>                  <!-- 组件最外围的box -->
    <div class="carousel-content-list">     <!-- 存放轮播图的box -->
        <div class="carousel-box-item carousel-box-one">1</div>
        <div class="carousel-box-item carousel-box-two">2</div>
        <div class="carousel-box-item carousel-box-three">3</div>
    </div>
    <div class="carousel-slick">             <!-- 活动指示器的box -->
        <div class="carousel-dot carousel-dot-one"></div>
        <div class="carousel-dot carousel-dot-two"></div>
        <div class="carousel-dot carousel-dot-three"></div>
    </div>
</div>

<style>
    .carousel-box {
        position: relative;
        width: fit-content;
        height: fit-content;
        box-sizing: border-box;
    }
    .carousel-content-list {
        width: fit-content;
        height: fit-content;
        display: flex;
        box-sizing: border-box;
    }
    .carousel-box-item {
        width: 200px;
        height: 100px;
        display: flex;
        flex-shrink: 0;
        align-items: center;
        justify-content: center;
        background-color: rgb(54, 77, 121);
        color: rgb(255, 255, 255);
        box-sizing: border-box;
    }
    .carousel-slick {
        z-index: 100;
        left: 0px;
        right: 0px;
        bottom: 12px;
        position: absolute;
        display: flex;
        justify-content: center;
        gap: 5px;
    }
    .carousel-dot {
        width: 16px;
        height: 3px;
        background-color: #fff;
        opacity: 0.3;
        box-sizing: border-box;
        transition: all .3s;
    }
</style>

现在的效果大致是这样:

carousel4.png

现在我们要处理一下轮播图显示的数量,一个轮播图组件每次应该只显示一张图,根据我们上面的div结构可知,carousel-content-list 元素的宽度是fit-content[也就是宽度等于图片数量宽度之和],那么我们只需要在 carousel-content-list 元素的外围加上一个类名为 carousel-content 的元素,然后让这个元素的宽度是一张图片的宽度,overflow置为hidden即可。代码如下:

<div class="carousel-box">
    <div class="carousel-content">
        <div class="carousel-content-list">
            <div class="carousel-box-item carousel-box-one">1</div>
            <div class="carousel-box-item carousel-box-two">2</div>
            <div class="carousel-box-item carousel-box-three">3</div>
        </div>
    </div>
    <div class="carousel-slick">
        <div class="carousel-dot carousel-dot-one"></div>
        <div class="carousel-dot carousel-dot-two"></div>
        <div class="carousel-dot carousel-dot-three"></div>
    </div>
</div>

<style>
    // ...其他样式不变
    .carousel-content {
        width: 200px;
        height: 100px;
        box-sizing: border-box;
        overflow: hidden;
    }
    .carousel-box-item {
        width: 200px;
        height: 100px;
        display: flex;
        flex-shrink: 0;
        align-items: center;
        justify-content: center;
        background-color: rgb(54, 77, 121);
        color: rgb(255, 255, 255);
        box-sizing: border-box;
    }
</style>

到目前为止,我们的静态页面已经写完,样子如下:

carousel5.png

4.2、手动切换

功能点如下:

  • 点击相应指示器,切换到相应轮播图
  • 切换过程中,指示器整体的box不变

我们还是来看下整体结构,这样会清晰很多:

<div class="carousel-box">                      <!-- 轮播图整体box -->
    <div class="carousel-content">              <!-- 显示轮播图的窗口,并且位置是固定的 -->
        <div class="carousel-content-list">     <!-- 存放所有的轮播图,切换主要是改变这个元素box的translateX -->
            <div class="carousel-box-item carousel-box-one">1</div>
            <div class="carousel-box-item carousel-box-two">2</div>
            <div class="carousel-box-item carousel-box-three">3</div>
        </div>
    </div>
    <div class="carousel-slick">                 <!-- 活动指示器box -->
        <div class="carousel-dot carousel-dot-one"></div>
        <div class="carousel-dot carousel-dot-two"></div>
        <div class="carousel-dot carousel-dot-three"></div>
    </div>
</div>

实现思路:

carousel2.gif

相信上面的图已经非常明了的解释了手动切换的精髓(就是动态的改变carousel-content-list元素的translateX)。

实现总结:

1、初始化2个变量a(记录当前展示的轮播图的位置)、b(记录当前点击的指示器的位置)。
2、如果 a != b,说明当前要切换轮播图。
3、如果 a < b,说明 即将切换的轮播图的位置一定在后面,carousel-content-list元素一定要向左滑动。
4、如果 a > b, 说明 即将切换的轮播图的位置一定在前面,carousel-content-list元素一定要向右滑动。

代码如下:

let curPanelIndex = 1;          // 当前面板的位置
let curclickedPanelIndex = 1;   // 当前点击的指示器的位置
let allDistance = 0;            // 滑动的总距离
function clickDot(event){
    let target = event.target; // 点击的元素,利用事件委托
    let curClickedElementClassList = Array.from(target.classList); // 当前点击元素的样式集合
    let contentList = document.querySelector('.carousel-content-list');  // 滑动容器
    let dotList = Array.from(document.querySelectorAll('.carousel-dot')); // 指示器集合
    if (curClickedElementClassList.includes('carousel-dot-one')){         // 说明点击的是第一个图片的活动指示器
        if (curPanelIndex == 1){
            return
        }
        curclickedPanelIndex = 1;
    } else if(curClickedElementClassList.includes('carousel-dot-two')){   // 说明点击的是第二个图片的活动指示器
        if (curPanelIndex == 2){
            return
        }
        curclickedPanelIndex = 2;
    } else if (curClickedElementClassList.includes('carousel-dot-three')){
        // three的情况就交给大家了, 哈哈哈
    }
    let distance = (curclickedPanelIndex - curPanelIndex) * 200;
    if (curclickedPanelIndex - curPanelIndex < 0){
        allDistance = allDistance + Math.abs(distance);
        contentList.style.transform = 'translateX(' + allDistance + 'px)';
    } else {
        allDistance = allDistance - Math.abs(distance);
        contentList.style.transform = 'translateX(' + allDistance + 'px)';
    }
    dotList.forEach((item, index) => {
        if (index + 1 == curclickedPanelIndex){
            item.classList.add('carousel-dot-click');
        } else {
            item.classList.remove('carousel-dot-click');
        }
    });
    curPanelIndex = curclickedPanelIndex;
}

html代码改动如下:

<div class="carousel-slick" onclick="clickDot(event)">                 <!-- 活动指示器box -->
    <div class="carousel-dot carousel-dot-one"></div>
    <div class="carousel-dot carousel-dot-two"></div>
    <div class="carousel-dot carousel-dot-three"></div>
</div>

到目前为止,我们就实现了基本的轮播图(手动切换),下一篇我们来讲解自动切换的轮播图。

五、最后

好啦,这次的手撸系列到这里就结束啦。最近建立了一个原生实现组件专栏,目的是带着大家手把手实现市面上的所有组件,欢迎点赞关注。