原生JS实现移动端web轮播图详解(结合Tween算法造轮子)


Posted in Javascript onSeptember 10, 2017

前言

相信大家应该都知道,移动端的轮播图是我们比较常见的需求, 我们最快的实现方式往往是 使用第三方的代码, 例如 swiper , 但当遇到一些比较复杂的轮播图需求时, 往往是束手无策,不知道怎么改.      

所以我们要尝试去自己造一些轮子, 以适应各种复杂多变的需求;  另外一点, 自己写的代码如果有bug是很容易修复的, 对自身的提高也很大.

在没有阅读swiper源码的过程下,我尝试自己实现一个简易而不失实用的移动端轮播图, 经过几个小时的思考和实践终于还是实现了(如图):

原生JS实现移动端web轮播图详解(结合Tween算法造轮子)

实现移动端的轮播图要比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结构

<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代码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仅仅是提供了一个解决问题的思路, 有了这个思路,相信各种复杂的需求也得以解决...

本文中使用的 tween算法来实现 ease-out效果 ,也可以使用 transtion动画实现, 代码更加简洁,参见轮播图优化篇: https://3water.com/article/123304.htm

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
复制本贴标题和地址的js代码
Jul 01 Javascript
动态加载js和css(外部文件)
Apr 17 Javascript
javascript使用定时函数实现跳转到某个页面
Dec 25 Javascript
详解javascript事件冒泡
Jan 09 Javascript
JavaScript中匿名函数的用法及优缺点详解
Jun 01 Javascript
微信小程序 简单教程实例详解
Jan 13 Javascript
Node.js设置CORS跨域请求中多域名白名单的方法
Mar 28 Javascript
JS表单提交验证、input(type=number) 去三角 刷新验证码
Jun 21 Javascript
使用cropper.js裁剪头像的实例代码
Sep 29 Javascript
vue组件name的作用小结
May 23 Javascript
使用 js 简单的实现 bind、call 、aplly代码实例
Sep 07 Javascript
JavaScript实现移动端弹窗后禁止滚动
May 25 Javascript
vue系列之动态路由详解【原创】
Sep 10 #Javascript
cocos creator Touch事件应用(触控选择多个子节点的实例)
Sep 10 #Javascript
把JavaScript代码改成ES6语法不完全指南(分享)
Sep 10 #Javascript
js 发布订阅模式的实例讲解
Sep 10 #Javascript
node.js 发布订阅模式的实例
Sep 10 #Javascript
基于node.js的fs核心模块读写文件操作(实例讲解)
Sep 10 #Javascript
深入浅出webpack教程系列_安装与基本打包用法和命令参数详解
Sep 10 #Javascript
You might like
发挥语言的威力--融合PHP与ASP
2006/10/09 PHP
PHP文件上传之多文件上传的实现思路
2016/01/27 PHP
php编程实现简单的网页版计算器功能示例
2017/04/26 PHP
PHP实现的mysql操作类【MySQL与MySQLi方式】
2017/10/07 PHP
YII2框架中ActiveDataProvider与GridView的配合使用操作示例
2020/03/18 PHP
Nigma vs AM BO3 第二场2.13
2021/03/10 DOTA
JavaScript入门学习书籍推荐
2008/06/12 Javascript
js 遍历json返回的map内容示例代码
2013/10/29 Javascript
JQuery异步获取返回值中文乱码的解决方法
2015/01/29 Javascript
详解Webwork中Action 调用的方法
2016/02/02 Javascript
jQuery中页面返回顶部的方法总结
2016/12/30 Javascript
js+css3实现旋转效果
2017/01/20 Javascript
关于webpack2和模块打包的新手指南(小结)
2017/08/07 Javascript
vue小图标favicon不显示的解决方案
2017/09/19 Javascript
详解Vue快速零配置的打包工具——parcel
2018/01/16 Javascript
Angular搜索场景中使用rxjs的操作符处理思路
2018/05/30 Javascript
原生JS封装_new函数实现new关键字的功能
2018/08/12 Javascript
vue-openlayers实现地图坐标弹框效果
2020/09/24 Javascript
vue中如何自定义右键菜单详解
2020/12/08 Vue.js
Python 获取新浪微博的最新公共微博实例分享
2014/07/03 Python
举例讲解如何在Python编程中进行迭代和遍历
2016/01/19 Python
python写一个md5解密器示例
2018/02/23 Python
超简单使用Python换脸实例
2019/03/27 Python
python实现引用其他路径包里面的模块
2020/03/09 Python
python实现按键精灵找色点击功能教程,使用pywin32和Pillow库
2020/06/04 Python
通过实例解析python and和or使用方法
2020/11/14 Python
加拿大领先的冒险和户外零售商:Atmosphere
2017/12/19 全球购物
Bally巴利英国官网:经典瑞士鞋履、手袋及配饰奢侈品牌
2018/05/07 全球购物
请问如下代码执行后a和b的值分别是什么
2016/05/05 面试题
听课评语大全
2014/04/30 职场文书
幼儿教师演讲稿
2014/05/06 职场文书
学校学习雷锋活动总结
2014/07/03 职场文书
公务员政审个人总结
2015/02/12 职场文书
2015年创先争优工作总结
2015/05/23 职场文书
公司2015年终工作总结
2015/05/26 职场文书
Python中re模块的元字符使用小结
2022/04/07 Python