js
实现一个轮播组件最近本身使用vue
实现了carousel
轮播组件,总体上的思路和以前的原生js
有很大的区别,对应的动画效果的实现是一个比较大的难点。这咱们用原生js
来实现轮播组件,方便咱们对框架和原生实现方式有一个认知和对比,也正好复习一下原生和DOM
相关的API
。css
在样式方面,你们能够发挥本身的想象力,也能够参考社区其它的相似组件,这里我实现了一个简易样式版本,供你们参考:html
vue
版本的在这里,而且支持移动端滑动切换:vue
在实现组件以前,咱们须要先了解一下有逢轮播和无缝轮播。css3
有缝轮播在轮播项正向移动到第一项或反向移动到最后一项时,会有一个回退效果。而无缝轮播会让人感受不到这个切换效果。git
接下来咱们一步步经过原生js
先实现一个有缝轮播,以后升级到无缝轮播,实现最终效果。github
有缝轮播相对来讲在实现思路上会简单一些,主要是利用了css3
提供的transiton
、translate
属性来实现切换的动画效果。web
首先搭建初始的页面结构,效果大概是这样的: 浏览器
如今假设咱们是使用组件的用户,咱们理想中的使用方式大概是这样的:app
<div class="slider">
<div class="slider-item">1</div>
<div class="slider-item">2</div>
<div class="slider-item">3</div>
</div>
复制代码
const slider = new Slider('.wk-slider',options);
复制代码
为了页面的美观,咱们添加一些初始化样式框架
/*初始化样式*/
* {
margin: 0;
padding: 0;
}
*,
*::after,
*::before {
box-sizing: inherit;
}
/* 通配符的css权重是最低的,html的标签都会继承box-sizing 而且对应元素进行盒模型更改的时候,对应的子元素也会更改 */
html {
box-sizing: border-box;
}
复制代码
用户本身的样式是这样的:
/*用户样式*/
.demo-wrapper {
margin: 40px;
}
.slider {
margin: 0 auto;
border: 4px solid black;
overflow: hidden;
}
.slider-item {
width: 400px;
height: 200px;
background-color: pink;
font-size: 80px;
text-align: center;
line-height: 200px;
color: #fff;
}
复制代码
接下来咱们进行js
逻辑的实现。
因为组件在使用时第一个参数为element
,第二个参数为options
配置项,因此咱们的构造函数须要这样写:
class Slider {
constructor (element, options) {
this.slider = document.querySelector(element);
this.options = options;
}
}
复制代码
以后咱们要获取到轮播项的对应的宽度赋值到slider
元素上,并为对应的元素添加特定前缀的css
类名,防止样式冲突。并且因为要让整个子元素进行平移,还要将全部子元素放到一块儿,而后再进行平移,并经过transition
设置过渡动效。
咱们在构造函数的原型上添加对应的方法
initSliderStyle () {
this.items = [...this.slider.children];
// 这里获取宽度时要当心异步加载
this.itemWidth = this.items[0].offsetWidth;
this.slider.classList.add('wk-slider');
this.slider.style.width = `${this.itemWidth}px`;
this.createItemsWrapper();
}
createItemsWrapper () {
this.itemsWrapper = document.createElement('div');
this.itemsWrapper.classList.add('wk-slider-items-wrapper');
this.slider.appendChild(this.itemsWrapper);
this.items.map(item => {
this.itemsWrapper.appendChild(item);
item.classList.add('wk-slider-item');
});
}
复制代码
对应的组件css
:
/*组件样式*/
.wk-slider {
display: flex;
}
.wk-slider-item {
flex-shrink: 0;
}
.wk-slider-items-wrapper {
display: flex;
flex-shrink: 0;
transition: all 1s;
}
复制代码
最后咱们为子元素容器设置定时器,让子元素动起来:
autoPlay () {
if (!this.options.autoPlay) return;
setInterval(() => {
this.index++;
this.go(this.index);
}, 2000);
}
go (index) {
const lastIndex = this.items.length - 1;
if (index > lastIndex) {this.index = 0;}
if (index < 0) {this.index = lastIndex;}
this.itemsWrapper.style.transform = `translateX(${-this.itemWidth * this.index}px)`;
}
复制代码
最终效果:
完整代码以下:
class Slider {
constructor (element, options) {
this.slider = document.querySelector(element);
this.options = options;
this.index = 0;
this.initSliderStyle();
this.autoPlay();
}
initSliderStyle () {
this.items = [...this.slider.children];
// 这里获取宽度时要当心异步加载
this.itemWidth = this.items[0].offsetWidth;
this.slider.classList.add('wk-slider');
this.slider.style.width = `${this.itemWidth}px`;
this.createItemsWrapper();
}
createItemsWrapper () {
this.itemsWrapper = document.createElement('div');
this.itemsWrapper.classList.add('wk-slider-items-wrapper');
this.slider.appendChild(this.itemsWrapper);
this.items.map(item => {
this.itemsWrapper.appendChild(item);
item.classList.add('wk-slider-item');
});
}
autoPlay () {
if (!this.options.autoPlay) return;
setInterval(() => {
this.index++;
this.go(this.index);
}, 2000);
}
go (index) {
const lastIndex = this.items.length - 1;
const { itemsWrapper, itemWidth } = this;
if (this.index > lastIndex) { this.lastToFirst(); }
if (this.index < 0) { this.firstToLast(lastIndex); }
itemsWrapper.style.transform = `translateX(${-itemWidth * this.index}px)`;
}
}
const slider = new Slider('.slider', { autoPlay: true });
复制代码
/*初始化样式*/
* {
margin: 0;
padding: 0;
}
*,
*::after,
*::before {
box-sizing: inherit;
}
/* 通配符的css权重是最低的,html的标签都会继承box-sizing 而且对应元素进行盒模型更改的时候,对应的子元素也会更改 */
html {
box-sizing: border-box;
}
/*用户样式*/
.demo-wrapper {
margin: 40px;
}
.slider {
margin: 0 auto;
border: 4px solid black;
overflow: hidden;
}
.slider-item {
width: 400px;
height: 200px;
background-color: pink;
font-size: 80px;
text-align: center;
line-height: 200px;
color: #fff;
}
/*组件样式*/
.wk-slider {
display: flex;
}
.wk-slider-item {
flex-shrink: 0;
}
.wk-slider-items-wrapper {
display: flex;
flex-shrink: 0;
transition: all 1s;
}
复制代码
这样一个简单的有缝轮播组件就初步完成了。可是这里咱们忽略了一个问题:当咱们的slide-item
是图片的时候,因为图片的异步加载,会致使获取的宽度并不许确,我想到的解决方法是在onload
事件以后再进行组件的使用:
window.onload = () => {
const slider = new Slider('.slider', { autoPlay: true });
}
复制代码
小伙伴也能够本身想一些其它的解决方法,展示奇思妙想的时候到了。
有缝轮播实际上是无缝轮播的一个升级版本,当图片轮播到最后一项的时候,能够继续像以前同样轮播到第一项,并不会有回退效果。
大概的一个思路以下:
这里咱们先复习几个原生js
语法:
Node.cloneNode()
: 克隆一个节点,返回调用该方法的节点的一个副本。若是参数传入true
,会克隆当前节点的全部后代节点。若是为false
,则只克隆该节点自己。默认参数为false
。parentNode.insertBefore(newNode,referenceNode)
: 在参考节点以前插入一个拥有指定父节点的子节点。接下来咱们一步一步来操做。
首先咱们分别复制第一项和最后一项插入到对应的位置:
appendCloneNode() {
const first = this.items[0];
const last = this.items[this.items.length - 1];
const firstClone = first.cloneNode(true);
const lastClone = last.cloneNode(true);
this.slider.insertBefore(lastClone, first);
this.slider.appendChild(firstClone);
this.items = [...this.slider.children];
}
复制代码
页面布局以下:
以后,咱们要分别对最后一项到第一项以及第一项到最后一项进行处理。
// 最后一张到第一张
lastToFirst () {
const { itemsWrapper, itemWidth } = this;
// 将transition效果取消
itemsWrapper.style.transition = 'none';
// 将索引设置为1
this.index = 1;
// 直接移动到第二张(没有过渡效果)
itemsWrapper.style.transform = `translateX(${-itemWidth * this.index}px)`;
// 索引+1
this.index++;
itemsWrapper.offsetWidth;
// 添加动画效果,经过go方法移动到第三章
itemsWrapper.style.transition = 'all 1s';
}
// 第一张到最后一张(代码含义同上)
firstToLast (lastIndex) {
const { itemsWrapper, itemWidth } = this;
// 将transition效果取消
itemsWrapper.style.transition = 'none';
this.index = lastIndex - 1;
itemsWrapper.style.transform = `translateX(${-itemWidth * this.index}px)`;
this.index--;
itemsWrapper.offsetWidth;
itemsWrapper.style.transition = 'all 1s';
}
复制代码
上边的代码是轮播的一个重要思路:将最后一张/第一张“闪动”到本身复制出来的哪一项,以后恢复过渡,继续轮播
而这里特别使人疑惑的一个地方:itemsWrapper.offsetWidth
。总体上来看,这一行代码和整个的逻辑并无任何关系,可是这里涉及到有关浏览器重排、重绘的一个重要知识点:
DOM变更和样式变更,都会触发浏览器的从新渲染。可是,浏览器比较智能,会尽可能把全部的变更集中在一块儿,排成一个队列,而后一次性执行,尽可能避免屡次从新渲染。
接下来咱们回到上边的代码:浏览器智能的将itemsWrapper.style.transition="none"
和itemsWrapper.style.transition = "all 1s"
进行合并后执行,而咱们要作的是强制浏览器进行从新渲染,让这俩行代码分别执行,而itemsWrapper.offsetWidth
就能够强制浏览器进行从新渲染。相似的属性和方法还有不少,如下是我收集的一些文章,但愿对你们的知识理解有帮助:
到这里,主要的逻辑功能已经基本上完成了。接下来的工做相对来讲就会很简单了,我这里再也不赘述,大概的工做是这样的:
以后咱们能够经过传入的options
来让用户本身控制是否支持自动轮播,是否须要左右箭头和下侧控制条,也可让用户自定义轮播的时间间隔。