应用思否猫素材实现一个轮播图
本文参加了1024程序员节,欢送正在浏览的你也退出。
通过本文,你将学到:
- html
- css
- js
没错,就是html,css,js,当初是框架流行的时代,所以很少会有人在意原生三件套,通过本文实现一个丝滑的轮播图,带你重温html,css和js基础知识。
为什么选用轮播图做示例?有如下几点:
- 业务当中最罕用
- 轮播图说简略也不简略,说简单也不简单,能够说是所有我的项目的基石
- 轮播图更适宜考查你对html,css,js的根底把握
废话不多说,让咱们先来看一下效果图,如下:
通过上图,咱们能够晓得,一个轮播图蕴含了三大部分,第一局部是轮播图的局部,第二局部则是轮播翻页局部,第三局部则是上一页和下一页。
所以一个轮播图的构造咱们基本上就清晰了,让咱们来具体看一下吧。
html文档构造
首先咱们要有一个容器元素,如下:
<!--容器元素--><div class="carousel-box"></div>
而后,咱们第一局部轮播图也须要一个容器元素,随后就是轮播图的元素列表,构造如下:
<div class="carousel-content"> <div class="carousel-item active"> <img src="https://www.eveningwater.com/img/segmentfault/1.png" alt="" class="carousel-item-img"> </div> <div class="carousel-item"> <img src="https://www.eveningwater.com/img/segmentfault/2.png" alt="" class="carousel-item-img"> </div> <div class="carousel-item"> <img src="https://www.eveningwater.com/img/segmentfault/3.png" alt="" class="carousel-item-img"> </div> <div class="carousel-item"> <img src="https://www.eveningwater.com/img/segmentfault/4.png" alt="" class="carousel-item-img"> </div> <div class="carousel-item"> <img src="https://www.eveningwater.com/img/segmentfault/5.png" alt="" class="carousel-item-img"> </div> <div class="carousel-item"> <img src="https://www.eveningwater.com/img/segmentfault/6.png" alt="" class="carousel-item-img"> </div> <div class="carousel-item"> <img src="https://www.eveningwater.com/img/segmentfault/7.png" alt="" class="carousel-item-img"> </div></div>
剖析下来就三个,容器元素,每一个轮播图元素外面再套一个图片元素。
接下来是第二局部,同样的也是一个容器元素,套每一个轮播点元素,如下:
<div class="carousel-sign"> <div class="carousel-sign-item active">0</div> <div class="carousel-sign-item">1</div> <div class="carousel-sign-item">2</div> <div class="carousel-sign-item">3</div> <div class="carousel-sign-item">4</div> <div class="carousel-sign-item">5</div> <div class="carousel-sign-item">6</div></div>
无论是轮播图局部还是轮播分页局部,都加了一个active类名,作为默认显示和选中的轮播图和轮播分页按钮。
第三局部,则是上一页和下一页按钮,这里如果咱们将最外层的轮播容器元素设置了定位,这里也就不须要一个容器元素了,咱们间接用定位,下一节写款式会具体阐明。咱们还是来看构造,如下:
<div class="carousel-ctrl carousel-left-ctrl"><</div><div class="carousel-ctrl carousel-right-ctrl">></div>
这里采纳了html字符实体用作上一页和下一页文本,对于什么是html字符实体,能够参考相干文章,这里不做详解。
通过以上的剖析,咱们一个轮播图的文档构造就实现了,接下来,让咱们编写款式。
编写款式
首先咱们依据效果图能够晓得,容器元素,轮播图局部容器元素以及每一个轮播图元素都是百分之百宽高的,款式如下:
.carousel-box,.carousel-content,.carousel-item ,.carousel-item-img { width: 100%; height: 100%;}
其次,容器元素和轮播图元素,咱们须要设置成绝对定位,为什么轮播图也要设置成绝对定位?因为咱们这里是应用的相对定位加left和right偏移从而实现的滑动轮播成果。
.carousel-box,.carousel-item { position: relative;}
而后,因为轮播图只显示以后的轮播图,而超出的局部也就是溢出局部咱们须要截断暗藏,因而为容器元素设置截断暗藏。
.carousel-box { overflow: hidden;}
接着,每一个轮播图元素默认都是暗藏的,只有加了active类名,才显示。
.carousel-item { display: none;}.carousel-item.active { display: block; left: 0;}
再而后别离是向左还是向右,这里咱们是通过增加类名的形式来实现滑动,所以咱们在这里额定为轮播元素减少了left和right类名,如下:
.carousel-item.active.left { left: -100%;}.carousel-item.active.right { left: 100%;}
有意思的点还在这里,就是每一个轮播图元素还额定的减少了next和prev类名,为什么要减少这两个类名?试想咱们以后轮播图显示的时候,后面的是不是应该被暗藏,而前面的下一个应该是紧紧排在以后轮播图之后,而后做筹备,而这两个类名的目标就是在这里,让成果看起来更加丝滑一些。
.carousel-item.next,.carousel-item.prev { display: block; position: absolute; top: 0;}.carousel-item.next { left: 100%;}.carousel-item.prev { left: -100%;}.carousel-item.next.left,.carousel-item.prev.right { left: 0%;}
最初补充一个轮播图片元素的款式,如下:
.carousel-item-img { object-fit: cover;}
到了这里,其实轮播图的外围思路曾经呈现了,就是利用的相对定位加left偏移来实现,而在javascript逻辑中,咱们只须要操作类名就能够了。
这样做的益处很显然,咱们将动画的逻辑包装在css中,因而轮播的动画逻辑也比拟好批改,批改css代码总比批改js代码简略吧?
到了这里,轮播局部的款式咱们就曾经实现了,接下来看分页按钮组的款式。
依据图片示例,分页按钮组元素是在底部的,其实分页按钮组也能够说是很惯例的按钮布局,所以款式都是一些很根底的,也没有必要做太多的详解。
.carousel-sign { position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); padding: 5px 3px; border-radius: 6px; user-select: none; background: linear-gradient(135deg,#73a0e4 10%,#1467e4 90%);}.carousel-sign-item { width: 22px; height: 20px; font-size: 14px; font-weight: 500; line-height: 20px; text-align: center; float: left; color:#f2f3f4; margin: auto 4px; cursor: pointer; border-radius: 4px;}.carousel-sign-item:hover { color:#fff;}.carousel-sign-item.active { color:#535455; background-color: #ebebeb;}
最初就是上一页下一页的款式,有意思的是上一页下一页默认是不应该显示的,鼠标悬浮到轮播图容器元素上,才会显示,所以这里也用到了定位。
.carousel-ctrl { position: absolute; top: 50%; transform: translateY(-50%); font-size: 30px; font-weight: 300; user-select: none; background: linear-gradient(135deg,#73a0e4 10%,#1467e4 90%); color: #fff; border-radius: 5px; cursor: pointer; transition: all .1s cubic-bezier(0.075, 0.82, 0.165, 1); text-align: center; padding: 1rem;}.carousel-ctrl.carousel-left-ctrl { left: -50px;}.carousel-ctrl.carousel-right-ctrl { right: -50px;}.carousel-box:hover .carousel-ctrl.carousel-left-ctrl { left: 10px;}.carousel-box:hover .carousel-ctrl.carousel-right-ctrl { right: 10px;}.carousel-ctrl:hover { background-color: rgba(0,0,0,.8);}
到了这里,款式的布局就实现了,接下来是javascript外围逻辑,让咱们一起来看一下吧。
轮播图的外围逻辑
咱们将轮播图封装在一个类当中,而后通过结构函数调用的形式来应用它,咱们先来看应用形式,如下:
const options = { el: '.carousel-box', speed: 1000, // 轮播速度(ms) delay: 0, // 轮播提早(ms) direction: 'left', // 图片滑动方向 monitorKeyEvent: true, // 是否监听键盘事件 monitorTouchEvent: true // 是否监听屏幕滑动事件}const carouselInstance = new Carousel(options);carouselInstance.start();
通过应用形式,咱们失去了什么?
轮播图的配置对象
- 1.1 el: 容器元素
- 1.2 speed: 轮播速度
- 1.3 delay: 轮播执行延迟时间
- 1.4 direction: 滑动方向,次要有left和right两个值
- 1.5 monitorKeyEvent: 是否监听键盘事件,也就是说是否能够通过点击键盘上的左右来切换轮播图
- 1.6 monitorTouchEvent: 是否监听屏幕滑动事件,也就是说是否能够通过滑动屏幕来切换图片
- Carousel是一个构造函数
- carousel构造函数外部提供了一个start办法用来开始轮播,很显然这里是开始主动轮播
依据以上的剖析,让咱们来一步步实现Carousel这个货色吧。
首先它是一个构造函数,反对传入配置对象,所以,咱们定义一个类,并且这个类还有一个start办法也初始化,如下:
class Carousel { constructor(options){ //外围代码 } start(){ //外围代码 }}
在初始化的时候咱们须要做什么?
首先咱们要获取到轮播元素,还有上一页下一页按钮以及咱们的分页按钮元素。如下:
class Carousel { constructor(options){ // 容器元素 this.container = $(options.el); // 轮播图元素 this.carouselItems = this.container.querySelectorAll('.carousel-item'); // 分页按钮元素组 this.carouselSigns = $$(('.carousel-sign .carousel-sign-item'),this.container); // 上一页与下一页 this.carouselCtrlL = $$(('.carousel-ctrl'),this.container)[0]; this.carouselCtrlR = $$(('.carousel-ctrl'),this.container)[1]; } start(){ //外围代码 }}
这里用到了$和$$办法,看起来和jQuery的获取DOM很像,用到了jQuery?那当然不是了,咱们来看这2个办法的实现。
const $ = (v,el = document) => el.querySelector(v);const $$ = (v,el = document) => el.querySelectorAll(v);
也就是获取dom元素的繁难封装啦。
- document.querySelector API
- document.querySelectorAll API
就是基于以上两个dom查问节点的办法封装的,让咱们来看下一步,首先咱们须要有一个确定以后轮播图的索引值,而后获取所有轮播图元素的长度,而后就是初始化配置对象。代码如下:
class Carousel { constructor(options){ //省略了代码 // 以后图片索引 this.curIndex = 0; // 轮播盒内图片数量 this.numItems = this.carouselItems.length; // 是否能够滑动 this.status = true; // 轮播速度 this.speed = options.speed || 600; // 期待延时 this.delay = options.delay || 3000; // 轮播方向 this.direction = options.direction || 'left'; // 是否监听键盘事件 this.monitorKeyEvent = options.monitorKeyEvent || false; // 是否监听屏幕滑动事件 this.monitorTouchEvent = options.monitorTouchEvent || false; } //省略了代码}
初始化实现之后,接下来咱们有两个逻辑还须要在初始化外面实现,第一个逻辑是增加动画过渡成果,也就是让动画看起来更丝滑一些,第二个就是增加事件逻辑。持续在构造函数中调用2个办法,代码如下:
class Carousel { constructor(options){ //省略了代码 // 增加了事件 this.handleEvents(); // 设置过渡成果 this.setTransition(); } //省略了代码}
咱们先来看最简略的setTransition办法,其实这个办法很简略,就是通过在head标签内增加一个style标签,通过insertRule办法增加款式。代码如下:
setTransition() { const styleElement = document.createElement('style'); document.head.appendChild(styleElement); const styleRule = `.carousel-item {transition: left ${this.speed}ms ease-in-out}` styleElement.sheet.insertRule(styleRule, 0);}
很显然这里是为每个轮播图元素增加过渡成果,接下来咱们来看绑定事件办法外部,咱们能够尝试思考一下,都有哪些事件呢?总结如下:
- 上一页与下一页
- 分页按钮组
- 滑动事件
- 键盘事件
- 轮播盒子元素的鼠标悬浮与鼠标移出事件
依据以上剖析,咱们的handleEvents办法就很好实现了,如下:
handleEvents() { // 鼠标从轮播盒上移开时持续轮播 this.container.addEventListener('mouseleave', this.start.bind(this)); // 鼠标挪动到轮播盒上暂停轮播 this.container.addEventListener('mouseover', this.pause.bind(this)); // 点击左侧控件向右滑动图片 this.carouselCtrlL.addEventListener('click', this.clickCtrl.bind(this)); // 点击右侧控件向左滑动图片 this.carouselCtrlR.addEventListener('click', this.clickCtrl.bind(this)); // 点击分页按钮组后滑动到对应的图片 for (let i = 0; i < this.carouselSigns.length; i++) { this.carouselSigns[i].setAttribute('slide-to', i); this.carouselSigns[i].addEventListener('click', this.clickSign.bind(this)); } // 监听键盘事件 if (this.monitorKeyEvent) { document.addEventListener('keydown', this.keyDown.bind(this)); } // 监听屏幕滑动事件 if (this.monitorTouchEvent) { this.container.addEventListener('touchstart', this.touchScreen.bind(this)); this.container.addEventListener('touchend', this.touchScreen.bind(this)); }}
这里有意思的点在于bind办法更改this对象,使得this对象指向以后轮播实例元素,还有一点就是咱们为每个分页按钮设置了一个slide-to的索引值,后续咱们就能够依据这个索引值来切换轮播图。
接下来,让咱们看看每一个事件对应的回调办法,首先是start办法,其实很容易就想到,start办法就是开始轮播,开始轮播也就是主动轮播,主动轮播咱们须要用到定时器,因而咱们的start函数就很好实现了,代码如下:
start() { const event = { srcElement: this.direction == 'left' ? this.carouselCtrlR : this.carouselCtrlL }; const clickCtrl = this.clickCtrl.bind(this); // 每隔一段时间模仿点击控件 this.interval = setInterval(clickCtrl, this.delay, event);}
这里有意思的点在于咱们的主动轮播,是间接去模仿点击上一页下一页进行切换的,除此之外,这里依据方向将srcElement元素作为事件对象传递,也就是说前面咱们会依据这个元素来做方向上的判断。
接下来咱们来看暂停函数,很简略,就是革除定时器即可,如下:
// 暂停轮播pause() { clearInterval(this.interval);}
接下来,让咱们来看clickCtrl办法,思考一下,咱们是如何批改以后轮播图的索引值的,失常状况下,比如说,咱们是向左滑动,索引值实际上就是将以后索引值相加,而后再判断是否超出了轮播图元素组的长度,重置索引值。
然而这里有一个更为奇妙的实现形式,那就是取模,将以后索引值加1而后与轮播图元素组的长度取模,这样也就保障了咱们的索引值始终不会超过轮播图元素组的长度。
如果是向右滑动,那么咱们应该是加上轮播图元素组的长度 - 1再取模,也就是与向左方向相同,依据以上剖析,咱们的代码就好实现了,如下:
// 解决点击控件事件clickCtrl(event) { if (!this.status) return; this.status = false; let fromIndex = this.curIndex, toIndex, direction; if (event.srcElement == this.carouselCtrlR) { toIndex = (this.curIndex + 1) % this.numItems; direction = 'left'; } else { toIndex = (this.curIndex + this.numItems - 1) % this.numItems; direction = 'right'; } this.slide(fromIndex, toIndex, direction); this.curIndex = toIndex;}
在这个办法外面还有一个slide办法,顾名思义就是轮播图的滑动办法,其实通篇下来,最外围的就是这个slide办法,这个办法次要做的逻辑就是依据索引值来切换class。代码如下:
slide(fromIndex, toIndex, direction) { let fromClass, toClass; if (direction == 'left') { this.carouselItems[toIndex].className = "carousel-item next"; fromClass = 'carousel-item active left'; toClass = 'carousel-item next left'; } else { this.carouselItems[toIndex].className = "carousel-item prev"; fromClass = 'carousel-item active right'; toClass = 'carousel-item prev right'; } this.carouselSigns[fromIndex].className = "carousel-sign-item"; this.carouselSigns[toIndex].className = "carousel-sign-item active"; setTimeout((() => { this.carouselItems[fromIndex].className = fromClass; this.carouselItems[toIndex].className = toClass; }).bind(this), 50); setTimeout((() => { this.carouselItems[fromIndex].className = 'carousel-item'; this.carouselItems[toIndex].className = 'carousel-item active'; this.status = true; // 设置为能够滑动 }).bind(this), this.speed + 50);}
这里分成了两块替换类名的逻辑,第一块是轮播图元素的替换类名,须要判断方向,第二块则是分页按钮组的类名替换逻辑,当然分页按钮组的逻辑是不须要替换类名的。
其实剖析到这里,这个轮播图的外围根本曾经实现了,接下来就是欠缺了,让咱们持续看分页按钮组的事件回调逻辑。
其实这里的逻辑也就是拿到索引值,后面为每个分页按钮组设置了一个slide-to属性,这里很显然就通过获取这个属性,而后生成slide办法所须要的参数,最初调用slide办法就行了。代码如下:
clickSign(event) { if (!this.status) return; this.status = false; const fromIndex = this.curIndex; const toIndex = parseInt(event.srcElement.getAttribute('slide-to')); const direction = fromIndex < toIndex ? 'left' : 'right'; this.slide(fromIndex, toIndex, direction); this.curIndex = toIndex;}
最初还剩两个逻辑,一个是键盘事件,另一个是滑动事件逻辑,咱们先来看键盘事件逻辑,键盘事件逻辑无非就是利用keyCode判断用户是否点击的是上一页和下一页,而后像主动开始轮播那样模仿调用上一页下一页按钮事件逻辑即可。代码如下:
keyDown(event) { if (event && event.keyCode == 37) { this.carouselCtrlL.click(); } else if (event && event.keyCode == 39) { this.carouselCtrlR.click(); }}
最初就是滑动事件了,滑动事件最难的点在于如何判断用户滑动的方向,其实咱们能够通过计算滑动角度来判断,这里的计算逻辑是来自网上的一个公式,感兴趣的能够查阅相干材料理解。咱们来看代码如下:
touchScreen(event) { if (event.type == 'touchstart') { this.startX = event.touches[0].pageX; this.startY = event.touches[0].pageY; } else { // touchend this.endX = event.changedTouches[0].pageX; this.endY = event.changedTouches[0].pageY; // 计算滑动方向的角度 const dx = this.endX - this.startX const dy = this.startY - this.endY; const angle = Math.abs(Math.atan2(dy, dx) * 180 / Math.PI); // 滑动间隔太短 if (Math.abs(dx) < 10 || Math.abs(dy) < 10) return; if (angle >= 0 && angle <= 45) { // 向右侧滑动屏幕,模仿点击左控件 this.carouselCtrlL.click(); } else if (angle >= 135 && angle <= 180) { // 向左侧滑动屏幕,模仿点击右控件 this.carouselCtrlR.click(); } }}
到此为止,咱们的一个轮播图就实现了,总结一下咱们学到了什么?
- CSS过渡成果
- CSS根本布局
javascript事件
- 鼠标悬浮与移出事件
- 滑动事件
- 键盘事件
- 元素点击事件
- javascript定时器
PS: 当前再也不必本人写轮播了,业务当中遇到,能够间接拿去用了,。
以上源码在这里。
以上示例在这里。