目录

I. 轮播图的展示与原理概述

📖 京剧电视机成品展示

📖 轮播图总体原理

📖 多张图放置原理

📖 平滑滚动原理

📖 定时与点击滚动原理

📖 首尾无缝衔接原理

📖 防鼠标连续点击原理

II. 封装动画函数

III. 实现定时和点击滚动

IV. 处理第一张图和最后一张图的无缝衔接

V. 处理鼠标快速点击事件

VI. 推广案例


【写在最前】

如果你是在找轮播图怎么做的小白,那么恭喜你,找到我这儿,不用再换了认认真真看完这篇文章,我保证你能学会做一个完整的轮播图,这个轮播图包括但不限于以下功能

平滑滚动、定时滚动 + 点击滚动、防鼠标连续点击、第一张图与最后一张图无缝处理

而且我会以面向小白的简单通俗讲解,带大家做完轮播图,保证全程没有盲点。

但也有知识基础:有简单的jQuery基础、会用setTimeout()和setInterval()函数,这两个要求可以在我的其他博客里学习到。

最后,源码太长,所以完整的源码和素材在评论区置顶,大家到评论区自取即可。

I. 轮播图的展示与原理概述

📖 京剧电视机成品展示

首先,与其他教你做轮播图的博文的案例不同,我不打算直接用一个轮播图例子来做案例,而是用一个和轮播图原理完全相同、有些地方更复杂一些的【京剧电视机】例子作案例,这个案例是结合了我国传统文化与前端技术的产物,大家请先看效果

1️⃣ 开机演示:


2️⃣ 关机演示:


3️⃣ 点击滑动演示:

展示效果不是为了炫技,而是想要让大家放心,这些效果我是可以实现并讲解给大家的;上面的这些效果,以及一些没有展示的效果(防鼠标重复点击、首尾图片无缝衔接),我都会在后面给大家一一讲解实现的逻辑。

在这部分最后,我们先展示一下HTML和css部分以便大家在后面能根据布局的代码,了解到每一个元素的id、class,方便理解js的逻辑:

<head>
<style>
    body {
        background-color: #faf1dd;
    }
    .tv {
        margin: 80px auto;
        position: relative;
        width: 469px;
        height: 314px;
        background: #dec17d;
        border: 11px solid #b2985b;
        overflow: hidden;
    }
    .screen {
        position: relative;
        top: 16px;
        left: 23px;
        width: 311px;
        height: 266px;
        border-radius: 18px;
        background: #474747;
        border: 8px solid #262626;
        overflow: hidden;
    }
    #container {
        position: absolute;
        width: 3110px;
        height: 266px;
        z-index: 1;
    }
    #container img {
        float: left;
        width: 311px;
        height: 266px;
    }
    .knob {
        position: absolute;
        width: 35px;
        height: 35px;
        border-radius: 35px;
        background: #b2985b;
        box-shadow: 0 1px #3d3d3d;
    }
    #next {
        cursor: pointer;
        top: 31px;
        right: 43px;
    }
    #pre {
        cursor: pointer;
        top: 88px;
        right: 43px;
    }
    .power-button {
        cursor: pointer;
        position: absolute;
        bottom: 27px;
        right: 50px;
        width: 20px;
        height: 20px;
        background: #ef5754;
        box-shadow: 0 1px #3d3d3d;
        border-radius: 20px;
    }
</style>
</head>

<body>
<div class="tv">
    <div class="screen">
        <div class="wrap" id="container">
            <img src="./img/end.jpg" alt="">
            <img src="./img/start.jpg" alt="">
            <img src="./img/p6.jpg" alt="">
            <img src="./img/p1.jpg" alt="">
            <img src="./img/p2.jpg" alt="">
            <img src="./img/p3.jpg" alt="">
            <img src="./img/p4.jpg" alt="">
            <img src="./img/p5.jpg" alt="">
            <img src="./img/p6.jpg" alt="">
            <img src="./img/p1.jpg" alt="">
        </div>
    </div>
    <div class="knob" id="next"></div>
    <div class="knob" id="pre"></div>
    <div class="power-button" id="pw-btn"></div>
</div>
</body>

然后我们再放一张图,通过这张图,大家再熟悉一下案例中所有HTML元素的类名、id名以及它们之间的结构等内容,后面我们讲实现的时候,这些名字就不再重复解释了,大家可以对照这张图去看:


📖 轮播图总体原理

首先,与同类文章相似,我们简单的了解一下轮播图的总体原理,它的总体原理可以用一张图来表示:

其中,外部有一个很大的盒子,盒子里面是轮播图的所有图片,例如共有五张轮播图,里面理论上应该有五张图片,所以外部盒子的宽度要设置成5倍单张图片的宽度里面的小盒子,是最终显示给用户看的盒子,在这个小盒子外部的其他内容都被隐藏了所以我们能看到只有一张图,但实际上摆放了很多张图

而我们要做的第一步是在大盒子里放置这五张图片,之后,我们利用一些其他的方式,让五张图中每一张按照我们的心意,移动到小盒子里,这就是轮播图的总体原理,下面分块介绍细节原理。


📖 多张图放置原理

首先,我们要先知道怎么把五张图放进去,这个其实很简单,如果css学的不错的小伙伴应该一眼就看出来,我们可以用浮动,让五张图排列在外部大盒子里。

接下来,我们需要让某一张显示出来,这个实现的方法不唯一,但是比较通用的是,给大盒子设置成绝对定位,给小盒子设置成相对定位,同时小盒子是大盒子的父元素,于是有:

大盒子因为绝对定位,脱标后,不占空间,小盒子相对定位,占有空间;

于是给小盒子设置一个overflow:hidden,即可满足我们的需求。

<style>
 .screen {
    position: relative;
    top: 16px;
    left: 23px;
    width: 311px;
    height: 266px;
    border-radius: 18px;
    background: #474747;
    border: 8px solid #262626;
    overflow: hidden;
}

 #container {
    position: absolute;
    width: 3110px;
    height: 266px;
    z-index: 1;
}
</style>

<div class="screen">
    <div class="wrap" id="container">
        <img src="./img/end.jpg" alt="">
        <img src="./img/start.jpg" alt="">
        <img src="./img/p6.jpg" alt="">
        <img src="./img/p1.jpg" alt="">
        <img src="./img/p2.jpg" alt="">
        <img src="./img/p3.jpg" alt="">
        <img src="./img/p4.jpg" alt="">
        <img src="./img/p5.jpg" alt="">
        <img src="./img/p6.jpg" alt="">
        <img src="./img/p1.jpg" alt="">
    </div>
</div>

这样就完成了图片放置的原理介绍。


📖 平滑滚动原理

聊平滑滚动之前,我们先聊聊怎么把这几张图片切换进小盒子吧,这其实特别简单,我们只需要调整外部大容器css样式left即可:

但是能不能做到平滑滚动,其实是新手和老手的第一道分水岭了,新手可能通过周期性调整left值,觉得自己做出了轮播图,其实这么做应该叫"轮切图",因为你不是播放的,是切换的图片,那平滑性,要如何实现呢?

这就需要我们通过封装一个动画函数,来实现运行的平滑性!

这部分,我们在后面会讲解具体的封装过程。


📖 定时与点击滚动原理

定时、点击滚动,其实算不上有什么原理,但我们也要实现它。

实现的思路是给整个过程设置一个定时器,让定时器定时地执行轮播;再给前进/后退按钮绑定对应的切换函数,即可完成这一步,因此这一步难度并不大(相比其他步骤)。


📖 首尾无缝衔接原理

首尾无缝衔接的实现,是第二道分水岭,那首尾无缝衔接,到底是什么意思呢?我相信很多人一直没有搞明白,那我继续用图片给大家解释

请看,当我们的大盒子继续移动,使得最后一张图呈现在小盒子里之后,下一个时刻,我们知道它应该回到第一张图了。那么,传统的做法是:

判断是否是最后一张,如果是最后一张图,那么在播完最后一张图后,滑动到第一张图。

这么做理论上是没有任何问题的,但是当你实际操作的时候,你会发现一个很尴尬的问题,我们演示一下尴尬在哪里

看出来了吗?

当我们从最后一张图切换到第一张的时候,尽管还是平滑的,但是由于距离太长了(从末尾的left值到开头的left值,是很长的),所以看到了一串连续的回滚,这就很不理想了,因为我们想要的是和正向播放一样的滚动效果,而不是上面的这种回滚。

于是这就是无缝衔接的诞生原因了,我们可以这么实现无缝衔接

我们在大容器的首位和最后一位,分别额外放置最后一张图和第一张图,也就是说,我们的大盒子里放的图片是这样的次序:

这样做之后,当我们每一次到达图片4时,我们要做如下两件事

(1) 让大盒子继续正常的向前滑动,滚到"第一张图",就和前面的滑动一致

(2) 滑动结束后,立刻把大盒子的left值设置为显示第一张图的left值,注意,这里不是用动画滚动到第一张图的left值,是瞬间修改!否则就无效了

为什么这样做,就看起来无缝了呢?

其实这是人眼的欺诈性,我们在看到它滚动到大盒子的最后那个位置,也就是第一张图之后,此时让大盒子的left值做瞬间的修改,由于视觉的短暂停留,我们的视觉上还是第一张图,我们是看不到这中间的修改的,于是视觉上,我们认为是平滑的。


📖 防鼠标连续点击原理

最后,防鼠标重复点击,防止的是重复点击前进、后退的按键,那么它的实现主要基于两个角度共同作用

(1) 当我们点击了一次前进或后退按钮之后,首先解除外部的定时器和前进、后退按钮的时间绑定。

(2) 当该次动画结束后,在动画的回调函数中恢复定时器和事件绑定。

这部分可能暂时你看不懂,这是因为我们没有封装动画函数,等动画函数封装后,你就能明白这其中的道理,于是我们过渡到下一部分,开始解释动画函数。


II. 封装动画函数

关于动画函数封装的讲解,我不想循规蹈矩的讲思路,而是直接给大家上代码,而后通过代码分析原理

function animate(obj, target, callback) {
    clearInterval(obj);
    obj.timer = setInterval(function () {
        var step = (target - parseInt(obj.css("left"))) / 10;
        console.log(obj.css("left"))
        console.log(step)
        step = step > 0 ? Math.ceil(step) : Math.floor(step);
        if (parseInt(obj.css("left")) == target) {
            clearInterval(obj.timer);
            if (callback) {
                callback();
            }
        }
        obj.css("left", parseInt(obj.css("left")) + step + "px");
    }, 30)
}

代码展示之后,我们从代码里面研究动画函数到底干了啥事

首先,参数有三个,分别是obj,target和callback,我们先一笔带过callback,它就是一个函数,这个函数会在整个动画执行完毕之后调用,因此不需要管这个参数;再一笔带过第一个参数,obj,它就是需要被执行动画的对象,在本例子里,就是外部的大盒子是obj

target我们不能一笔带过了,它的意思是我们需要的目标位置,这个位置是从left样式的角度给出的,举个例子,例如我们想要大盒子移动到left值为-1000px,那么我们的target传入的参数值就是-1000

参数说完了,我们看看是怎么实现的平滑滚动

其实是利用了之前我们讲过的setInterval函数,以30ms的速度,让left值进行不断的修改逼近目标值,而30ms由于很短,所以我们认为是在连续的运动,但其实有30ms的间隔,只不过这个间隔我们是看不出来的。

其中,还有一个细节是:

var step = (target - parseInt(obj.css("left"))) / 10;

这里有人有看不懂了,说不对啊,难道不应该是等分当前位置与目标位置,而后按照等分的距离移动吗?为什么每一部分的距离要通过这个式子去计算,其实这里涉及到一个逐渐变慢的移动,也就是减速运动,具体的原理,由于不是我们本次讲解的重点,大家可以先不用过度理解,先拿去用

总之,通过上面封装的动画函数,我们传入一个目标left值,它就可以平滑的移动到目的left值。这个动画函数,大家也可以下载下来保存一下,以后做项目的时候可以直接导入这个函数,就不用再次编码。(这里由于篇幅原因,动画函数的封装我说的不是很细,但是后面会专门出一期文章给大家讲解匀速运动和减速运动的原理)


III. 实现定时和点击滚动

定时和点击滚动,其实是比较简单的部分,但是我还是给大家说一下:

首先是定时,定时其实可以用一个定时器来实现,实现的思路是,给定时器设置一个周期,这个周期就是最终轮播的周期注意这个周期要大于执行上面那个减速运动的时间,否则会出现滚动混乱,它的实现是这样的:

var timer = null;
// 自动轮播定时器的封装
function startTimer() {
    timer = setInterval(function () {
        var target = parseInt(container.css("left")) - 311;
        if (target != -2799) {
            // 加解绑定 同下面的解绑
            btn_pre.unbind("click", display_pre);
            btn_next.unbind("click", display_next);
            animate(container, target, function () {
                btn_pre.bind("click", display_pre);
                btn_next.bind("click", display_next);
            });
        }
        else {
            btn_pre.unbind("click", display_pre);
            btn_next.unbind("click", display_next);
            animate(container, target, function () {
                container.css("left", "-933px");
                btn_pre.bind("click", display_pre);
                btn_next.bind("click", display_next);
            }
        }
    }, 1800);
}

其中1800毫秒是通过计算,在本例中能够满足执行动画后还有一小段空闲的一个时间,大家做项目的时候,可自行测试一下动画的执行时间,并相应调整这个时间。

前进和后退按键的事件封装和绑定类似于定时器,是这样实现的:

// 封装上一幕、下一幕的函数
function display_next()
    var target = parseInt(container.css("left")) - 311;
    stopTimer();
    // 暂时解绑定上一幕和下一幕按钮,防止多次点击
    btn_next.unbind("click", display_next);
    btn_pre.unbind("click", display_pre);
    if (target != -2799) {
        animate(container, target, function () {
            btn_pre.bind("click", display_pre);
            btn_next.bind("click", display_next);
            startTimer();
        });
    }
    else {
        animate(container, target, function () {
            btn_pre.bind("click", display_pre);
            btn_next.bind("click", display_next);
            container.css("left", "-933px");
            startTimer();
        });
    }

function display_pre() {
    var target = parseInt(container.css("left")) + 311;
    // 停止外圈的活动
    stopTimer();
    // 暂时解绑定上一幕和下一幕按钮,防止多次点击
    btn_pre.unbind("click", display_pre);
    btn_next.unbind("click", display_next);
    if (target != -622) {
        animate(container, target, function () {
            btn_pre.bind("click", display_pre);
            btn_pre.bind("click", display_next);
            startTimer();
        });
    }
    else {
        animate(container, target, function () {
            container.css("left", "-2488px");
            btn_pre.bind("click", display_pre);
            btn_pre.bind("click", display_next);
            startTimer();
        });
    }

 }

IV. 处理第一张图和最后一张图的无缝衔接

第一张图和最后一张图的无缝衔接,原理刚才讲给大家听了,那么代码层面上,是这样实现的:

if (target != -2799) {
    animate(container, target, function () {
        btn_pre.bind("click", display_pre);
        btn_next.bind("click", display_next);
        startTimer();
    });
}
else {
    animate(container, target, function () {
        btn_pre.bind("click", display_pre);
        btn_next.bind("click", display_next);
        container.css("left", "-933px");
        startTimer();
    });

通过判定,最后一张的时候,首先执行动画,并在回调函数中,以迅雷不及掩耳之速度,把left值修改,这样就用人眼的视觉停留,形成了无缝衔接!


V. 处理鼠标快速点击事件

最后,我们要处理一下鼠标快速点击前进后退按钮事件,当然了如果单纯说轮播图,其实已经给大家讲完了,但是我觉得有必要再补充一下这部分,否则鼠标重复点击,会造成轮播的混乱(堤防熊孩子!)

我们按照之前原理分析那样,把防止重复点击分成两部分解决:

// 停止外圈的活动
stopTimer();
// 暂时解绑定上一幕和下一幕按钮,防止多次点击
btn_pre.unbind("click", display_pre);
btn_next.unbind("click", display_next);
if (target != -622) {
    animate(container, target, function () {
        btn_pre.bind("click", display_pre);
        btn_pre.bind("click", display_next);
        startTimer();
    });
}
else {
    animate(container, target, function () {
        container.css("left", "-2488px");
        btn_pre.bind("click", display_pre);
        btn_pre.bind("click", display_next);
        startTimer();
    });
}

当我们点击了按钮,立刻停掉定时器,同时给按钮本身解绑;而后在回调函数中,恢复定时器和绑定

另外为了防止点击的同时,刚好轮播图也在因定时器的执行而切换图片,造成混乱,我们给最外面的定时器也做一下处理:

if (target != -2799) {
    // 加解绑定 同下面的解绑
    btn_pre.unbind("click", display_pre);
    btn_next.unbind("click", display_next);
    animate(container, target, function () {
        btn_pre.bind("click", display_pre);
        btn_next.bind("click", display_next);
    });
}
else {
    btn_pre.unbind("click", display_pre);
    btn_next.unbind("click", display_next);
    animate(container, target, function () {
        container.css("left", "-933px");
        btn_pre.bind("click", display_pre);
        btn_next.bind("click", display_next);
    });

这样,当每一次轮播图要自己切换的时候,它会把按键解绑,防止两个时间发生冲突


VI. 推广案例

最后,我们聊聊这个案例可以怎么推广,我给出如下的思路:

(1) 可以修改成传统轮播图,用作轮播图。(缺点是把这个很好的小电视创意给弄没了)

(2) 可以不修改,只是把图片换一下,放在自己的网站当一个装饰物,图片可以换成自己的个人介绍,这样显得很拉风不是吗?哈哈哈

(3) 可以把里面的图片换成你和你的另一半的纪念照片,然后把它送给你的另一半,这不是极好的嘛?

最后的最后,再提醒一下大家,由于完整代码比较长,我已经把代码和素材打包放在评论区置顶了,大家自提即可

好了,以上是本期的全部内容,喜欢的小伙伴们可以三连支持一下!💓💓💓!(尤其是提取了资源的小伙伴,不三连等着过年呀哈哈哈)

 另外本期博客参与了新星计划】,还请大家支持一下🌟🌟🌟

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐