一、前言
「回顾2022,展望2023,我正在参与2022年终总结征文大赛活动」
目前,在移动端
或是网页端
对于轮播图的需求并不可少,我们有许多代替好的框架或者组件实现这个功能,但不如我们今天自己着手定制一个原生js轮播图
,顺便总结提高一下相关知识点。
轮播图
目前出现在各大购物网站的首页用来展示商品信息
,现在也出了很多插件帮助我们更加便捷的实现多种多样的轮播图
。
京东
天猫
淘宝
纯手写轮播图
对于初学者可能很难,也会有公司面试出轮播图来考察面试者的基础能力
了。其实轮播图只要细分成几个小的模块
,逐步实现
起来还是比较简单的。
下面,带大家来实现一下简易的轮播图
。
二、动画基础
我们都知道轮播图是有一个动画过程的,那如何封装实现这一个动画函数呢?
1. 定时器
前端的定时器有两种,一种是一次性定时器setTimeout
,一种是重复性定时器setInterval
如上图所示,setTimeout
你只有点击一下按钮物体才会向前跑过了15ms就向前跑10px
。而对于setInterval
只需要点击一次便会每间隔15ms执行一次,页面中的倒计时效果也是这样做的。
所以,我们的轮播图肯定要选择setInterval
第二种方案了。
2. left与offsetLeft
left
就是我们加了定位的物体距离左侧的位置,这里可以参考一些常见的定位属性。
offsetLeft
是一个只读属性(不能修改值),返回当前元素相对于 offsetParent
节点左边界的偏移像素值。当前父亲节点是整个页面,所以只需要把offsetLeft
赋值给object
的left
偏移量就行了。
3. 封装函数
有了定时器之后,我们就要考虑把这段代码封装成动画函数,想要的时候调用就行了。
封装函数要注意参数问题,那么我们定时器要传进来什么参数呢?
物体
目标点
回调函数
3.1 物体
物体为我们要移动的dom
元素,就是上面哪个在屏幕行动的粉色盒子。
3.2 目标点
上面的盒子运动方向是有了,但是它一旦执行起来一个劲的向前冲,这也不行呀!
所以,我们试着修改一下上面的代码,比如让它到达800px
就清楚定时器让它停下来,否则继续向前运动。
这时候我们只需要在定时器加了一个if else
判断就行了。
if(object.offsetLeft==500){
clearInterval(timer);
}
else{
object.style.left=object.offsetLeft+10+'px';
}
目标点满足了,但是还有两个疑问?
- 到达800px后,如何后退?
- 如何改变物体的运动曲线?
这两个问题只需要一个解决方案,只需要加一个step
的变量,代表每一次移动的值代替固定的10px
。
这个变量只需要改成下面的公式计算就行:
var step = (target - obj.offsetLeft) / 10;
我们一开始的运动曲线是这样的,匀速状态:
通过这样的公式既可以保证物体可以后退,又满足了先加速后减速的曲线运动
。
这个公式如果仔细查看css
的距离其实会有偏差的,比实际的目标点偏小,由于浮点数
的计算问题,所以要使用公式,做近似处理。
step = step > 0 ? Math.ceil(step) : Math.floor(step);
3.3 回调函数
回调函数
顾名思义当我调用这个定时器函数时,到达目标点了,定时器被清空了,就可以执行我传入的回调函数了。
下面让到达800px
的物体,进行变色效果。
当然,后面也可以根据自己的要求实现特点的效果。
4.封装
我们把上面写好的代码统一到一个animate.js
的文件中,需要的时候引入就行了。
function animate(obj, target, callback) {
//排他原理
clearInterval(obj.timer);
obj.timer = setInterval(function () {
//步长
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
clearInterval(obj.timer);
//回调函数
callback && callback();
}
else {
obj.style.left = obj.offsetLeft + step + 'px';
}
}, 15);
}
为了优化代码,防止用户过度点击,我们要在定时器执行的一开始通过排他原理
预先清空之前的定时器,然后再执行我们自己的定时器。
三、基础结构
搭建html
页面的结构其实很简单,我们主要把它分成三个部分,分别是中间的焦点图
、左右两侧的按钮
、底部的小圆点
。
3.1 焦点图
<ul class="rotate-middle">
<li><a href="#"><img src="images/1.jpg" alt=""></a></li>
<li><a href="#"><img src="images/2.jpg" alt=""></a></li>
<li><a href="#"><img src="images/3.jpg" alt=""></a></li>
<li><a href="#"><img src="images/4.jpg" alt=""></a></li>
<li><a href="#"><img src="images/5.jpg" alt=""></a></li>
</ul>
焦点图在底部先定义一个400*300
的盒子,盒子里面放入一张张的图片。这时候要注意每一个li
加上浮动,ul
盒子的大小也需要伸长到足够容纳这一行排列图片
的大小。
3.2 按钮
<button class="btn-lt"><</button>
<button class="btn-rt">></button>
按钮我没采用字体图标
、伪元素
之类的,直接粗暴的使用普通的< >
了,样式就合适的更改就行了。
按钮额外要注意添加z-index
提高位置,防止被图片压住了。
3.3 小圆点
<ol class="rotate-bottom">
<li class="current"></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ol>
底部小圆点
现在我是直接在html
里面写了,但是后面在js
代码中会根据实际的图片数量实时更新。
3.4 总结
这三个基础的结构统一采用position
定位(子绝父相),并且对于我们的ul
也要加一个position: absolute;
,因为后面要对这个盒子进行动画移动。
四、按钮显示
这部分我们的要求就是,让鼠标移动到图片
的时候,按钮显示,离开不显示。正式写代码之前我们先来弄清楚一个问题?
mouseenter
和mouseover
有什么区别?
推荐文章:mouseenter与mouseover为何这般纠缠不清?
看完之后,选择mouseenter
避免冒泡,获取时间源后,直接添加程序执行,按钮的隐藏还是显示直接使用display
。
focus.addEventListener('mouseenter', function () {
lt.style.display = 'block';
rt.style.display = 'block';
});
focus.addEventListener('mouseleave', function () {
lt.style.display = 'none';
rt.style.display = 'none';
});
五、圆点
5.1 生成
for (var i = 0; i < ul.children.length; i++) {
//创建ol li
var cloneli = this.document.createElement('li');
ol.appendChild(cloneli);
}
一开始,我们在html
没给圆点,后面在js
代码中根据图片的数量生成li
。
5.2 属性
//自定义属性
ol.children[i].setAttribute('index', i);
为了后面更好的移动轮播图,我们需要给每一个ol li
定义一个属性index
,这里我们是5
张图片,index
范围就是0~4
。
5.3 移动
//绑定事件
ol.children[i].addEventListener('click', function () {
// 排他原理
for (var j = 0; j < ol.children.length; j++) {
ol.children[j].className = '';
}
this.className = 'current';
index = this.getAttribute('index');
num = index;
circle = index;
animate(ul, -fixWidth * index);
})
这时候,我们每点击一个圆点就获取它当前的index
的值,然后调用动画函数传入目标值 ul
距离- 盒子的大小 * inddex
,就可以移动图片了。
六、按钮
6.1 准备
左右两侧按钮移动本质上是一致的,我们先来做右侧按钮。预期目标是我每点击一次右侧按钮
,图片就会向后移动一张。
那么这里我们还需要给每个图片也加上自定义属性
吗?
其实不用,自定义一个变量num
,随着点击的次数++
就行了。
6.2 出错
如上面的图片一样,有几个问题:
- 图片是动了,但是小圆点没动?
- 到了最后一张图片,怎么返回?
下面,我们一个个解决。
6.2.1 小圆点跟随
小圆点跟随简单,我们也定义一个变量circle
,当按钮每点击一次,circle++
,然后让对应的圆点填充颜色就行了。
注意,这里和num
我们要改善一下边界问题,到达最后一张图片是就不能继续++
,要归零
才行。
if (circle == 4) {
circle = 0;
}
else {
circle++;
}
6.2.2 图片返回
其实,当我们到达最后一张图片再点击是不连贯的,失去了平缓过度的效果。其实,我们可以在最后一张后面克隆
第一张图片加上去。
当图片向后移动时,会过渡到最后一张(也就是第一张的克隆版本),这个时候再点击我们就快速回到第一张图片就行了。
//节点操作,复制照片
var cloneimg = ul.children[0].cloneNode(true);
ul.appendChild(cloneimg);
6.3 bug
如上图,我们点击圆点到达第三张图,然后点击右侧的按钮,没回到第四张图,为啥又退回了第二张图片。这主要是因为我们ol li
的index
和num cirle
没同步导致的。
如何改善,很简单,把index
实时赋值给num cirle
就可以改善了。
左侧按钮,直接复制粘贴,改几个数值就行了,我不细讲了。
七、定时器
为了让轮播图按时移动,我们需要在最后加一个定时器效果,其实很简单,定时器内部直接调用右侧按钮的代码就行了。
var timer = this.setInterval(function () {
rt.click();
}, 1000);
每次间隔一秒,图片向右移动一张,鼠标接触,停止轮播图,鼠标移开,继续轮播图。
八、总结
本篇文章主要实现了一个简易的轮播图效果,下次再见!