轮播图已成为网站、移动应用等交互界面中不可或缺的元素。轮播图能够高效地展示多张图片或内容,提升用户体验。JavaScript(JS)作为前端开发的核心技术之一,在轮播图的实现中发挥着重要作用。本文将深入解析JS轮播图源码,分析其工作原理,并提出优化策略,以期为前端开发者提供有益的参考。
一、JS轮播图源码解析
1. HTML结构
轮播图的HTML结构通常包含以下元素:
(1)轮播容器:用于承载轮播图片的容器。
(2)轮播按钮:用于切换轮播图片的按钮。
(3)轮播指示器:用于显示当前轮播图片位置的指示器。
以下是一个简单的HTML结构示例:
```html
在做移动端开发的时候,必不可少的是轮播图,下面这篇文章主要给大家介绍了关于利用纯JS实现移动端web轮播图的相关资料,重要的是结合Tween算法造轮子,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧。
前言
相信大家应该都知道,移动端的轮播图是我们比较常见的需求, 我们最快的实现方式往往是 使用第三方的代码, 例如 swiper , 但当遇到一些比较复杂的轮播图需求时, 往往是束手无策,不知道怎么改.
所以我们要尝试去自己造一些轮子, 以适应各种复杂多变的需求; 另外一点, 自己写的代码如果有bug是很容易修复的, 对自身的提高也很大.
在没有阅读swiper源码的过程下,我尝试自己实现一个简易而不失实用的移动端轮播图, 经过几个小时的思考和实践终于还是实现了(如图):
实现移动端的轮播图要比pc复杂一些,主要表现在以下几个方面:
1.轮播图要适应不同宽度/dpr的屏幕
2.需要使用 touch相关的事件
3.不同机型对 touch事件支持的不太一样,可能会有一些兼容性问题
4.手指移动图片一部分距离,剩下的距离需要自动完成
5.自动完成距离需要有 ease 时间曲线
但编程解决问题的思路都是差不多的,
我们在使用轮播图的时候可以仔细观察,通过现象看到本质:
我们在使用轮播图的时候可以仔细观察,通过现象看到本质:
手指放在图片上, 手指向左或者向右移动, 图片也随之移动;
手指移动的距离少时,图片自动复原位置;手指移动的距离多时,自动切换到下一张;
手指向左或者向右移动的快时,会切换到下一张;
图片轮播是无限循环的, 我们需要采用 3 1 2 3 1的方式来实现, 即 N+2张图来实现N张图的无限循环轮播
我们通过分析现象,可以提出一个基本实现方案:
1. 手指触摸事件可以通过 touchstart touchmove touchend 3个事件来实现
2.在手指 touchstart的时候我们需要记录 手指的x坐标, 可以使用 touch的pageX属性; 还有 这个时间点,
3.手指touchmove的时候我们也需要记录pageX,并且记录累计移动的距离 moveX
4.手指离开的时候,记录时间点, 根据前两步计算的 x方向移动的距离,时间点之差
5.通过比较x方向移动距离来判断移动方向, 以及是否应该切换到下一张图; 根据时间判断用户是否进行了左右扫动的操作
6.移动图片可以使用 translate3d来实现,开启硬件加速
7.移动一段距离需要 easeOut效果,我们可以使用 Tween算法中的easeOut来实现我们每次移动的距离; 当然也可以使用 js设置 transition动画
实现源码(仅供参考):
head头部样式
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=.5,maximum-scale=.5">
<title>移动端轮播图</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0
}
.banner {
overflow: hidden;
width: 100%;
height: 300px
}
.banner .img-wrap {
position: relative;
height: 100%
}
.banner img {
display: block;
position: absolute;
top: 0;
width: 100%;
height: 100%
}
</style>
</head>HTML结构
<p class="banner">
<p class="img-wrap" id="imgWrap">
<img src="images/banner_3.jpg" data-index="-1">
<img src="images/banner_1.jpg" data-index="0">
<img src="images/banner_2.jpg" data-index="1">
<img src="images/banner_3.jpg" data-index="2">
<img src="images/banner_1.jpg" data-index="3">
</p>
</p>JS代码1, easeOut动画式移动,
这里的 HTMLElement.prototype.tweenTranslateXAnimate ,是给所有的HTML元素类扩展的tweenTranslateXAnimate方法
移动一段距离我们需要使用定时器来帮助我们完成,这个重复的操作
<script>
HTMLElement.prototype.tweenTranslateXAnimate = function (start, end, callback) {
var duration = 50;
var t = 0;
var vv = end - start;
var Tween = {
Quad: {
easeOut: function (t, b, c, d) {
return -c * (t /= d) * (t - 2) + b;
}
}
};
this.timer = setInterval(function () {
var dis = start + Tween.Quad.easeOut(++t, 0, vv, duration);
this.style.transform = 'translate3d(' + dis + 'px, 0, 0)';
if (vv > 0 && parseInt(this.style.transform.slice(12)) >= end) {
this.style.transform = 'translate3d(' + parseInt(dis) + 'px, 0, 0)';
clearInterval(this.timer);
callback && callback();
}
if (vv < 0 && parseInt(this.style.transform.slice(12)) <= end) {
this.style.transform = 'translate3d(' + parseInt(dis) + 'px, 0, 0)';
clearInterval(this.timer);
callback && callback();
}
}.bind(this), 4);
}
</script>touch事件部分
<script>
~function () {
var lastPX = 0; // 上一次触摸的位置x坐标, 需要计算出手指每次移动的一点点距离
var movex = 0; // 记录手指move的x方向值
var imgWrap = document.getElementById('imgWrap');
var startX = 0; // 开始触摸时手指所在x坐标
var endX = 0; // 触摸结束时手指所在的x坐标位置
var imgSize = imgWrap.children.length - 2; // 图片个数
var t1 = 0; // 记录开始触摸的时刻
var t2 = 0; // 记录结束触摸的时刻
var width = window.innerWidth; // 当前窗口宽度
var nodeList = document.querySelectorAll('#imgWrap img'); // 所有轮播图节点数组 NodeList
// 给图片设置合适的left值, 注意 querySelectorAll返回 NodeList, 具有 forEach方法
nodeList.forEach(function (node, index) {
node.style.left = (index - 1) * width + 'px';
});
/**
* 移动图片到当前的 tIndex索引所在位置
* @param {number} tIndex 要显示的图片的索引
* */
function toIndex(tIndex) {
var dis = -(tIndex * width);
var start = parseInt(imgWrap.style.transform.slice(12));
// 动画移动
imgWrap.tweenTranslateXAnimate(start, dis, function () {
setTimeout(function () {
movex = dis;
if (tIndex === imgSize) {
imgWrap.style.transform = 'translate3d(0, 0, 0)';
movex = 0;
}
if (tIndex === -1) {
imgWrap.style.transform = 'translate3d(' + width * (1 - imgSize) + 'px, 0, 0)';
movex = -width * (imgSize - 1);
}
}, 0);
});
}
/**
* 处理各种触摸事件 ,包括 touchstart, touchend, touchmove, touchcancel
* @param {Event} evt 回调函数中系统传回的 js 事件对象
* */
function touch(evt) {
var touch = evt.targetTouches[0];
var tar = evt.target;
var index = parseInt(tar.getAttribute('data-index'));
if (evt.type === 'touchmove') {
var di = parseInt(touch.pageX - lastPX);
endX = touch.pageX;
movex += di;
imgWrap.style.webkitTransform = 'translate3d(' + movex + 'px, 0, 0)';
lastPX = touch.pageX;
}
if (evt.type === 'touchend') {
var minus = endX - startX;
t2 = new Date().getTime() - t1;
if (Math.abs(minus) > 0) { // 有拖动操作
if (Math.abs(minus) < width * 0.4 && t2 > 500) { // 拖动距离不够,返回!
toIndex(index);
} else { // 超过一半,看方向
console.log(minus);
if (Math.abs(minus) < 20) {
console.log('距离很短' + minus);
toIndex(index);
return;
}
if (minus < 0) { // endX < startX,向左滑动,是下一张
toIndex(index + 1)
} else { // endX > startX ,向右滑动, 是上一张
toIndex(index - 1)
}
}
} else { //没有拖动操作
}
}
if (evt.type === 'touchstart') {
lastPX = touch.pageX;
startX = lastPX;
endX = startX;
t1 = new Date().getTime();
}
return false;
}
imgWrap.addEventListener('touchstart', touch, false);
imgWrap.addEventListener('touchmove', touch, false);
imgWrap.addEventListener('touchend', touch, false);
imgWrap.addEventListener('touchcancel', touch, false);
}();
</script>在触摸事件中最关键的参数是 pageX参数, 记录x的位置.
当然这只是一个demo,还需要进一步的优化和封装, 以便于我们用在真实的项目.
本demo仅仅是提供了一个解决问题的思路, 有了这个思路,相信各种复杂的需求也得以解决...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>最简单的轮播广告</title>
<style>
body, div, ul, li {
margin: 0;
padding: 0;
}
ul {
list-style-type: none;
}
body {
background: #000;
text-align: center;
font: 12px/20px Arial;
}
#box {
position: relative;
width: 492px;
height: 172px;
background: #fff;
border-radius: 5px;
border: 8px solid #fff;
margin: 10px auto;
}
#box .list {
position: relative;
width: 490px;
height: 170px;
overflow: hidden;
border: 1px solid #ccc;
}
#box .list li {
position: absolute;
top: 0;
left: 0;
width: 490px;
height: 170px;
opacity: 0;
transition: opacity 0.5s linear
}
#box .list li.current {
opacity: 1;
}
#box .count {
position: absolute;
right: 0;
bottom: 5px;
}
#box .count li {
color: #fff;
float: left;
width: 20px;
height: 20px;
cursor: pointer;
margin-right: 5px;
overflow: hidden;
background: #F90;
opacity: 0.7;
border-radius: 20px;
}
#box .count li.current {
color: #fff;
opacity: 0.7;
font-weight: 700;
background: #f60
}
</style>
</head>
<body>
<div id="box">
<ul class="list">
<li class="current" style="opacity: 1;"><img src="img/images04/01.jpg" width="490" height="170"></li>
<li style="opacity: 0;"><img src="img/images04/02.jpg" width="490" height="170"></li>
<li style="opacity: 0;"><img src="img/images04/03.jpg" width="490" height="170"></li>
<li style="opacity: 0;"><img src="img/images04/04.jpg" width="490" height="170"></li>
<li style="opacity: 0;"><img src="img/images04/05.jpg" width="490" height="170"></li>
</ul>
<ul class="count">
<li class="current">1</li>
<li class="">2</li>
<li class="">3</li>
<li class="">4</li>
<li class="">5</li>
</ul>
</div>
<script>
var box=document.getElementById('box');
var uls=document.getElement**yTagName('ul');
var imgs=uls[0].getElement**yTagName('li');
var btn=uls[1].getElement**yTagName('li');
var i=index=0; //中间量,统一声明;
var play=null;
console.log(box,uls,imgs,btn);//获取正确
//图片切换, 淡入淡出效果我是用(transition: opacity 0.8s linear)做的,不纠结、简单 在css里面
function show(a){ //方法定义的是当传入一个下标时,按钮和图片做出对的反应
for(i=0;i<btn.length;i++ ){
btn[i].className=''; //很容易看懂吧?每个按钮都先设置成看不见,然后把当前按钮设置成可见。
btn[a].className='current';
}
for(i=0;i<imgs.length;i++){ //把图片的效果设置和按钮相同
imgs[i].style.opacity=0;
imgs[a].style.opacity=1;
}
}
//切换按钮功能,响应对应图片
for(i=0;i<btn.length;i++){
btn[i].index=i; //不知道你有没有发现,循环里的方法去调用循环里的变量体i,会出现调到的不是i的变动值的问题。所以我先在循环外保存住
btn[i].onmouseover=function(){
show(this.index);
clearInterval(play); //这就是最后那句话追加的功能
}
}
//自动轮播方法
function autoPlay(){
play=setInterval(function(){ //这个paly是为了保存定时器的,变量必须在全局声明 不然其他方法调不到 或者你可以调用auto.play 也许可以但是没时间试了
index++;
index>=imgs.length&&(index=0);//可能有优先级问题,所以用了括号,没时间测试了。
show(index);
},1000)
}
autoPlay();//马上调用,我试过用window.onload调用这个方法,但是调用之后影响到了其他方法,使用autoPlay所以只能这样调用了
//div的鼠标移入移出事件
box.onmouseover=function(){
clearInterval(play);
};
box.onmouseout=function(){
autoPlay();
};
//按钮下标也要加上相同的鼠标事件,不然图片停止了,定时器没停,会突然闪到很大的数字上。 貌似我可以直接追加到之前定义事件中。
</script>
</body>
</html>
这是我以前写过的一个,样式你改一下就OK了
<div class="fbanner widget3924">
<div id="i_focus">
<div id="i_focus_pic">
<ul id="i_focus_piclist">
<li style="display: none;">
<a href="" target="_blank" style="background: url(/templates/runlinjinguan/images/banner1.jpg) center 0 no-repeat;">
</a>
</li>
<li style="display: list-item;">
<a href="" target="_blank" style="background: url(/templates/runlinjinguan/images/banner4.jpg) center 0 no-repeat;">
</a>
</li>
<li style="display: none;">
<a href="" target="_blank" style="background: url(/templates/runlinjinguan/images/banner3.jpg) center 0 no-repeat;">
</a>
</li>
<li style="display: none;">
<a href="" target="_blank" style="background: url(/templates/runlinjinguan/images/banner2.jpg) center 0 no-repeat;">
</a>
</li>
</ul>
<div id="i_focus_opdiv">
</div>
<ul id="i_focus_btn">
<li id="p0" class="">
<span>
</span>
</li>
<li id="p0" class="i_cur">
<span>
</span>
</li>
<li id="p0" class="">
<span>
</span>
</li>
<li id="p0" class="">
<span>
</span>
</li>
</ul>
</div>
</div>
<script type="text/javascript" src="/templates/runlinjinguan/js/flash.js"></script>
<script type="text/javascript">
$("#i_focus_btn").find("li").eq(0).addClass("i_cur");
</script>
</div>
引用的js文件
// JavaScript Document
//flash js
$(document).ready(function () {
var i_curIndex = 0;
var beauBeauSlide; //函数对象
var i_curID = 0; //取得鼠标下方的对象ID
var pictureID = 0; //索引ID
$("#i_focus_piclist li").eq(0).show(); //默认
autoScroll();
$("#i_focus_btn li").hover(function (e) {
StopScrolll();
$("#i_focus_btn li").removeClass("i_cur") //所有的li去掉当前的样式加上正常的样式
$(this).addClass("i_cur"); //而本身则加上当前的样式去掉正常的样式
i_curID = $(this).attr("id"); //取当前元素的ID
pictureID = $("#i_focus_btn li").index(this);// i_curID.substring(i_curID.length - 1); //取最后一个字符
$("#i_focus_piclist li").eq(pictureID).fadeIn("slow"); //本身显示
$("#i_focus_piclist li").not($("#i_focus_piclist li")[pictureID]).hide(); //除了自身别的全部隐藏
$("#i_focus_tx li").hide();
$("#i_focus_tx li").eq(pictureID).show();
},
function () {
//当鼠标离开对象的时候获得当前的对象的ID以便能在启动自动时与其同步
i_curID = $(this).attr("id"); //取当前元素的ID
pictureID = i_curID.substring(i_curID.length - 1); //取最后一个字符
i_curIndex = pictureID;
autoScroll();
});
//自动滚动
function autoScroll() {
var myNubli = $("#i_focus_btn li").size();
if (myNubli > 1) {
$("#i_focus_btn li:last").removeClass("i_cur");
$("#i_focus_tx li:last").hide();
$("#i_focus_btn li").eq(i_curIndex).addClass("i_cur");
$("#i_focus_btn li").eq(i_curIndex - 1).removeClass("i_cur");
$("#i_focus_tx li").eq(i_curIndex).show();
$("#i_focus_tx li").eq(i_curIndex - 1).hide();
$("#i_focus_piclist li").eq(i_curIndex).fadeIn("slow");
$("#i_focus_piclist li").eq(i_curIndex - 1).hide();
i_curIndex++;
i_curIndex = i_curIndex >= myNubli ? 0 : i_curIndex;
beauBeauSlide = setTimeout(autoScroll, 5000);
}
}
function StopScrolll() //当鼠标移动到对象上面的时候停止自动滚动
{
clearTimeout(beauBeauSlide);
}
});
//第二个渐隐幻灯开始
var defaultOpts = {
interval: 3000,
fadeInTime: 800,
fadeOutTime: 500
};
var _titles = $("ul.slide-txt li");
var _titles_bg = $("ul.op li");
var _bodies = $("ul.slide-pic li");
var _count = _titles.length;
var _current = 0;
var _intervalID = null;
var stop = function () {
window.clearInterval(_intervalID);
};
var slide = function (opts) {
if (opts) {
_current = opts.current || 0;
} else {
_current = (_current >= (_count - 1)) ? 0 : (++_current);
};
_bodies.filter(":visible").fadeOut(defaultOpts.fadeOutTime,
function () {
_bodies.eq(_current).fadeIn(defaultOpts.fadeInTime);
_bodies.removeClass("cur").eq(_current).addClass("cur");
});
_titles.removeClass("cur").eq(_current).addClass("cur");
_titles_bg.removeClass("cur").eq(_current).addClass("cur");
}; //endof slide
var go = function () {
stop();
_intervalID = window.setInterval(function () {
slide();
},
defaultOpts.interval);
}; //endof go
var itemMouseOver = function (target, items) {
stop();
var i = $.inArray(target, items);
slide({
current: i
});
}; //endof itemMouseOver
_titles.hover(function () {
if ($(this).attr('class') != 'cur') {
itemMouseOver(this, _titles);
} else {
stop();
}
},
go);
//_titles_bg.hover(function() { itemMouseOver(this, _titles_bg); }, go);
_bodies.hover(stop, go);
go();
var slideX = {
_this: $('.catalog .imgbox'),
_btnLeft: $('.catalog .left'),
_btnRight: $('.catalog .right'),
init: function () {
slideX._btnLeft.click(slideX.slideLeft);
slideX._btnRight.click(slideX.slideRight);
},
slideLeft: function () {
slideX._btnLeft.unbind('click', slideX.slideLeft);
for (i = 0; i < 2; i++) {
slideX._this.find('li:last').prependTo(slideX._this);
}
slideX._this.css('marginLeft', -224);
slideX._this.animate({
'marginLeft': 0
},
500,
function () {
slideX._btnLeft.bind('click', slideX.slideLeft);
});
return false;
},
slideRight: function () {
slideX._btnRight.unbind('click', slideX.slideRight);
slideX._this.animate({
'marginLeft': -224
},
500,
function () {
slideX._this.css('marginLeft', '0');
for (i = 0; i < 2; i++) {
slideX._this.find('li:first').appendTo(slideX._this)
}
slideX._btnRight.bind('click', slideX.slideRight);
});
return false;
}
}
$(document).ready(function () {
slideX.init();
})
$(document).ready(function () {
var newTime = new Date();
var newTime = newTime.getTime();
var $imgTmp = $('#topromotion').find('img:first');
var osrc = $imgTmp.attr('src');
$imgTmp.attr('src', osrc + '?' + newTime);
});
希望对你有帮助!
文章已关闭评论!
2025-05-09 23:05:13
2025-05-09 22:45:22
2025-05-09 22:33:25
2025-05-09 22:22:51
2025-05-09 22:01:11
2025-05-09 21:15:02
2025-05-09 21:02:22
2025-05-09 20:43:13