[原]js实现无缝轮播图插件

看了几个网上的轮播图例子,然后理解了其中的原理,自己写了一个插件,实现了无缝轮播。

先来说一下实现原理:

  • 首先要明确做一个插件的基本要求,我认为至少需要满足以下几点要求:具备默认设置参数的功能;插件自身的作用域与用户的作用域相互独立;用户可以自己调节参数达到自己想要的效果。

  • 然后看一下轮播图的需求。

我们需要实现下图所示的轮播图,它具有以下功能:

A. 每隔一定的时间图片自动播放;

B. 点击对应的圆点跳转到对应的图片;

C. 点击两边的按钮可以播放上一张图或者下一张图;

D. 当鼠标悬停在图片上方时暂停播放。

效果图

  • 然后需要考虑轮播图从何下手。

首先要实现功能A,这里已默认5张图为例,真实的图片肯定不是五张,这个想必大家都已知道,如果后台仍然使用5张图就不可能实现真正的无缝了。而在此我们不仅要实现自动播放,还要实现按钮功能,所以我们还需有第一张图跳转到最后一张图的功能,所以需要7张图片。然后要实现图片轮播的效果就要让图片在一条线上,使用float可以轻松实现(这里要注意,我们整个框架是:div>ul>li>img,div和li,img肯定是固定宽高,而ul就需要内容撑开宽度了,我们不知道图片的数量,如果固定了宽度,就会造成图片不再一条线上);接着要让他动起来,我们首先想到的是通过改变ul的left值来改变图片的显示,当然这里我也用的是left,但是如果使用left会降低效率,不如用translateX()更加高效。不过那个还没有做出来,等后面做出来了再修改一下。自动播放就是把这个功能放在一个setInterval()里面就可以了。

其次要实现功能B,看到这个功能我们想到的是如何将圆点与图片连接起来,还有处理圆点的点击事件。针对前一个问题,我的解决方法是给span(圆点)添加data-index属性,并对其编号,这样我们通过点击获得的编号就可以知道要跳转到哪张图片了。针对后一个问题,我想到了可以给span外面包一层div,然后给div添加点击事件,通过事件冒泡得到事件源(即target属性),然后获取事件源的data-index即可。

然后是功能C,这个就比较简单了,右边的按钮就是触发一次播放的动画,左边的按钮就是反方向触发一次放的动画。

最后是功能D,可以通过hover()清除setInterval(),移除后重新创建一个。

考虑到插件的兼容性,即因为图片数量是未知的,所以小圆点span标签需要动态创建,同时对两个按钮也动态创建。

  • 了解了所有需求以及实现方法就要开始正式写代码了。

先附上结构

<div class="wrapper">
<div class="box">
<ul class="carousel-inner">
<li class="carousel-item"><img src="./images/-jpg" alt=""></li>
<li class="carousel-item"><img src="./images/-jpg" alt=""></li>
<li class="carousel-item"><img src="./images/-jpg" alt=""></li>
<li class="carousel-item"><img src="./images/-jpg" alt=""></li>
<li class="carousel-item"><img src="./images/-jpg" alt=""></li>
<li class="carousel-item"><img src="./images/-jpg" alt=""></li>
<li class="carousel-item"><img src="./images/-jpg" alt=""></li>
</ul>
</div>
</div>

首先我们扩展一个封装函数sowingMap(),并在函数内通过jq的extend方法处理参数,如果用户没有传进来参数就使用默认参数。

(function() {
$.fn.sowingMap = function(option) {
var args = $.extend({
count : 2,//图片数量
time : 3000 //自动播放时间间隔
}, option);
}
})(jQuery);

为了结构看起来清楚明了,我另外创建了一个“构造函数”Init(),并在此函数的原型链上编程。

function Init(ele, args) { //ele:父级节点,args:参数列表
if (args.count > 1) {
this.init(ele, args);
} else {
alert('请输入正确的图片数量')
}
}
Init.prototype = {}

在原型链上,首先写一个入口函数init()处理传进来的参数,并初始化后面需要用到的dom节点,调用主要函数。

init : function(ele, args) {
this.ele = ele,
this.count = args.count,
this.time = args.time,
this.index = 0,
this.oUl = this.ele.find('.carousel-inner'),
this.oLi = this.oUl.find('.carousel-item'),
this.createSpan(), //生成圆点以及左右按钮
this.handleUl(), //处理图片
this.automatic(), //自动播放
this.eventBind() //点击事件
}

然后写一个handleUl()方法,用来处理图片,即复制最后一张图片并将其接在第一位,复制第一张图片并将其接在最后一位。

handleUl : function() {
var last = this.oLi.eq(0).clone(),
first = this.oLi.eq(this.count - 1).clone(),
width = this.ele.width(),
left = - (this.index + 1) * this.ele.width(); //确认第一张图片的位置
this.oUl.prepend(first).append(last).css('left', left + 'px');
this.oUl.width((this.count + 2 ) * width);
}

接着写一个createSpan()方法,用来动态创建span标签以及btn按钮。

createSpan : function() {
var str = '<div class="carousel-indicators">';
for (var i = 0; i < this.count; i ++) {
str += '<span data-index="' + i + '"></span>';
}
str += '</div><span class="carousel-btn carousel-prev-btn"></span>'
+ '<span class="carousel-btn carousel-next-btn"></span>';
this.ele.append(str);
this.ele.find('.carousel-indicators span').eq(this.index).addClass('active')
}

接着就是处理动画了,首先写一个ianimate()方法,用来处理动画,在这里面要注意,当图片动画结束后我们要处理圆点的效果以及图片的衔接问题。

ianimate : function(ileft) {
var tleft = this.oUl.position().left,
i = this;
i.oUl.animate({
left : ileft + tleft
}, function() {
var left = i.oUl.position().left,
width = i.ele.width();
ileft > 0 && left > -width && i.oUl.css('left', - i.count * width); //判断是否是第0张(即最前面的最后一张)
ileft < 0 && left < - i.count * width && i.oUl.css('left', -width); //判断是否是最后一张(即最后面的第一张)
i.index = parseInt(- left / width) - 1;
i.index = i.index > i.count - 1 ? 0 : i.index;
i.index = i.index < 0 ? i.count - 1 : i.index;
i.renBtns(); // 给圆点添加特效
})
}

接着是renBtns()方法,通过给对应的span添加类名来改变样式。

renBtns : function() {
this.ele.find('.carousel-indicators span').removeClass("active").eq(this.index).addClass('active')
}

当动画做好后就要去触发动画了,写一个eventBind()方法来放置圆点以及按钮的点击事件还有外层div的hover事件。

eventBind : function() {
var i = this,
prev = this.ele.find(".carousel-prev-btn"),
next = this.ele.find(".carousel-next-btn"),
span = this.ele.find(".carousel-indicators"),
ileft = this.ele.width();
prev.on('click', function(event) {
i.ianimate(ileft);
});
next.on('click', function(event) {
i.ianimate(-ileft);
});
span.on('click', function(event) {
var e = event || window.event;
var target = e.target || e.srcElement;
i.ianimate(- ($(target).data('index') - i.index) * ileft);
});
i.ele.hover(function() {
clearInterval(i.timer);
}, function() {
i.automatic();
})
}

要处理外层div的hover事件,自然先要写一个方法automatic()用来触发动画自动播放。

automatic : function() {
var next = this.ele.find(".carousel-next-btn");
this.timer = setInterval(function() {
next.trigger('click')
}, this.time)
}

为了保证动画的流畅程度,在动画开始之前需要加一个判断,判断现在是否有正在进行的动画,刚开始我想到了加锁,但发现不是很好操作,于是查了一下文档,发现可以通过jq内置的is()方法来判断当前是否有动画在进行,如下。

if(!i.oUl.is(":animated")) {
//动画
}

最后我们只需要引用此插件并调用就可以了

$('.box').sowingMap({
count : 7,
time : 3000
});

至此,主要功能以及编写完毕,只需要在处理一下细节,一个轮播图插件就做好了。

文章作者: JaCo Wu
文章链接: https://jacokwu.cn/blog/2018/06/05/原-js实现无缝轮播图插件/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 JaCo Wu的博客