原生JS实现移动端Web轮播图:基于Tween算法的完整解析
本文详细解析如何使用原生JavaScript实现移动端Web轮播图。通过结合Touch事件监听与Tween算法中的easeOut缓动函数,解决屏幕适配、手势交互及平滑过渡等核心问题。文章提供了从HTML结构到JS逻辑的完整代码示例,重点讲解如何利用translate3d开启硬件加速,并通过N+2张图的策略实现无限循环滚动,为处理复杂轮播需求提供底层实现思路。
移动端轮播图实现概述
前言
在移动端开发中,轮播图是极为常见且核心的功能模块。通常情况下,开发者倾向于直接引入Swiper等成熟的第三方库以快速实现功能。然而,当业务需求涉及高度定制化的交互或特殊的视觉效果时,现有库往往难以满足,导致修改困难。
因此,掌握自行构建基础组件的能力至关重要。这不仅能够灵活应对复杂多变的产品需求,还能在遇到Bug时更快速地定位和修复问题,从而显著提升个人技术能力与代码掌控力。
在未深入研读Swiper源码的前提下,笔者尝试独立实现一个兼具实用性与轻量级的移动端轮播图组件。经过数小时的逻辑推演与代码实践,最终实现了如图所示的效果:

移动端实现难点分析
相较于PC端,移动端轮播图的实现复杂度显著提升,主要体现在以下几个维度:
1. 轮播图必须兼容不同宽度及不同DPR(设备像素比)的屏幕显示。
2. 必须准确处理Touch系列相关事件,以捕捉用户的手势操作。
3. 不同移动设备对Touch事件的支持存在差异,可能引发兼容性问题。
4. 当手指拖动图片移动一定距离后,剩余的距离需要由程序自动完成滑动。
5. 自动滑动的过程必须包含ease(缓动)时间曲线,以提供自然的视觉体验。
尽管实现细节复杂,但解决此类问题的编程思路具有共通性。
交互现象与核心逻辑
我们在日常使用轮播图时,可以通过观察现象来推导其底层逻辑:
- 具体表现为以下交互行为:
- 当手指在图片上向左或向右滑动时,图片会跟随手指的移动轨迹进行位移。
- 若手指移动的距离较短,松开后图片会自动复原至初始位置;若移动距离超过阈值,则自动切换至下一张图片。
- 若手指左右滑动的速度较快,系统会判定为快速滑动操作,直接切换至下一张图片。
- 为了实现图片的无限循环播放,我们需要采用“3+1+2”的结构策略,即通过N+2张图的排列方式来实现N张图的无限循环轮播效果。
技术方案与实现步骤
基于上述现象分析,我们可以提出以下基本实现方案:
1. 利用touchstart、touchmove和touchend三个Touch事件来完整捕获用户的触摸交互过程。
2. 在touchstart事件触发时,需记录手指的X坐标(通过Touch对象的pageX属性获取)以及当前的时间戳。
3. 在touchmove事件触发期间,持续记录pageX值,并累计算手指在X方向上的累计移动距离moveX。
4. 在手指离开屏幕(touchend)时,记录结束时间戳,并结合前两步获取的X方向移动距离与时间差进行计算。
5. 通过比较X方向的移动距离来判断滑动方向,并依据是否达到切换阈值决定是否需要切换到下一张图片;同时根据时间差判断用户是否执行了快速扫动操作。
6. 图片的位移操作建议使用translate3d来实现,以开启GPU硬件加速,提升渲染性能。
7. 对于移动一段距离后的缓动效果,可以使用Tween算法中的easeOut函数来计算每次移动的增量;当然,也可以利用CSS transition动画来实现,但Tween算法提供了更精细的控制。
核心代码实现解析
以下是具体的实现源码,供参考学习:
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结构搭建
<div class="banner">
<div 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">
</div>
</div>
JS代码第一部分:实现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旨在提供一种解决问题的核心思路,掌握该思路后,相信能够应对各种复杂的轮播图需求。
本文中采用Tween算法来实现ease-out效果,若追求代码简洁,也可使用transition动画实现,具体优化方案可参见轮播图优化篇:https://www.jb51.net/article/123304.htm
总结与扩展
总结
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对本站的支持。
JS在setTimeout或Ajax回调中打开新标签页失败怎么处理
2026-11-05 15:50:12
JS直播协议选型:HLS、FLV与WebRTC的延迟与兼容性对比
2026-11-05 12:16:14
JS实现回到顶部:scrollTop赋值与浏览器兼容性处理
2026-11-04 23:43:35
JS使用classList动态添加、移除和切换CSS类名
2026-11-04 23:40:58
在 D3.js 中实现平滑过渡动画的技巧
2026-11-03 23:46:09
