是不是有点印象了,没错,他的最基本的用法就是左右滑动,插件使用者只需要写几行简单的html和js即可实现一个简单滑动效果,不过你完全可以组合各种元素来适应不同的场景。

当然插件我已经写好了,咱先看下这个插件是怎么来用的,对插件有一个大概了解,一会写起来不至于太懵逼。。。

插件地址:https://github.com/laravuel/swiper.git
demo目录有演示和用法,不过插件我用了webpack和babel转码,可以不用管,直接看src/swiper.js即可。

<!-- demo.html -->

<!-- swiper名称可以自定义的啦 -->
<div id="swiper">
<!-- swiper-item名称也可以自定义啦,相当于一个滑块 -->
<div class="swiper-item">
<img src="./images/1.jpg" />
</div>
<div class="swiper-item">
<img src="./images/2.jpg" />
</div>
<div class="swiper-item">
<img src="./images/3.jpg" />
</div>
</div> <script src="../dist/swiper.js"></script>
<script>
new Swiper({
swiper: '#swiper', // swiper节点名称
item: '.swiper-item', // swiper内部滑块的节点名称
autoplay: false, // 是否自动滑动
duration: 3000, // 自动滑动间隔时间
change(index) { // 每滑动一个滑块,插件就会触发change函数,index表示当前的滑块下标
console.log(index);
}
});
</script>

就是这么简单,插件本身只是一个类,你只需要new一个对象出来,然后传递一些参数就ok了。而且,插件还提供了一个change方法,让使用者可以在外部控制滑块的滑动!

const swiper = new Swiper({...});
swiper.change(2); // 滑动到第三个滑块

那么接下来,就是我们的教程时间了,我也不确定你能不能硬着头皮看完,不过我敢肯定,如果你能够亲手把插件写出来,你肯定会开心的飞起!!!

由于本次教程内容比较多,所以我分上下两部分来讲,第一部分主要讲解原理,第二部分开始着手编写插件。所以,感兴趣的小伙伴可以加个关注先。

1. 功能分析

俗话说,一上来就贴代码纯属耍流氓~
我们要清楚自己想实现哪些功能,懒得思考的童鞋可以结合我上面的动图来分析:

  1. 滑块可以左右滑动(支持移动端和pc端)
  2. 滑块块内部可以写任何元素
  3. 滑动到第一个和最后一个滑块时会有一个限制,防止越界
  4. 能够自动播放

我们所能看到的大概就这些,接下来我们会对这些功能一一进行拆解和分析。

2. 实现原理

上面简单梳理了一些功能,其实可以再扩展出以下几个问题:

  1. 滑块的html结构是什么样的?
  2. 滑块的滑动原理是什么?
  3. 如何来触发滑动?

别急,一个个来

2.1.1 滑块的html结构是什么样的?

我们先来看一张图:

 
滑块结构

这就是一个滑块的最基本的结构图,有三个部分组成:

  • 视图
    我们的内容展示区域,相当于最外层的一个展示层
  • 容器
    容器的宽度是无限长的,容纳我们所有需要切换的内容,滑块的左右滑动,实质上是容器的左右移动(left),而每个滑块相对于容器其实是静止的
  • 滑块
    一个个的内容

那么根据这个结构,可以用如下html代码来表示:

<!-- 视图层 -->
<div class="swiper">
<!-- 容器 -->
<div class="swiper-container">
<!-- 滑块 -->
<div class="swiper-item" style="background: #000">1</div>
<div class="swiper-item" style="background: #4269eb">2</div>
<div class="swiper-item" style="background: #247902">3</div>
</div>
</div>

然后再配上css样式:

.swiper {
position: relative;
width: 300px; /* 下面是为了让大家看的更清楚,加的修饰 */
padding: 30px 0;
margin: 0 auto;
background: #FFB973;
}
.swiper .swiper-container {
position: relative;
/* 为啥要设置-300px呢,因为我想让他默认在第二个滑块的位置,一会会给大家演示 */
left: -300px;
/* 让容器尽可能的宽,这样才能容纳更多的滑块 */
width: 10000%;
/* 让内部滑块可以排成一行 */
display: flex; /* 下面是为了让大家看的更清楚,加的修饰 */
background: red;
padding: 15px 0;
}
.swiper .swiper-container .swiper-item {
/* 宽度设置1%会按照外层视图的宽度来铺满 */
width: 1%;
height: 300px;
background: #eee; /* 下面是为了让大家看的更清楚,加的修饰 */
text-align: center;
font-size: 40px;
color: #fff;
}

你就会看到这么个效果:

 
初始效果

当然,你可以把我加的修饰css样式都给去掉,然后再试试。

2.1.2 滑块的滑动原理是什么?

如果你能够理解上面的html结构的话,那我们就可以进行滑动的讲解了。(如果还不理解的话,那就继续往下看吧~或许会突然恍然大悟!)

上面我们提到了“滑块容器”这个概念,滑块的左右移动就是他来负责的

.swiper .swiper-container {
position: relative;
left: -300px;
}

因为滑块的宽度是和视图的宽度一样的,所以我们这里滑块的宽度是300px,那么我们把容器的left设置为-300px,就相当于向左移动了一个滑块的宽度,设置为-600px就表示向左移动了两个滑块的宽度,懂了吧,如果你想移动到某个滑块,那么只需要知道这个滑块的顺序(从0开始),然后乘以滑块宽度的相反数就行了,比如要移动到第三个滑块,他的顺序是2,那么就是2 * -300 = -600

看下面动图演示:

 
滑动原理

但是好像并没有出现滑动的动画效果耶,废话,还没写呢,有些童鞋可能喜欢用jquery,习惯了他的animate动画方法,说实话其实我不太喜欢,因为我觉得css自带的动画完全可以解决大部分需求,而且当你以后用了vue这种mvvm框架,你会发现jquery这种动画方式很不实用!

扯远了,不过今天我们不用css的animation属性,我们用另外一个属性transition就可以满足,看名字你也能猜到,就是一个过渡属性,详细的用法请参考:https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions

我们把容器加上transition属性试试看哈:

.swiper .swiper-container {
/* 省略... */ transition: left 0.2s ease-in-out;
}
 
滑动动画

所以,我们写的这段css代码transition: left 0.2s ease-in-out;是表示:如果元素的left值变更,那么会有一个0.2s的过渡动画(补间动画)

到这里,我觉得你应该能理解了吧,每个滑块swiper-item的左右滑动,并不是滑块本身在移动,而是他的父元素swiper-container容器在左右移动(left值变化),然后我们用transition属性来让这个变化过程出现一个过渡动画效果!

2.1.3 如何来触发滑动?

上面我们扯了一堆html和css,接下来我们说点js吧。
“如何来触发滑动?”,我们先不考虑手机端,就按照pc网页来,那么触发操作就是在容器上按住鼠标向左/右拖动,然后松开鼠标后,滑块就会向左/右滑动

整个流程都跟鼠标事件挂钩:

  1. mousedown 鼠标按下事件
  2. mousemove 鼠标移动事件
  3. mouseup 鼠标抬起事件

利用好这3个事件,我们就可以来实现鼠标控制滑块移动了!!我们先来实现摁住鼠标向左、向右拖动滑块。
既然我们的容器swiper-container是负责左右移动的,那么我们就来监听他的鼠标事件吧,首先用querySelector获取视图容器两个元素节点:

// 首先获取视图层元素
const swiperEl = document.querySelector('.swiper');
// 在视图层里边查找容器元素
const containerEl = swiperEl.querySelector('.swiper-container');

获取到容器元素后,就可以用他的addEventListener来监听事件了:

containerEl.addEventListener('mousedown', (event) => {
console.log('鼠标按下了');
});
containerEl.addEventListener('mousemove', (event) => {
console.log('鼠标移动了');
});
containerEl.addEventListener('mouseup', (event) => {
console.log('鼠标抬起了');
});

看下动图操作:

 
鼠标按下抬起事件

虽然我们可以成功的监听到鼠标的操作事件,但是好像有点问题,我们期望的结果是,只有当鼠标按下后才会触发鼠标移动操作,但是现在看来并没有,所以可以考虑加一个状态来控制。

let state = 0;  // 鼠标默认状态

containerEl.addEventListener('mousedown', (event) => {
state = 1; // 设置为1表示按下了鼠标
console.log('鼠标按下了');
});
containerEl.addEventListener('mousemove', (event) => {
if (state != 1) return; // 只有当state == 1时候才允许执行该事件
console.log('鼠标移动了');
});
containerEl.addEventListener('mouseup', (event) => {
state = 0; // 恢复默认状态
console.log('鼠标抬起了');
});
 
鼠标按下抬起事件2

这样就好多了!!!

那么鼠标事件有了,接下来要让容器跟着鼠标左右动才行。

我们要知道,浏览器对于鼠标的任何操作,都会有一个坐标参数(pageX和pageY),所以,我们可以根据鼠标移动时候的坐标参数来计算容器的left值,你可以想象一下,当你摁下鼠标然后左右移动,鼠标每次移动相对于上次都会产生一个距离,我们是不是可以把容器的left值加上或者减去这个距离,从而达到一个拖动效果呢?记得前面我们回调函数里边的event参数了吗,他就是鼠标当前操作的相关属性,而我们目前只需要用到pageX属性

 
event

下面我们来写代码,有个地方需要注意下,我们先把容器的transition这个属性给注释掉,后面会解释为什么?

.swiper .swiper-container {
/* 省略... */ /* transition: left 0.2s ease-in-out; */
}
每一步的操作,都在注释里边详细标注: // 首先获取视图层元素
const swiperEl = document.querySelector('.swiper');
// 在视图层里边查找容器元素
const containerEl = swiperEl.querySelector('.swiper-container'); let state = 0; // 鼠标默认状态
let oldEvent = null; // 用来记录鼠标上次的位置
// 获取容器的初始left值
let left = containerEl.offsetLeft; containerEl.addEventListener('mousedown', (event) => {
state = 1; // 设置为1表示按下了鼠标
oldEvent = event; // 当鼠标按下时候记录初始位置
console.log('鼠标按下了');
}); containerEl.addEventListener('mousemove', (event) => {
if (state != 1) return; // 只有当state == 1时候才允许执行该事件 // 用当前鼠标的位置来和上次鼠标的位置作比较
// 如果当前鼠标的pageX小于上次鼠标的pageX,那就表示鼠标在向左拖动,就需要把容器left值减去鼠标移动的距离
if (event.pageX < oldEvent.pageX) {
left -= oldEvent.pageX - event.pageX;
}
else {
left += event.pageX - oldEvent.pageX;
}
// 完事之后记得把当前鼠标的位置赋值给oldEvent
oldEvent = event;
// 最后再把left赋值给容器
containerEl.style.left = left + 'px';
console.log('鼠标移动了');
}); containerEl.addEventListener('mouseup', (event) => {
state = 0; // 恢复默认状态
console.log('鼠标抬起了');
});

运行看效果:

 
鼠标拖动.gif

没毛病,你看这个鼠标,他又白又。。。

可是,可是,你这鼠标松开后,也没滑动到对应位置啊,额,额,前面我们不是讲了嘛,滑块顺序滑块宽度还记得么?0 - 滑块顺序 * 滑块宽度就会移动到这个滑块,还记得不?

我们用index来记录当前滑块的顺序

let index = 0;          // 记录当前滑块的顺序(从0开始)

itemWidth来存储滑块的宽度

// 获取到所有的滑块元素
const itemEls = containerEl.querySelectorAll('.swiper-item');
// 获取到滑块的宽度
const itemWidth = itemEls[0].offsetWidth;

把我们的left变量改一下,之前left变量是直接获取容器元素的left值,现在我们要根据index来计算

// let left = containerEl.offsetLeft;
// 存储容器的left,这里我们根据index来计算初始容器的left值
let left = 0 - itemWidth * index;
// 设置容器的初始位置
containerEl.style.left = left + 'px';

这样我们只需要修改index变量的值,那么容器初始位置就会发生变化。

然后,我们在鼠标按下的时候,记录下坐标位置,在鼠标抬起的时候拿当前鼠标的位置和按下的位置作比较,来判断用户是向左划的,还是向右划的!

 
滑块左右移动判断

加一个变量,用来记录鼠标按下的参数,并且在鼠标按下的时候进行赋值!

let startEvent = null;  // 用来记录鼠标按下时候的位置(最初位置)

containerEl.addEventListener('mousedown', (event) => {
state = 1; // 设置为1表示按下了鼠标
startEvent = oldEvent = event; // 当鼠标按下时候记录初始位置
console.log('鼠标按下了');
});

那么鼠标抬起的时候,只需要和startEvent.pageX做比较,就可以判断出左滑还是右滑,左滑我们让index + 1,右滑就让index - 1,最终我们通过index再来计算left

containerEl.addEventListener('mouseup', (event) => {
state = 0; // 恢复默认状态 // 鼠标抬起时候,和按下的坐标作比对,用来判断是向左滑动还是向右滑动
// 向左滑动那么就是要显示下一个滑块,所以index要加1
if (event.pageX < startEvent.pageX) {
index ++;
}
else {
index --;
} left = 0 - itemWidth * index;
containerEl.style.left = left + 'px';
console.log('鼠标抬起了');
});
 
通过index设置滑块位置.gif

是不是像那么回事了,不过怎么没滑动动画呢,还记得我们注释掉的那个transition么,为什么要注释掉呢,因为只有在鼠标抬起的那一刻才需要滑动动画,左右拖动是根据鼠标位移距离来计算left,数值很小,完全不需要衔接动画,所以,我们先把注释掉那个transition代码单独提取出来放到一个和swiper-container同级的.move类里边,当鼠标抬起的时候,我们把swiper-container追加一个move类就行。

.swiper .swiper-container.move {
transition: left 0.2s ease-in-out;
} containerEl.addEventListener('mouseup', (event) => {
state = 0; // 恢复默认状态 // 鼠标抬起时候,和按下的坐标作比对,用来判断是向左滑动还是向右滑动
// 向左滑动那么就是要显示下一个滑块,所以index要加1
if (event.pageX < startEvent.pageX) {
index ++;
}
else {
index --;
} // 追加一个move样式
containerEl.className += ' move';
// 当过度动画结束后,一定要把这个类给移除掉
containerEl.addEventListener('transitionend', () => {
// 正则替换 \s+ 表示一个或多个空白字符
containerEl.className = containerEl.className.replace(/\s+move/, '');
}) left = 0 - itemWidth * index;
containerEl.style.left = left + 'px';
console.log('鼠标抬起了');
});

注意观察swiper-container的dom节点:

 
滑动雏形.gif

仔细看上面的动图,第一个和最后一个滑动的时候是不是越界了,那么我们只需要判断index就行,看代码:

containerEl.addEventListener('mouseup', (event) => {
state = 0; // 恢复默认状态 // 鼠标抬起时候,和按下的坐标作比对,用来判断是向左滑动还是向右滑动
// 向左滑动那么就是要显示下一个滑块,所以index要加1
if (event.pageX < startEvent.pageX) {
index ++;
}
else {
index --;
} // 防止滑块越界
// 如果当前滑块是第一个,向右滑动后,回到第一个滑块
// 如果是最后一个,向左滑动后,回到最后一个滑块
if (index < 0) {
index = 0;
}
else if (index > itemEls.length - 1) {
index = itemEls.length - 1;
} // 追加一个move样式
containerEl.className += ' move';
// 当过度动画结束后,一定要把这个类给移除掉
containerEl.addEventListener('transitionend', () => {
// 正则替换 \s+ 表示一个或多个空白字符
containerEl.className = containerEl.className.replace(/\s+move/, '');
}) left = 0 - itemWidth * index;
containerEl.style.left = left + 'px';
console.log('鼠标抬起了');
});
 
image

我们扩展出的三个问题,基本上都解决了。
而且到目前为止,其实你已经实现了一个基本的滑块功能了,只不过略显粗糙!

2.2 自动播放的原理

回到我们的功能列表,我们来看下第四条“自动播放”,第一个想到的是setInterval

setInterval(() => {
// 这个回调会每隔2秒执行一次
}, 2000);

所以,我们只需要在这个回调函数里边写上让滑块滑动的代码不就行了?
我们是用index变量来控制当前滑块的,那么每隔2秒让index加1,最后再根据index计算出left的值,不就可以了?

setInterval(() => {
// 默认向左滑动
index ++;
// 如果滑动到最后一个滑块,则回到第一个滑块
if (index > itemEls.length - 1) {
index = 0;
} // 下面的代码跟我们鼠标抬起的事件的代码一样的,要不要考虑简单的封装一下?
// 追加一个move样式
containerEl.className += ' move';
// 当过度动画结束后,一定要把这个类给移除掉
containerEl.addEventListener('transitionend', () => {
// 正则替换 \s+ 表示一个或多个空白字符
containerEl.className = containerEl.className.replace(/\s+move/, '');
}) left = 0 - itemWidth * index;
containerEl.style.left = left + 'px';
}, 2000);
 
自动播放.gif

关于重复逻辑的问题,我们会在第二部分写插件时候进行封装,这部分,我们只讲原理,当然如果你是个强迫症患者,可以自己试着封装个函数。

不过他老是这么自动播放也不是个事,有时候我想看看内容,还没看完呢,就自动划走了,所以,我们可以当鼠标放在容器上的时候,停止播放,鼠标移开后又恢复自动播放

  1. mouseover 鼠标移动到某个元素上
  2. mouseout 鼠标在某个元素上移开

我们还是在容器上监听这两个事件,并用一个状态autoplay来控制播放:

// 自动播放状态
let autoplay = true; setInterval(() => {
if (!autoplay) return;
// 默认向左滑动
index ++;
// 如果滑动到最后一个滑块,则回到第一个滑块
if (index > itemEls.length - 1) {
index = 0;
} // 追加一个move样式
containerEl.className += ' move';
// 当过度动画结束后,一定要把这个类给移除掉
containerEl.addEventListener('transitionend', () => {
// 正则替换 \s+ 表示一个或多个空白字符
containerEl.className = containerEl.className.replace(/\s+move/, '');
}) left = 0 - itemWidth * index;
containerEl.style.left = left + 'px';
}, 2000); containerEl.addEventListener('mouseover', () => {
// 鼠标移动到容器上,停止播放
autoplay = false;
});
containerEl.addEventListener('mouseout', () => {
// 鼠标从容器上移开,恢复播放
autoplay = true;
});
 
鼠标控制自动播放.gif

当然,还有其他的方法来控制自动播放,比如用clearInterval函数等。

3. 结尾

至此,我们的原理都讲的差不多了,有遗漏的地方,还望指出,那么在第二部分,我会和大家一块来把写的杂七杂八的代码做一个封装,让我们的代码插件化,适应更多的场景。

作者:Mr_芝麻
链接:https://www.jianshu.com/p/22accec4d17b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

用原生js来写一个swiper滑块插件的更多相关文章

  1. 给Ionic写一个cordova(PhoneGap)插件

    给Ionic写一个cordova(PhoneGap)插件 之前由javaWeb转html5开发,由于面临新技术,遂在适应的过程中极为挣扎,不过还好~,这个过程也极为短暂:现如今面临一些较为复杂的需求还 ...

  2. 用javascript写一个emoji表情插件

    概述 以我们写的这个emoji插件为例,网上已经有一些相关的插件了,但你总感觉有些部分的需求不能被满足(如:可以自行添加新的表情包而不用去改源代码等等) 详细 代码下载:http://www.demo ...

  3. Skywalking-02:如何写一个Skywalking trace插件

    如何写一个Skywalking trace插件 javaagent 原理 美团技术团队-Java 动态调试技术原理及实践 类图 实现 ConsumeMessageConcurrentlyInstrum ...

  4. js单行写一个评级组件

    单行写一个评级组件:"★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate); -----------------------------------分隔符- ...

  5. 写一个Vue loading 插件

    什么是vue插件? 从功能上说,插件是为Vue添加全局功能的一种机制,比如给Vue添加一个全局组件,全局指令等: 从代码结构上说,插件就是一个必须拥有install方法的对象,这个方法的接收的第一个参 ...

  6. 改变滚动条的原始样式: chrome 可以改变, IE只能变相关颜色,firfox好像也不好改。最好是自己写一个或是用插件

    相关作者链接地址: https://www.lyblog.net/detail/314.html 问题: 1.我在项目中遇到的问题: 在设置了::-webkit-scrollbar 后,滚动条不见了! ...

  7. 【原生JS】写最简单的图片轮播

    非常简单的一个大图轮播,通过将控制显示位置来进行轮播效果,写来给正在学习的新手朋友们参考交流. 先看效果:(实际效果没有这么快) 先看布局: <div id="display" ...

  8. 原生js来写获取元素距离顶部距离,以及滚动条滚动指定距离和时间控制

    这是我在写vue项目里封装的一个公共js类 里面还有一些其他的方法,一并拿过来了 class Public { isDesktop(){ //判断是否为pc端 return (window.scree ...

  9. 用原生JS实现的一个导航下拉菜单,下拉菜单的宽度与浏览器视口的宽度一样(js+html+css)

    这个导航下拉菜单需要实现的功能是:下拉菜单的宽度与浏览器视口的宽度一样宽:一级导航只有两项,当鼠标移到一级导航上的导航项时,相应的二级导航出现.在本案例中通过改变二级导航的高度来实现二级导航的显示和消 ...

随机推荐

  1. 构建 JVM(HotSpot) 源码调试环境(OpenJDK8)

    原本想在 Windows 下编译调试,但过程中遇到了诸多错误(老是报路径错误...),最后只好放弃. 此次记录调试的方法为 CentOS7 上编译,Windows 上使用 Clion 远程调试(也可直 ...

  2. JScript 对字符串、数组处理的常用方法

    1.anchor 方法 在对象中的指定文本两端放置一个有 NAME 属性的 HTML 锚点.     strVariable.anchor(anchorString) var strVariable ...

  3. 打开App显示文件已损坏,打不开,您应该将它移到废纸篓,怎么办?

    1. 首先确保系统安全设置已经改为任何来源 sudo spctl --master-disable 2. 打开任何来源后,到应用程序目录中尝试运行软件,如果仍提示损坏,请在应用图标上,鼠标右键,在弹出 ...

  4. Linux - 搭建Web项目(Django + nginx + uwsgi)

    工作中碰到需要使用Django + nginx + uwsgi 搭建项目环境 1. 搭建基本环境 需要有python环境,不多做说明 需要安装nginx,不多做说明 需要安装uwsgi: yum in ...

  5. mongdb mapReduce聚合操作

    1.数据准备 请看group操作 2.mapReduce名词解释 mapReduce 随着"大数据"概念而流行.其实mapReduce的概念非常简单,从功能上说,相当于RDBMS的 ...

  6. 安德鲁1.2Ku使用感受

    看中玻璃钢天线了,华达太贵,安德鲁性价比比较高.就在上周,决定入一个试试.周二微信转账,380+120运费,安能物流送货上门,上周6中午午睡时接到电话.去广场拿货. 锅面包装很简单,纸壳与胶带简单粗暴 ...

  7. js上拉加载

    <ul class="u-f-log"> <li class="u-f-log-alone" v-for="item in log& ...

  8. Spring学习之==>IoC

    一.概述 Spring的三大核心思想:IoC(控制反转),DI(依赖注入),AOP(面向切面编程).本问讲着重介绍一下控制反转. 何谓控制反转:Spring 通过一种称作控制反转(IoC)的技术促进了 ...

  9. RxJava2实战--第八章 RxJava的背压

    RxJava2实战--第八章 RxJava的背压 1 背压 在RxJava中,会遇到被观察者发送消息太快以至于它的操作符或者订阅者不能及时处理相关的消息,这就是典型的背压(Back Pressure) ...

  10. python安装二进制k8s 1.11.0 一个master、一个node 查看node节点是主机名---apiserver无法启动,后来改了脚本应该可以

    一.脚本说明: 本实验中master.node.etcd都是单体. 安装顺序为:先安装test1节点主要组件,然后开始安装test2节点,最后回头把test1节点加入集群中,这样做目的是理解以后扩容都 ...