如何用原生js来写一个swiper滑块插件(上)原理

栏目: CSS · 发布时间: 5年前

内容简介:嗨~ 这里是芝麻,今天我们一块来做一个“滑块插件”。那么啥是滑块插件呢?滑块插件能干嘛呢?请看下图:是不是有点印象了,没错,他的最基本的用法就是左右滑动,插件使用者只需要写几行简单的html和js即可实现一个简单滑动效果,不过你完全可以组合各种元素来适应不同的场景。当然插件我已经写好了,咱先看下这个插件是怎么来用的,对插件有一个大概了解,一会写起来不至于太懵逼。。。

嗨~ 这里是芝麻,今天我们一块来做一个“滑块插件”。那么啥是滑块插件呢?滑块插件能干嘛呢?请看下图:

如何用原生js来写一个swiper滑块插件(上)原理

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

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

插件地址: github.com/laravuel/sw… 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结构是什么样的?

我们先来看一张图:

如何用原生js来写一个swiper滑块插件(上)原理

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

  • 视图 我们的内容展示区域,相当于最外层的一个展示层
  • 容器 容器的宽度是无限长的,容纳我们所有需要切换的内容,滑块的左右滑动,实质上是容器的左右移动(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;
}
复制代码

你就会看到这么个效果:

如何用原生js来写一个swiper滑块插件(上)原理

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

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

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

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

.swiper .swiper-container {
    position: relative;
    left: -300px;
}
复制代码

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

看下面动图演示:

如何用原生js来写一个swiper滑块插件(上)原理

但是好像并没有出现滑动的动画效果耶,废话,还没写呢,有些童鞋可能喜欢用 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;
}
复制代码
如何用原生js来写一个swiper滑块插件(上)原理
所以,我们写的这段css代码 transition: left 0.2s ease-in-out; 是表示: 如果元素的left值变更,那么会有一个0.2s的过渡动画(补间动画)

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

2.1.3 如何来触发滑动?

上面我们扯了一堆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('鼠标抬起了');
});
复制代码

看下动图操作:

如何用原生js来写一个swiper滑块插件(上)原理

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

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('鼠标抬起了');
});
复制代码
如何用原生js来写一个swiper滑块插件(上)原理

这样就好多了!!!

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

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

如何用原生js来写一个swiper滑块插件(上)原理
下面我们来写代码,有个地方需要注意下,我们先把容器的 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('鼠标抬起了');
});
复制代码

运行看效果:

如何用原生js来写一个swiper滑块插件(上)原理

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

可是,可是,你这鼠标松开后,也没滑动到对应位置啊,额,额,前面我们不是讲了嘛, 滑块顺序滑块宽度 还记得么? 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 变量的值,那么容器初始位置就会发生变化。

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

如何用原生js来写一个swiper滑块插件(上)原理

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

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('鼠标抬起了');
});
复制代码
如何用原生js来写一个swiper滑块插件(上)原理
是不是像那么回事了,不过怎么没滑动动画呢,还记得我们注释掉的那个 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节点:

如何用原生js来写一个swiper滑块插件(上)原理
仔细看上面的动图,第一个和最后一个滑动的时候是不是越界了,那么我们只需要判断 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('鼠标抬起了');
});
复制代码
如何用原生js来写一个swiper滑块插件(上)原理

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

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);
复制代码
如何用原生js来写一个swiper滑块插件(上)原理

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

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

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;
});
复制代码
如何用原生js来写一个swiper滑块插件(上)原理
当然,还有其他的方法来控制自动播放,比如用 clearInterval

函数等。


以上所述就是小编给大家介绍的《如何用原生js来写一个swiper滑块插件(上)原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

疯狂又脆弱  坚定又柔软

疯狂又脆弱 坚定又柔软

朱墨 / 湖南文艺出版社 / 2018-3 / 39.80元

《疯狂又脆弱 坚定又柔软》是朱墨的一部作品集,介绍了作者考研到北京,工作在华谊,以及留学去英国的经历,在这短短几年中她一路升职加薪,25岁升任华谊宣传总监,27岁赚到人生的第一笔100万,30岁却毅然离职去英国留学,在表面的光鲜亮丽之下,她也曾付出过外人所不知道的心血和努力。她的人生告诉我们,每一个身居高位或者肆意潇洒的人,都曾为梦想疯狂地倾尽全力,而那些心怀梦想的人也总是怀揣一颗坚定又柔软的内心......一起来看看 《疯狂又脆弱 坚定又柔软》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具