嗨~ 这里是芝麻,今天咱们一块来作一个“滑块插件”。那么啥是滑块插件呢?滑块插件能干吗呢?请看下图:
javascript
是否是有点印象了,没错,他的最基本的用法就是左右滑动,插件使用者只须要写几行简单的html和js便可实现一个简单滑动效果,不过你彻底能够组合各类元素来适应不一样的场景。css
固然插件我已经写好了,咱先看下这个插件是怎么来用的,对插件有一个大概了解,一会写起来不至于太懵逼。。。html
插件地址:github.com/laravuel/sw… demo目录有演示和用法,不过插件我用了webpack和babel转码,能够不用管,直接看src/swiper.js便可。vue
<!-- 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方法,让使用者能够在外部控制滑块的滑动!java
const swiper = new Swiper({...});
swiper.change(2); // 滑动到第三个滑块
复制代码
那么接下来,就是咱们的教程时间了,我也不肯定你能不能硬着头皮看完,不过我敢确定,若是你可以亲手把插件写出来,你确定会开心的飞起!!!jquery
因为本次教程内容比较多,因此我分上下两部分来说,第一部分主要讲解原理,第二部分开始着手编写插件。因此,感兴趣的小伙伴能够加个关注先。
webpack
俗话说,一上来就贴代码纯属耍流氓~ 咱们要清楚本身想实现哪些功能,懒得思考的童鞋能够结合我上面的动图来分析:git
- 滑块能够左右滑动(支持移动端和pc端)
- 滑块块内部能够写任何元素
- 滑动到第一个和最后一个滑块时会有一个限制,防止越界
- 可以自动播放
咱们所能看到的大概就这些,接下来咱们会对这些功能一一进行拆解和分析。
github
上面简单梳理了一些功能,其实能够再扩展出如下几个问题:web
别急,一个个来
咱们先来看一张图:
- 视图 咱们的内容展现区域,至关于最外层的一个展现层
- 容器 容器的宽度是无限长的,容纳咱们全部须要切换的内容,滑块的左右滑动,实质上是容器的左右移动(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样式都给去掉,而后再试试。
若是你可以理解上面的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
就能够知足,看名字你也能猜到,就是一个过渡属性,详细的用法请参考:developer.mozilla.org/zh-CN/docs/…
咱们把容器加上transition
属性试试看哈:
.swiper .swiper-container {
/* 省略... */
transition: left 0.2s ease-in-out;
}
复制代码
transition: left 0.2s ease-in-out;
是表示:
若是元素的left值变动,那么会有一个0.2s的过渡动画(补间动画)
到这里,我以为你应该能理解了吧,每一个滑块swiper-item
的左右滑动,并非滑块自己在移动,而是他的父元素swiper-container
容器在左右移动(left值变化),而后咱们用transition
属性来让这个变化过程出现一个过渡动画效果!
上面咱们扯了一堆html和css,接下来咱们说点js吧。 “如何来触发滑动?”,咱们先不考虑手机端,就按照pc网页来,那么触发操做就是在容器上按住鼠标向左/右拖动,而后松开鼠标后,滑块就会向左/右滑动。
整个流程都跟鼠标事件挂钩:
mousedown
鼠标按下事件mousemove
鼠标移动事件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('鼠标抬起了');
});
复制代码
那么鼠标事件有了,接下来要让容器跟着鼠标左右动才行。
咱们要知道,浏览器对于鼠标的任何操做,都会有一个坐标参数(pageX和pageY),因此,咱们能够根据鼠标移动时候的坐标参数来计算容器的left值,你能够想象一下,当你摁下鼠标而后左右移动,鼠标每次移动相对于上次都会产生一个距离,咱们是否是能够把容器的left值加上或者减去这个距离,从而达到一个拖动效果呢?记得前面咱们回调函数里边的event
参数了吗,他就是鼠标当前操做的相关属性,而咱们目前只须要用到pageX属性
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('鼠标抬起了');
});
复制代码
运行看效果:
但是,但是,你这鼠标松开后,也没滑动到对应位置啊,额,额,前面咱们不是讲了嘛,滑块顺序、滑块宽度还记得么?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('鼠标抬起了');
});
复制代码
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节点:
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('鼠标抬起了');
});
复制代码
回到咱们的功能列表,咱们来看下第四条“自动播放”,第一个想到的是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);
复制代码
不过他总是这么自动播放也不是个事,有时候我想看看内容,还没看完呢,就自动划走了,因此,咱们能够当鼠标放在容器上的时候,中止播放,鼠标移开后又恢复自动播放
mouseover
鼠标移动到某个元素上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;
});
复制代码
clearInterval
函数等。
至此,咱们的原理都讲的差很少了,有遗漏的地方,还望指出,那么在第二部分,我会和你们一块来把写的杂七杂八的代码作一个封装,让咱们的代码插件化,适应更多的场景。