移动web:tab选项卡
平常做移动端会用到tab选项卡,这和PC端有些区别,移动端是触摸滑动切换,PC端是点击、移入切换。
这里滑动切换就是一个移动端事件的应用,这里主要用到的触摸事件:touchstart、touchmove、touchend。
和做其他的效果一样,先有html结构,css样式修饰,再写JS代码。
html:

<div class="mtabs" id="tabs"> <ul class="mhead"> <li>tab1</li> <li>tab2</li> <li>tab3</li> </ul> <div class="mcontent"> <ul> <li>tab1内容内容内容内容</li> <li>tab1内容内容内容内容</li> <li>tab1内容内容内容内容</li> <li>tab1内容内容内容内容</li> <li>tab1内容内容内容内容</li> </ul> <ul> <li>tab2内容内容内容内容</li> <li>tab2内容内容内容内容</li> <li>tab2内容内容内容内容</li> <li>tab2内容内容内容内容</li> <li>tab2内容内容内容内容</li> </ul> <ul> <li>tab3内容内容内容内容</li> <li>tab3内容内容内容内容</li> <li>tab3内容内容内容内容</li> <li>tab3内容内容内容内容</li> <li>tab3内容内容内容内容</li> </ul> </div> </div><!-- End .mtabs -->
css:

body,div,ul,li{ margin:0; padding:0; } ul,li { list-style:none; } body { font-size:100%; font-family:Helvetica,STHeiti,Droid Sans Fallback; } .mtabs { width:100%; overflow:hidden; } .mhead { height:38px; border-top:2px solid #9ac7ed; background:#ECF2F6; -webkit-tap-highlight-color:rgba(0,0,0,0); } .mhead li { position:relative; font-size:1.125em; text-align:center; float:left; width:64px; height:38px; line-height:38px; color:#2a70be; } .mhead li.current { border-top:2px solid #2a70be; margin-top:-2px; background:#FFF; color:#c14545; } .mcontent { width:100%; overflow:hidden; } .mcontent ul { width:100%; float:left; } .mcontent li { height:35px; line-height:35px; font-size:1.125em; padding:0 10px; }
下面的截图是想要的一个效果预览:
下面是实际效果,可以在Chrome的移动模式查看:
- tab1
- tab2
- tab3
- tab1内容内容内容内容
- tab1内容内容内容内容
- tab1内容内容内容内容
- tab1内容内容内容内容
- tab1内容内容内容内容
- tab2内容内容内容内容
- tab2内容内容内容内容
- tab2内容内容内容内容
- tab2内容内容内容内容
- tab2内容内容内容内容
- tab3内容内容内容内容
- tab3内容内容内容内容
- tab3内容内容内容内容
- tab3内容内容内容内容
- tab3内容内容内容内容
先贴上JS代码,供参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | /** * LBS mTabs * Date: 2014-5-10 * =================================================== * opts.mtab tabs外围容器/滑动事件对象(一个CSS选择器) * opts.mhead tabs标题容器/点击对象(一个CSS选择器) * opts.mcontent tabs内容容器/滑动切换对象(一个CSS选择器) * opts.index tabs索引(默认0) 指定显示哪个索引的标题、内容 * opts.current tabs当前项的类名(默认current) * =================================================== **/ ;( function (){ window.mTabs = function (opts){ if ( typeof opts === undefined) return ; //取得tabs外围容器、标题容器、内容容器 this .mtab = document.querySelector(opts.mtab); this .mhead = document.querySelector(opts.mhead); this .mcontent = document.querySelector(opts.mcontent); //取得标题容器内选项集合、内容容器内容集合 this .mheads = this .mhead.children; this .mcontents = this .mcontent.children; this .length = this .mheads.length; if ( this .length < 1) return ; if (opts.index > this .length-1) opts.index = this .length-1; this .index = this .oIndex = opts.index || 0; this .current = opts.current || 'current' ; //当前活动选项类名 this .touch = {}; //自定义一个对象 用来保存手指触摸相关信息 this .init(); } mTabs.prototype = { init: function (opts){ this .set(); this .initset(); this .bind(); }, initset: function (){ for ( var i = 0; i < this .length; i++){ this .mheads[i].index = i; //设置了一个属性 方便点击时判断是点了哪一项 this .mheads[i].className = this .mheads[i].className.replace( this .current, '' ); this .mcontents[i].className = this .mcontents[i].className.replace( this .current, '' ); } //初始化设置、先清空手动加在标题或内容HTML标签的当前类名(this.current)、再设置哪一项为当前选项并设置类名 this .mheads[ this .index].className += ' ' + this .current; this .mcontents[ this .index].className += ' ' + this .current;<br> //对应的内容要显示在可视区域 //this.mcontent.style.webkitTransform = this.mcontent.style.transform = "translateX(" + (-this.index * this.width) + "px)"; //this.mcontent.style.webkitTransform = this.mcontent.style.transform = "translate3d(" + (-this.index * this.width) + "px,0,0)"; }, set: function (){ //获取浏览器的视口宽度、并设置内容容器的宽度、每一项内容区域的宽度,屏幕旋转,浏览器窗口变换会再次设置这些值 this .width = document.documentElement.clientWidth || document.body.clientWidth; this .mcontent.style.width = this .length * this .width + 'px' ; for ( var i = 0; i < this .length; i++) this .mcontents[i].style.width = this .width + 'px' ; //调整在可视区域显示的内容项 //this.mcontent.style.webkitTransform = this.mcontent.style.transform = "translateX(" + (-this.index * this.width) + "px)"; this .mcontent.style.webkitTransform = this .mcontent.style.transform = "translate3d(" + (- this .index * this .width) + "px,0,0)" ; }, bind: function (){<br> //绑定各种事件 var _this = this ; this .mtab.addEventListener( "touchstart" , function (e){ _this.touchStart(e); }, false ); this .mtab.addEventListener( "touchmove" , function (e){ _this.touchMove(e); }, false ); this .mtab.addEventListener( "touchend" , function (e){ _this.touchEnd(e); }, false ); this .mtab.addEventListener( "touchcancel" , function (e){ _this.touchEnd(e); }, false ); this .mhead.addEventListener( "click" , function (e){ _this.touchClick(e); }, false ); this .mcontent.addEventListener( 'webkitTransitionEnd' , function (){ _this.transitionEnd(); }, false ); window.addEventListener( "resize" , function (){ setTimeout( function (){ _this.set(); },100); }, false ); window.addEventListener( "orientationchange" , function (){ setTimeout( function (){ _this.set(); },100); }, false ); }, touchStart: function (e){ this .touch.x = e.touches[0].pageX; this .touch.y = e.touches[0].pageY; this .touch.time = Date.now(); this .touch.disX = 0; this .touch.disY = 0; this .touch.fixed = '' ; //重要 这里采用了判断是滚动页面行为、还是切换选项行为 如果是滚动页面就在滑动时只滚动页面 相应的切换选项就切换不会滚动页面 }, touchMove: function (e){ if ( this .touch.fixed === 'up' ) return ; e.stopPropagation(); if (e.touches.length > 1 || e.scale && e.scale !== 1) return ; this .touch.disX = e.touches[0].pageX - this .touch.x; this .touch.disY = e.touches[0].pageY - this .touch.y; if ( this .touch.fixed === '' ){ //行为判断 采用这种方式 主要解决手指按下移动(左上、右上)时滑动切换和滚动页面同时执行的问题 if ( Math.abs( this .touch.disY) > Math.abs( this .touch.disX) ){ this .touch.fixed = 'up' ; } else { this .touch.fixed = 'left' ; } } if ( this .touch.fixed === 'left' ){ e.preventDefault(); if ( ( this .index === 0 && this .touch.disX > 0) || ( this .index === this .length-1 && this .touch.disX < 0) ) this .touch.disX /= 4; //在 第一项向右滑动、最后一项向左滑动 时 //this.mcontent.style.webkitTransform = this.mcontent.style.transform = "translateX(" + ( this.touch.disX - this.index * this.width ) + "px)"; this .mcontent.style.webkitTransform = this .mcontent.style.transform = "translate3d(" + ( this .touch.disX - this .index * this .width ) + "px,0,0)" ; } }, touchEnd: function (e){ if ( this .touch.fixed === 'left' ){ var _this = this , X = Math.abs( this .touch.disX); this .mcontent.style.webkitTransition = this .mcontent.style.transition = 'all 100ms' ; if ( (Date.now() - this .touch.time > 100 && X > 10) || X > this .width/2 ){ this .touch.time = Date.now(); this .touch.disX > 0 ? this .index-- : this .index++; this .index < 0 && ( this .index = 0); this .index > this .length - 1 && ( this .index = this .length - 1); if ( this .index === this .oIndex) this .mcontent.style.webkitTransition = this .mcontent.style.transition = 'all 300ms' ; if ( this .index !== this .oIndex) this .replace(); } //this.mcontent.style.webkitTransform = this.mcontent.style.transform = "translateX(" + (-this.index * this.width) + "px)"; this .mcontent.style.webkitTransform = this .mcontent.style.transform = "translate3d(" + (- this .index * this .width) + "px,0,0)" ; } }, transitionEnd: function (){ this .mcontent.style.webkitTransition = this .mcontent.style.transition = 'all 0ms' ; }, touchClick: function (e){ var target = e.target; if (target.nodeType === 1 && target.index !== undefined){ if (target.index === this .index) return ; e.preventDefault(); e.stopPropagation(); this .index = target.index; this .mcontent.style.webkitTransition = this .mcontent.style.transition = 'all 100ms' ; //this.mcontent.style.webkitTransform = this.mcontent.style.transform = "translateX(" + (-this.index * this.width) + "px)"; this .mcontent.style.webkitTransform = this .mcontent.style.transform = "translate3d(" + (- this .index * this .width) + "px,0,0)" ; this .replace(); } }, replace: function (){ this .mheads[ this .index].className += ' ' + this .current; this .mheads[ this .oIndex].className = this .mheads[ this .oIndex].className.replace( this .current, '' ).trim(); this .mcontents[ this .index].className += ' ' + this .current; this .mcontents[ this .oIndex].className = this .mcontents[ this .oIndex].className.replace( this .current, '' ).trim(); this .oIndex = this .index; } } }()); |
使用方式很简单,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | document.addEventListener( 'DOMContentLoaded' , function (){ //use mTabs new mTabs({ mtab: '#tabs' , mhead: '#tabs .mhead' , mcontent: '#tabs .mcontent' }); /*new mTabs({ mtab: '#tabs', mhead: '#tabs .mhead', mcontent: '#tabs .mcontent', index: 1, current: 'active' });*/ }, false ); |
mtab: <div class="mtabs" id="tabs"> //.. </div> mhead: <ul class="mhead"> //.. </ul> mcontent: <div class="mcontent"> //.. </div>
在此说下思路:
先获得一个tabs容器对象(mtab),它包含了两个对应的类集合容器,一个是标签栏(mhead)、一个是内容栏(mcontent),再分别取得类集合容器里面对应的选项mheads、mcontents。
1 2 3 4 5 6 | this .mtab = document.querySelector(opts.mtab); this .mhead = document.querySelector(opts.mhead); this .mcontent = document.querySelector(opts.mcontent); this .mheads = this .mhead.children; this .mcontents = this .mcontent.children; |
获取设备浏览器窗口的宽,并更新内容容器(mcontent)的宽,内容项的宽,一般在页面都会有文档声明 <!DOCTYPE html> document.documentElement.clientWidth 就能获取浏览器窗口的宽。
1 2 3 | this .width = document.documentElement.clientWidth || document.body.clientWidth; this .mcontent.style.width = this .length * this .width + 'px' ; for ( var i = 0; i < this .length; i++) this .mcontents[i].style.width = this .width + 'px' ; |
在手指触摸按上时(在tabs容器对象上), 获取手指按下时在页面的位置 ( e.touches[0].pageX)。 touchs想象成有几根手指,只需要第一根按下的手指( touches[0] )。初始化了一个行为判断 this.touch.fixed (当在tabs上滑动时是要滚动页面还是要切换选项卡)。
1 2 3 | this .touch.x = e.touches[0].pageX; //.. this .touch.fixed = '' ; |
在移动手指时,做出行为的判断。先获得移动的距离(左右方向、上下方向),根据两个方向的值比较判断是哪种行为。
1 2 3 4 5 6 7 8 9 10 | this .touch.disX = e.touches[0].pageX - this .touch.x; this .touch.disY = e.touches[0].pageY - this .touch.y; //.. if ( this .touch.fixed === '' ){ if ( Math.abs( this .touch.disY) > Math.abs( this .touch.disX) ){ this .touch.fixed = 'up' ; } else { this .touch.fixed = 'left' ; } } |
在移动手指时,内容容器(mcontent)也会跟着移动,并且做了在处于第一项和最后一项时的移动限制。
1 | if ( ( this .index === 0 && this .touch.disX > 0) || ( this .index === this .length-1 && this .touch.disX < 0) ) this .touch.disX /= 4; |
在手指离开屏幕的时候,做出切换判断,是向左还是向右。在第一项时,不能向左切换,最后一项时不能向右切换。
1 2 3 | this .touch.disX > 0 ? this .index-- : this .index++; this .index < 0 && ( this .index = 0); this .index > this .length - 1 && ( this .index = this .length - 1); |
最后是真正的移动切换,用了css3动画切换,translateX 或者 translate3d .
1 2 | //this.mcontent.style.webkitTransform = this.mcontent.style.transform = "translateX(" + (-this.index * this.width) + "px)"; this .mcontent.style.webkitTransform = this .mcontent.style.transform = "translate3d(" + (- this .index * this .width) + "px,0,0)" ; |
代码中有个transitionEnd方法,配合webkitTransitionEnd事件在动画切换执行完成时调用。这里调用这个方法是用来清除动画定义的持续时间。
1 2 3 4 5 6 7 | transitionEnd: function (){ this .mcontent.style.webkitTransition = this .mcontent.style.transition = 'all 0ms' ; } this .mcontent.addEventListener( 'webkitTransitionEnd' , function (){ _this.transitionEnd(); }, false ); |
点击切换是判断点击了哪一项,在初始设置时已经为每一项保存了索引值(index),根据对应的索引值,切换选项,更新状态。可以循环绑定点击事件,也可以使用事件委托,这里使用的是事件委托。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | this .mheads[i].index = i; touchClick: function (e){ var target = e.target; if (target.nodeType === 1 && target.index !== undefined){ //.. this .index = target.index; //.. this .mcontent.style.webkitTransform = this .mcontent.style.transform = "translate3d(" + (- this .index * this .width) + "px,0,0)" ; } } this .mhead.addEventListener( "click" , function (e){ _this.touchClick(e); }, false ); |
tab选项卡主要是获得两组对应的类似集合(一组标签,一组内容),两个类似数组都有索引值(数组下标),通过这个索引值,做出对应的切换。获取索引值是tab选项卡的关键,移动web端的选项卡主要是增加了触摸事件操作这个索引值。加上定时器,每隔多少时间增加或者减少这个索引值,自动播放也就完成了。会了tab选项卡也就会了图片的切换,焦点图什么的,原理都是一样。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步