原生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 相关文章推荐
ie9 提示'console' 未定义问题的解决方法
Mar 20 Javascript
修改或扩展jQuery原生方法的代码实例
Jan 13 Javascript
jQuery中empty()方法用法实例
Jan 16 Javascript
javascript实现页面刷新时自动清空表单并选中的方法
Jul 18 Javascript
jquery实现树形菜单完整代码
Dec 29 Javascript
JavaScript基础语法之js表达式
Jun 07 Javascript
JavaScript Ajax编程 应用篇
Jul 02 Javascript
javascript实现鼠标点击页面 移动DIV
Dec 02 Javascript
ES6中Array.find()和findIndex()函数的用法详解
Sep 16 Javascript
在Node.js下运用MQTT协议实现即时通讯及离线推送的方法
Jan 24 Javascript
angular异步验证防抖踩坑实录
Dec 01 Javascript
解决Vue大括号字符换行踩的坑
Nov 09 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实现的在线人员函数库
2008/04/09 PHP
php 接口类与抽象类的实际作用
2009/11/26 PHP
PHP下利用header()函数设置浏览器缓存的代码
2010/09/01 PHP
PHP抓屏函数实现屏幕快照代码分享
2014/01/02 PHP
PHP速成大法
2015/01/30 PHP
随机显示经典句子或诗歌的javascript脚本
2007/08/04 Javascript
利用location.hash实现跨域iframe自适应
2010/05/04 Javascript
JS判断浏览器是否支持某一个CSS3属性的方法
2014/10/17 Javascript
jquery实现模拟百分比进度条渐变效果代码
2015/10/29 Javascript
javascript定义类和类的实现实例详解
2015/12/01 Javascript
JavaScript模拟数组合并concat
2016/03/06 Javascript
详解Node.js模块间共享数据库连接的方法
2016/05/24 Javascript
jQuery插件Echarts实现的渐变色柱状图
2017/03/23 jQuery
vue组件 $children,$refs,$parent的使用详解
2017/07/31 Javascript
Cropper.js 实现裁剪图片并上传(PC端)
2017/08/20 Javascript
jquery实现二级导航下拉菜单效果实例
2019/05/14 jQuery
解决layui-table单元格设置为百分比在ie8下不能自适应的问题
2019/09/28 Javascript
实现python版本的按任意键继续/退出
2016/09/26 Python
Python如何抓取天猫商品详细信息及交易记录
2018/02/23 Python
和孩子一起学习python之变量命名规则
2018/05/27 Python
Python中Numpy包的安装与使用方法简明教程
2018/07/03 Python
django 中QuerySet特性功能详解
2019/07/25 Python
使用 Python 在京东上抢口罩的思路详解
2020/02/27 Python
Python map及filter函数使用方法解析
2020/08/06 Python
美国羊皮公司:Overland
2018/01/15 全球购物
巴西女装购物网站:Eclectic
2018/04/24 全球购物
万代美国官网:PREMIUM BANDAI USA
2020/09/11 全球购物
如何设定的weblogic的热启动模式(开发模式)与产品发布模式
2012/09/08 面试题
this关键字的作用
2016/01/30 面试题
会计电算化专业毕业生自荐信
2013/12/20 职场文书
高二历史教学反思
2014/01/25 职场文书
质量承诺书怎么写
2014/05/24 职场文书
2014银行领导班子四风对照检查材料思想汇报
2014/09/25 职场文书
幼儿教师自我剖析材料
2014/09/29 职场文书
2015入党自传格式范文
2015/06/26 职场文书
PostgreSQL怎么创建分区表详解
2022/06/25 PostgreSQL