原生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 相关文章推荐
xtree.js 代码
Mar 13 Javascript
jQuery $.extend()用法总结
Jun 15 Javascript
js实现从中间开始往上下展开网页窗口的方法
Mar 02 Javascript
jQuery弹出下拉列表插件(实现kindeditor的@功能)
Aug 16 Javascript
Javascript实现基本运算器
Jul 15 Javascript
angularJS实现动态添加,删除div方法
Feb 27 Javascript
vue页面离开后执行函数的实例
Mar 13 Javascript
vue中element-ui表格缩略图悬浮放大功能的实例代码
Jun 26 Javascript
微信小程序收货地址API兼容低版本解决方法
May 18 Javascript
layui table 表格上添加日期控件的两种方法
Sep 28 Javascript
原生JS实现天气预报
Jun 16 Javascript
vue的项目如何打包上线
Apr 13 Vue.js
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 用sock技术发送邮件的函数
2007/07/21 PHP
Pain 全世界最小最简单的PHP模板引擎 (普通版)
2011/10/23 PHP
解析isset与is_null的区别
2013/08/09 PHP
基于linnux+phantomjs实现生成图片格式的网页快照
2015/04/15 PHP
浮动的div自适应居中显示的js代码
2013/12/23 Javascript
javascript实现的淘宝旅行通用日历组件用法实例
2015/08/03 Javascript
jQuery处理XML文件的几种方法
2016/06/14 Javascript
Bootstrap编写一个兼容主流浏览器的受众门户式风格页面
2016/07/01 Javascript
vue2.0+webpack环境的构造过程
2016/11/08 Javascript
js验证手机号、密码、短信验证码代码工具类
2020/06/24 Javascript
bootstrap jquery dataTable 异步ajax刷新表格数据的实现方法
2017/02/10 Javascript
canvas轨迹回放功能实现
2017/12/20 Javascript
JS对象与json字符串相互转换实现方法示例
2018/06/14 Javascript
jQuery实现获取动态添加的标签对象示例
2018/06/28 jQuery
Vue中的异步组件函数实现代码
2018/07/20 Javascript
微信小程序API—获取定位的详解
2019/04/30 Javascript
生产制造追溯系统之在线打印功能
2019/06/03 Javascript
微信小程序中button去除默认的边框实例代码
2019/08/01 Javascript
vue 中几种传值方法(3种)
2019/11/12 Javascript
Vue项目vscode 安装eslint插件的方法(代码自动修复)
2020/04/15 Javascript
vue-quill-editor的使用及个性化定制操作
2020/08/04 Javascript
JavaScript常用工具函数汇总(浏览器环境)
2020/09/17 Javascript
addEventListener()和removeEventListener()追加事件和删除追加事件
2020/12/04 Javascript
[48:12]Secret vs Optic Supermajor 胜者组 BO3 第三场 6.4
2018/06/05 DOTA
[52:36]VGJ.S vs Serenity 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
pycharm安装图文教程
2017/05/02 Python
Python远程linux执行命令实现
2020/11/11 Python
python基于Kivy写一个图形桌面时钟程序
2021/01/28 Python
Corelle官方网站:购买康宁餐具
2016/11/02 全球购物
explicit和implicit的含义
2012/11/15 面试题
实习心得体会
2014/01/02 职场文书
20年同学聚会邀请函
2014/02/04 职场文书
主题教育活动总结
2014/05/05 职场文书
罚款通知怎么写
2015/04/22 职场文书
使用golang编写一个并发工作队列
2021/05/08 Golang
关于mysql中时间日期类型和字符串类型的选择
2021/11/27 MySQL