最近做的一个需求,用到了jQuery FlexSlider
这个插件,本以为一个算是已经比较成熟的插件,用起来应该没什么难度,然而真正用起来才发现,其实还是有些坑的,不过好在这个插件源代码写的比较好,比较好看,所以自己多看看也就能弄明白。
网上那些随便一搜就能找到的你抄我我抄你大家互相抄的东西,我就不继续浪费网络资源和自己的时间了,主要是一些网上很难找到的或者比较模棱两可的点,这里以jQuery FlexSlider v2.6.3
这个版本为例,总结如下。
网上一些提到 flex-slider
插件的方法,基本上都是一带而过,而且模棱两可,我估计当时作者不知从哪里看到然后随手 copy下来的时候,连他自己都不知道自己在写些什么。
一共 7
个回调函数,如下:
start: function(){}
源代码里面标注的解释是:当第一个滑块第一次加载完毕后执行。 说白了就是当此组件初始化(init)完成后,然后又当第一个滑块加载完毕后,此函数执行,在当前slider
组件的整个生命周期中,只执行一遍。before: function(){}
在滑块运动之前的一刻执行,也就是说当你点击切换当前轮播项的时候,此函数会在轮播项开始运动的前一刻执行。after: function(){}
和上一个相反,在滑块运动完成的下一刻执行,也就是说当你点击切换当前轮播项的时候,此函数会在轮播项完成运动的下一刻执行。end: function(){}
当前整个轮播组件滑动到了最后一个滑块的时候执行,与滑块的运动动作异步并行执行,也就是说是在before
和after
函数之间执行,每次到达尾部,此函数都会执行一遍。added: function(){}
当新的滑块被追加到轮播组件中后,就会调用此回调函数。removed: function(){}
当轮播组件中有滑块被删除掉后,此函数会执行init: function() {}
当轮播组件初始化完成后执行,此函数实际上不仅只在开始的时候和执行一遍,如果第一次初始化完后,继续向轮播组件中追加滑块,则此函数还可能会被调用,所以在轮播组件的整个生命周期中,此函数被调用的次数 >=start
函数被调用的次数。
如果轮播组件的数据是异步获取而来,则很显然在数据尚未获得之前,flex-slider
是不能够进行初始化的,否则将会出现异常情况。
对于这种情况,有两种常用的解决手段,一是使用回调函数,二是使用 Promise
首先页面发送获取数据的的请求,当数据成功获取后,再在回调函数中进行 flex-slider
的初始化。
如下示例(为了简化代码,使用到了 Jquery
库):
$(function(){
getData(){
$.getJSON('http://api.example.com&callback=?', (data)=>{
// 这里可以放初始化`flex-slider`的代码
// ...
})
}
})
Promise
初始化组件如果不方便在回调函数中执行 flex-slider
组件的初始化才做,或者希望以同步的写法来进行组件的初始化,那么 Promise
值得一用。
首先依旧是需要首先请求数据,但是回调函数中不执行初始化操作,而是返回一个 Promise
对象:
$(function(){
getData(){
return new Promise((resolve, reject)=> {
$.getJSON('http://api.example.com&callback=?', (data)=>{
// 将获取到的数据当成 `Promise`的参数传回去
resolve(data)
})
})
}
})
然后就能保证在初始化 组件之前获取到数据了:
let promiseResult = getData()
promiseResult.then((data)=>{
// 这里可以放初始化`flex-slider`的代码
// ...
})
当然还可以使用 async
函数来完成异步操作的同步操作,如果需要兼容不支持 ES6
的浏览器,则最后别忘了使用一些工具将 ES6
代码转换成 ES5
的形式。
如果 flex-slider
组件的滑块数量是固定的,而且还不用懒加载,是一次性加载初始化完毕的,那么没什么好说的,但是如果在组件第一次完成初始化后,还将会动态地向组件内增加 slider
,也就是滑块的数量是动态变化的,那么就需要用到 flex-slider
另外的方法了。
需要用到 flex-slider
的 addSlide()
方法,此方法代码很简单:
要做的事情也很清楚:
首先更新组件的滑块数量,然后重新将组件初始化一遍,最后调用
added()
这个回调函数。
从addSlide()
方法中可以看出,动态增加滑块数量,是会引起组件重新初始化的,所以 init:function(){}
回调函数会执行,这也就解释了为什么 init
并不总是只执行一遍的原因。
从上述代码中可以看到,addSlide()
方法其实是放在 slider
对象上的,想用addSlide()
方法,就必须要有 slider
对象才行,有人可以不清楚这个slider
到底是什么鬼,其实这个对象,可以在前面所说的 7个回调函数中获取,因为这个slider
就是那 7个回调函数的参数。
例如:
$("#flexSlider").flexslider({
start(slider): function(){
// 这里就可以使用 addSlide()方法
...
slider.addSlide(obj, pos);
}
})
其他 6个回调函数都有 slider
这个函数,和 start
一样。
至于 addSlide
的两个参数,一个是必选参数obj
,第二个则是可选的。
obj
此参数代表的就是你想要动态追加的滑块HTML
字符串,例如,在被传入
addSlide
方法中后,会被自动包装成Jquery
对象。pos
你可以通过 此参数来选择将动态插入的滑块HTML
插入到原有的一些滑块的什么位置,如果你不传入此参数,则默认追加到原有组件的末尾处。
addSlide
方法的简单改进从 addSlide
的实现方法中可以看到,此方法每次只能动态新增一个滑块,数量每次只能加 1
,如果你想一次性新增多个滑块,则必须多次调用此函数,也就会多次执行对 flex-slider
的初始化操作,这显然很影响性能。
而且据我测试,连续多次调用此函数,flex-slider
的初始化会出现异常,例如无法正确计算出每次滑动的距离,导致组件布局崩溃。
所以我就简单对这个方法改进了一下,让其能够一次性插入多个滑块的DOM
,但是却仅在最后重新初始化一次,如下:
此函数只是将原先每次固定的只能追加一个滑块,变成了能够自由选择数量,其余的一些操作依旧不变。
四个参数中 obj
和 pos
的含义和作用和原先一样,至于 slider
则和上面的 slider
指的是同一个对象,都是那 7个回调函数的参数,这是为了方便通过此对象来调用我们自己重写的 addSlider
方法,number
值得就是你需要新增的滑块数量,默认为 1
。
用法示例如下:
$("#flexSlider").flexslider({
// 这里我们假设当每次组件滑动到最后一个滑块的时候,就追加新的滑块
// 有点类似于懒加载或者预加载的意思
end(slider): function(){
// 假设每一个 `li`元素,就是一个滑块的单位,这里我们一次性追加了三个滑块的 `DOM`
let str = ''
// 调用我们重写的 addSlide() 方法,这里我们一次性新增了三个滑块,
// 所以传入数字 3
addSlide(slider, str, 3);
}
})
如果是理想情况下,新增滑块的数据是通过同步方法一次性顺序获得,那么一切正常。
但不知是不是我用法不太对,如果是通过异步请求数据的方法来取得新增滑块的数据,无论我是通过 flex-slider
提供的 addSlide
方法,正常地每次新增一个滑块,还是使用自己改进的 addSlide
方法来一次性新增多个滑块,亦或是我通过回调函数还是 Promise
的方法来请求异步数据,甚至我把新增滑块的动作,在 7个回调函数中都试了一遍,最终的结果,都是 flex-slider
组件无法正常初始化,无法正常计算每次需要滑动的距离。
我明明想让它每次滑动三个滑块的距离,结果它却滑动了一个半,甚至还给我来了个漂移地来回滑动,导致布局崩溃。
最后折腾了好长时间,试了好多种方法,但好在总算是让我找出了一个还算是好用的方法。
道理很简单,既然如果是同步请求数据来追加新的滑块,组件就能够正常初始化,那么我们就假装让组件中追加的新滑块数据是同步得来的就行了:
先用没有任何内容的滑块
DOM
(例如空的li
标签)来占位,让flex-slider
组件正常新增滑块,这样组件就能正常初始化,能够正确计算每次滑动的距离,一切正常。
然后在此基础上,在稍后获取到异步数据后,将获取到的真正的滑块数据,替换掉之前的占位空滑块,这样对于flex-slider
组件来说,滑块的数量并没有任何变化,也就不会引起其任何的动作,所以替换完成后,组件依旧是正常的。
下面用代码说话。
假设我们异步获取新增滑块数据的方式是通过 Promise
,每次到达组件末尾的时候,就自动获取新的数据,追加新的滑块。
$("#flexSlider").flexslider({
// 这里我们假设当每次组件滑动到最后一个滑块的时候,就追加新的滑块
// 有点类似于懒加载或者预加载的意思
end(slider): function(){
// 假设每一个 `li`元素,就是一个滑块的单位,这里我们一次性追加了三个空滑块的 `DOM`,
// 这三个 `DOM`只有一个作用,那就是给将来真正要呈现在这里的滑块占位,
// 以便于 `flex-slider`组件正常的重新初始化,不会产生各种各样的异常,将来是要被替换掉的。
let str = ''
// 调用我们重写的 addSlide() 方法,这里我们一次性新增了三个滑块,
// 所以传入数字 3
addSlide(slider, str, 3);
let promiseResult = getData()
promiseResult.then((data)=>{
// 假设 data 中有动态追加滑块的数据
// len 指的是新增滑块的数量
let len = data.length
for(let i=0;i// 在这里组装真正的滑块DOM
let newDOM = ...
// 关键在这里,将原先的占位滑块DOM,替换成真正想要展示的
// 这里假设是追加到 flex-slider 的末尾处
$('#flexSlider .slides li:eq('+(slider.count-j)+')').replaceWith(str)
}
})
}
})
删除数据使用到另外一个方法 :removeSlide
:
此方法做的事情,基本上都是一样的,都重新指定了删减后滑块的数量,以及重新初始化,并且在最后调用了 removed
回调函数。
这个方法只有一个 obj
参数,代表的意思与 addSlide
相同,没有第二个参数,也就是不能随意指定要删除哪个滑块,只能是最后一个。
不过我们同样可以自己将此方法重写一遍,让其能一次性删除多个滑块,并且能够指定要删除的滑块下标,我没有实际做过,期间可能还会有其他的异常,但大致思路是这样的肯定没错。
removeSlide
方法的用法与 addSlide
没有太大区别,就不多说了。