需求描述
需要实现类似QQ中对联系人的操作:向左滑动,滑出删除按钮。滑动超过一半时松开则自动滑到底,不到一半时松开则返回原处。
纯js实现
使用了h5的touchmove等事件,以及用js动态改变css3的translate属性来达到动画效果:
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" id="viewport" content="width=device-width, initial-scale=1"><title>html5向左滑动删除特效</title><style>* {padding: 0;margin: 0;list-style: none;}header {background: #f7483b;border-bottom: 1px solid #ccc}header h2 {text-align: center;line-height: 54px;font-size: 16px;color: #fff}.list-ul {overflow: hidden}.list-li {line-height: 60px;border-bottom: 1px solid #fcfcfc;position: relative;padding: 0 12px;color: #666;background: #f2f2f2;-webkit-transform: translateX(0px);}.btn {position: absolute;top: 0;right: -80px;text-align: center;background: #ffcb20;color: #fff;width: 80px}</style><script>/** 描述:html5苹果手机向左滑动删除特效*/window.addEventListener('load', function() {var initX; //触摸位置var moveX; //滑动时的位置var X = 0; //移动距离var objX = 0; //目标对象位置 window.addEventListener('touchstart', function(event) {event.preventDefault();var obj = event.target.parentNode;if (obj.className == "list-li") {initX = event.targetTouches[0].pageX;objX = (obj.style.WebkitTransform.replace(/translateX\(/g, "").replace(/px\)/g, "")) * 1;}if (objX == 0) {window.addEventListener('touchmove', function(event) {event.preventDefault();var obj = event.target.parentNode;if (obj.className == "list-li") {moveX = event.targetTouches[0].pageX;X = moveX - initX;if (X >= 0) {obj.style.WebkitTransform = "translateX(" + 0 + "px)";} else if (X < 0) {var l = Math.abs(X);obj.style.WebkitTransform = "translateX(" + -l + "px)";if (l > 80) {l = 80;obj.style.WebkitTransform = "translateX(" + -l + "px)";}}}});} else if (objX < 0) {window.addEventListener('touchmove', function(event) {event.preventDefault();var obj = event.target.parentNode;if (obj.className == "list-li") {moveX = event.targetTouches[0].pageX;X = moveX - initX;if (X >= 0) {var r = -80 + Math.abs(X);obj.style.WebkitTransform = "translateX(" + r + "px)";if (r > 0) {r = 0;obj.style.WebkitTransform = "translateX(" + r + "px)";}} else { //向左滑动 obj.style.WebkitTransform = "translateX(" + -80 + "px)";}}});}})window.addEventListener('touchend', function(event) {event.preventDefault();var obj = event.target.parentNode;if (obj.className == "list-li") {objX = (obj.style.WebkitTransform.replace(/translateX\(/g, "").replace(/px\)/g, "")) * 1;if (objX > -40) {obj.style.WebkitTransform = "translateX(" + 0 + "px)";objX = 0;} else {obj.style.WebkitTransform = "translateX(" + -80 + "px)";objX = -80;}}})})</script> </head><body><header><h2>消息列表</h2></header><section class="list"><ul class="list-ul"><li id="li" class="list-li"><div class="con">你的快递到了,请到楼下签收</div><div class="btn">删除</div></li><li class="list-li"><div class="con">哇,你在干嘛,快点来啊就等你了</div><div class="btn">删除</div></li></ul></section> </body></html>
做成zepto插件
实际项目中,我们可能有很多个地方会用到这个功能。现在我们将这个功能做成zepto插件,方便后面使用。
这个插件,我们仅实现这个功能,然后传入参数(删除按钮的样式名),让程序在js中计算所需要滑动的距离,方便复用。
zepto.touchWipe.js
1 /** 2 * zepto插件:向左滑动删除动效 3 * 使用方法:$('.itemWipe').touchWipe({itemDelete: '.item-delete'}); 4 * 参数:itemDelete 删除按钮的样式名 5 */ 6 ; 7 (function($) { 8 $.fn.touchWipe = function(option) { 9 var defaults = { 10 itemDelete: '.item-delete', //删除元素 11 }; 12 var opts = $.extend({}, defaults, option); //配置选项 13 14 var delWidth = $(opts.itemDelete).width(); 15 16 var initX; //触摸位置 17 var moveX; //滑动时的位置 18 var X = 0; //移动距离 19 var objX = 0; //目标对象位置 20 $(this).on('touchstart', function(event) { 21 event.preventDefault(); 22 var obj = this; 23 initX = event.targetTouches[0].pageX; 24 objX = (obj.style.WebkitTransform.replace(/translateX\(/g, "").replace(/px\)/g, "")) * 1; 25 if (objX == 0) { 26 $(this).on('touchmove', function(event) { 27 event.preventDefault(); 28 var obj = this; 29 moveX = event.targetTouches[0].pageX; 30 X = moveX - initX; 31 if (X >= 0) { 32 obj.style.WebkitTransform = "translateX(" + 0 + "px)"; 33 } else if (X < 0) { 34 var l = Math.abs(X); 35 obj.style.WebkitTransform = "translateX(" + -l + "px)"; 36 if (l > delWidth) { 37 l = delWidth; 38 obj.style.WebkitTransform = "translateX(" + -l + "px)"; 39 } 40 } 41 }); 42 } else if (objX < 0) { 43 $(this).on('touchmove', function(event) { 44 event.preventDefault(); 45 var obj = this; 46 moveX = event.targetTouches[0].pageX; 47 X = moveX - initX; 48 if (X >= 0) { 49 var r = -delWidth + Math.abs(X); 50 obj.style.WebkitTransform = "translateX(" + r + "px)"; 51 if (r > 0) { 52 r = 0; 53 obj.style.WebkitTransform = "translateX(" + r + "px)"; 54 } 55 } else { //向左滑动 56 obj.style.WebkitTransform = "translateX(" + -delWidth + "px)"; 57 } 58 }); 59 } 60 61 }) 62 $(this).on('touchend', function(event) { 63 event.preventDefault(); 64 var obj = this; 65 objX = (obj.style.WebkitTransform.replace(/translateX\(/g, "").replace(/px\)/g, "")) * 1; 66 if (objX > -delWidth / 2) { 67 obj.style.transition = "all 0.2s"; 68 obj.style.WebkitTransform = "translateX(" + 0 + "px)"; 69 obj.style.transition = "all 0"; 70 objX = 0; 71 } else { 72 obj.style.transition = "all 0.2s"; 73 obj.style.WebkitTransform = "translateX(" + -delWidth + "px)"; 74 obj.style.transition = "all 0"; 75 objX = -delWidth; 76 } 77 }) 78 79 //链式返回 80 return this; 81 }; 82 83 })(Zepto);
touchWipe.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" id="viewport" content="width=device-width, initial-scale=1"> 6 <title>html5向左滑动删除特效</title> 7 8 <style> 9 *{ padding:0; margin:0; list-style: none;} 10 header{ background: #f7483b; border-bottom: 1px solid #ccc} 11 header h2{ text-align: center; line-height: 54px; font-size: 16px; color: #fff} 12 .list-ul{ overflow: hidden} 13 .list-li{ line-height: 60px; border-bottom: 1px solid #fcfcfc; position:relative;padding: 0 12px; color: #666; 14 background: #f2f2f2; 15 -webkit-transform: translateX(0px); 16 } 17 .btn{ position: absolute; top: 0; right: -80px; text-align: center; background: #ffcb20; color: #fff; width: 80px} 18 </style> 19 20 </head> 21 <body> 22 <header> 23 <h2>消息列表</h2> 24 </header> 25 <section class="list"> 26 <ul class="list-ul"> 27 <li id="li" class="list-li"> 28 <div class="con"> 29 你的快递到了,请到楼下签收 30 </div> 31 <div class="btn">删除</div> 32 </li> 33 <li class="list-li"> 34 <div class="con"> 35 哇,你在干嘛,快点来啊就等你了 36 </div> 37 <div class="btn">删除</div> 38 </li> 39 </ul> 40 </section> 41 42 <p>X: <span id="X"></span></p> 43 <p>objX: <span id="objX"></span></p> 44 <p>initX: <span id="initX"></span></p> 45 <p>moveX: <span id="moveX"></span></p> 46 47 <script type="text/javascript" src="http://apps.bdimg.com/libs/zepto/1.1.4/zepto.min.js"></script> 48 <script type="text/javascript" src="zepto.touchWipe.js"></script> 49 <script type="text/javascript"> 50 $(function() { 51 $('.list-li').touchWipe({itemDelete: '.btn'}); 52 }); 53 54 </script> 55 </body> 56 </html>
效果:
实际项目中的应用效果:
消除BUG
到上面一步,基本实现了我们所需要的功能。但是有几个问题:
1. 右边的删除按钮点击失灵,因为span无法冒泡到大按钮上;
2. 非常严重的问题,我们给div添加了touchmove事件同时用preventDefault()屏蔽了原始的浏览器事件,导致上下滑动div的时候 页面无法滚动了!
第一个问题比较容易解决,我们把span直接去掉,将“删除”写到css中的:before里,像这样:
.itemWipe .item-delete:before {content: '删除';color: #fff; }
对于第二个问题,网上说用iscroll来解决。我们这里参考手机QQ中对联系人的滑动操作。
大致原理:在滑动最开始的时候,判断是Y轴的移动多 还是 X轴的移动多。 如果是X轴移动大,则判断为滑动删除操作,我们再使用preventDefault();
其中,我们加入一个变量flaxX来判断滑动方向,会在第一次触发滑动事件时设置。
之后就可以根据滑动方向来选择是否阻止浏览器默认事件了。
修改后: zepto.touchWipe.js
1 /** 2 * zepto插件:向左滑动删除动效 3 * 使用方法:$('.itemWipe').touchWipe({itemDelete: '.item-delete'}); 4 * 参数:itemDelete 删除按钮的样式名 5 */ 6 ; 7 (function($) { 8 $.fn.touchWipe = function(option) { 9 var defaults = { 10 itemDelete: '.item-delete', //删除元素 11 }; 12 var opts = $.extend({}, defaults, option); //配置选项 13 var delWidth = $(opts.itemDelete).width(); 14 15 var initX; //触摸位置X 16 var initY; //触摸位置Y 17 var moveX; //滑动时的位置X 18 var moveY; //滑动时的位置Y 19 var X = 0; //移动距离X 20 var Y = 0; //移动距离Y 21 var flagX = 0; //是否是左右滑动 0为初始,1为左右,2为上下,在move中设置,在end中归零 22 var objX = 0; //目标对象位置 23 24 $(this).on('touchstart', function(event) { 25 console.log('start..'); 26 var obj = this; 27 initX = event.targetTouches[0].pageX; 28 initY = event.targetTouches[0].pageY; 29 console.log(initX + ':' + initY); 30 objX = (obj.style.WebkitTransform.replace(/translateX\(/g, "").replace(/px\)/g, "")) * 1; 31 console.log (objX); 32 if (objX == 0) { 33 $(this).on('touchmove', function(event) { 34 35 // 判断滑动方向,X轴阻止默认事件,Y轴跳出使用浏览器默认 36 if (flagX == 0) { 37 setScrollX(event); 38 return; 39 } else if (flagX == 1) { 40 event.preventDefault(); 41 } else { 42 return; 43 } 44 45 var obj = this; 46 moveX = event.targetTouches[0].pageX; 47 X = moveX - initX; 48 if (X >= 0) { 49 obj.style.WebkitTransform = "translateX(" + 0 + "px)"; 50 } else if (X < 0) { 51 var l = Math.abs(X); 52 obj.style.WebkitTransform = "translateX(" + -l + "px)"; 53 if (l > delWidth) { 54 l = delWidth; 55 obj.style.WebkitTransform = "translateX(" + -l + "px)"; 56 } 57 } 58 }); 59 } else if (objX < 0) { 60 $(this).on('touchmove', function(event) { 61 62 // 判断滑动方向,X轴阻止默认事件,Y轴跳出使用浏览器默认 63 if (flagX == 0) { 64 setScrollX(event); 65 return; 66 } else if (flagX == 1) { 67 event.preventDefault(); 68 } else { 69 return; 70 } 71 72 var obj = this; 73 moveX = event.targetTouches[0].pageX; 74 X = moveX - initX; 75 if (X >= 0) { 76 var r = -delWidth + Math.abs(X); 77 obj.style.WebkitTransform = "translateX(" + r + "px)"; 78 if (r > 0) { 79 r = 0; 80 obj.style.WebkitTransform = "translateX(" + r + "px)"; 81 } 82 } else { //向左滑动 83 obj.style.WebkitTransform = "translateX(" + -delWidth + "px)"; 84 } 85 }); 86 } 87 }) 88 89 //结束时判断,并自动滑动到底或返回 90 $(this).on('touchend', function(event) { 91 var obj = this; 92 objX = (obj.style.WebkitTransform.replace(/translateX\(/g, "").replace(/px\)/g, "")) * 1; 93 if (objX > -delWidth / 2) { 94 obj.style.transition = "all 0.2s"; 95 obj.style.WebkitTransform = "translateX(" + 0 + "px)"; 96 obj.style.transition = "all 0"; 97 objX = 0; 98 } else { 99 obj.style.transition = "all 0.2s"; 100 obj.style.WebkitTransform = "translateX(" + -delWidth + "px)"; 101 obj.style.transition = "all 0"; 102 objX = -delWidth; 103 } 104 flagX = 0; 105 }) 106 107 //设置滑动方向 108 function setScrollX(event) { 109 moveX = event.targetTouches[0].pageX; 110 moveY = event.targetTouches[0].pageY; 111 X = moveX - initX; 112 Y = moveY - initY; 113 114 if (Math.abs(X) > Math.abs(Y)) { 115 flagX = 1; 116 } else { 117 flagX = 2; 118 } 119 return flagX; 120 } 121 122 //链式返回 123 return this; 124 }; 125 126 })(Zepto);