06#Web 实战:实现可滑动的标签页

实现效果图

06#Web 实战:实现可滑动的标签页

本随笔只是记录一下大概的实现思路,如果感兴趣的小伙伴可以通过代码和本随笔的说明去理解实现过程。👉我的 GiteeGitHub 地址。注意哦:这个只是 PC 上的标签页,手机端的没用,因为监听器是 mouse,而不是手势。

在线浏览地址:10.标签页

构建静态页面

可滑动的页面需要 5 个 div,标签也需要 5 个。只有页面是随着我们操作而变化,标签处于静态,标签下面有一个滑块也是随着我们操作而变化。所以,构建基本的 HTML 骨架如下:

<div>   <!-- 标签 -->   <div class="tab-bar flex-space">     <div class="bar-item flex-center">标签5</div>     <div class="bar-item flex-center">标签1</div>     <div class="bar-item flex-center">标签2</div>     <div class="bar-item flex-center">标签3</div>     <div class="bar-item flex-center">标签4</div>     <!-- 滑块 -->     <div class="slider"></div>   </div>   <!-- 标签页 -->   <div class="tab-page">     <div class="page-item page-5">Index 5</div>     <div class="page-item page-1">Index 1</div>     <div class="page-item page-2">Index 2</div>     <div class="page-item page-3">Index 3</div>     <div class="page-item page-4">Index 4</div>   </div> </div> 

我写的这个标签页的起始标签是5,而不是标签1。下面直接给上默认的 CSS,如果不全,就去仓库找:

.tab-bar {   width: 100%;   height: 40px;   position: relative;   margin-bottom: 8px; }  .bar-item {   margin: 0;   padding: 0;   cursor: pointer; }  .slider {   position: absolute;   background-color: #5677fc;   transform: translateX(0%);   width: 35px;   height: 3px;   background-color: blue;   border-radius: 3px;   bottom: 0;   transition: all 0.2s ease-in-out; }  .tab-page {   overflow-x: hidden;   position: relative;   width: 100%;   height: calc(100vh - 64px);   transition: all 0.2s ease-in-out;   transform: translate(0%, 0px) translateZ(0px); }  .page-item {   cursor: pointer;   position: absolute;   width: 100%;   height: 100%;   top: 0;   left: 0;   transition: all 0.5s ease-in-out;   text-align: center;   font-size: 50px; }  .page-1 {   background-color: rgb(169, 187, 228); }  .page-2 {   background-color: #5677fc; }  .page-3 {   background-color: rgb(101, 192, 225); }  .page-4 {   background-color: rgb(153, 60, 235); }  .page-5 {   background-color: coral; } 

slider 滑块通过transform: translateX()来平移,默认滑块所在位置是标签1 的位置,现在的问题是如何定义滑块默认的位置、以及每一次往前移动一个标签需要多少距离。

实现滑块的移动

获取 tab-bar 滑块容器的宽度,并且去除容器的 padding 和 margin 值,计算每一个标签所占的长度是多少:

let tabbarWidth = $(".tab-bar").width(); let barItemLength = $(".tab-bar").find("div").length - 1; let barItemPerWidth = tabbarWidth / barItemLength; 

滑块的宽度是 35px,假设一个标签的宽度是 99px。要让这个滑块处于标签的中间,就得让滑块左边缘定在如下图所示的地方:

06#Web 实战:实现可滑动的标签页

标签的一半再减去滑块的一半就是滑块左边缘的位置,从而使得滑块处于标签的中央。滑块的宽度可以用一个变量来代替,可以考虑让滑块自适应 tab-bar 容器宽度,这里就用实际数字了:

let sliderTranslateX = barItemPerWidth / 2 - 35 / 2; 

滑块往前移动,移动多少呢?实际可以如下图这样思考,第一个标签往前移动了一个标签的长度,相当于滑块也跟随着这个标签往前移动,来到了下一个标签的位置。当然,这个标签是不会动的,只有滑块在移动。

06#Web 实战:实现可滑动的标签页

滑块往前移动一次,到下一个标签,实际就是 sliderTranslateX + barItemPerWidth。前面说了,滑块默认在标签1 的下面:

$(".slider").css({ transform: `translateX(${sliderTranslateX + barItemPerWidth}px)` }); 

刷新页面,如下图所示,滑块来到了标签1 的下面:

06#Web 实战:实现可滑动的标签页

现在,实现点击事件,往前(往右)挨个点击标签,滑块跟随我们点击的位置进行平移。

let moveTranslateX = 0;  function moveSlider(index) {   // 0  1  2   3   4  5  6   // 17 77 137 207 .. .. ..   moveTranslateX = sliderTranslateX + barItemPerWidth * index;   $(".slider").css({ transform: `translateX(${moveTranslateX}px)` }); }  $(".tab-bar")   .find(".bar-item")   .each((index, elem) => {     // 平均地设置每一个 tabBar 的宽度     $(elem).css({ width: `${barItemPerWidth}px` });      // 给每一个 tabBar 添加踢点击事件     $(elem).on("click", e => {       moveSlider(index);     });   }); 

主要看moveSlider()这个函数,通过 each 遍历每一个标签,并给其注册一个点击事件,点击事件中,给 moveSlider 传递当前标签的 index(索引值)。假设,index 大于 0,说明我们是往前挨个点击着走的,那么,moveTranslateX 的值就是 sliderTranslateX 加上 barItemPerWidth,以及乘以 index。

06#Web 实战:实现可滑动的标签页

index 是 1,就是往前滑动一个标签的距离。但目前,我们只可能(只考虑往前点击)从 index 1 开始点击,所以 index 是 2,滑块从标签5 的位置往前移动了两个标签的距离,来到了标签3 的底下:

06#Web 实战:实现可滑动的标签页

Now!滑块的移动功能就已经实现了:

06#Web 实战:实现可滑动的标签页

实现页面的滑动

往前滑动

我是把页面放在数组里面进行思考的。第一行是初始的状态,5、1、2、3、4 标签页在数组中排着。往前拉(鼠标往右滑动)标签页1、2、3、4 的 index 依次往前 +1;唯独标签页5 排在数组末尾处。

06#Web 实战:实现可滑动的标签页

继续往前拉,标签页2、3、4、5 的 index 依次往前 +1;标签页1 排在数组末尾处。依次类推,直到第5行的下一次,才跳回到第1行继续循环上面的规律。

第1行变成第2行的样子,要经历 5 次排序,每一次排序都会破坏原有的数组结构,因此我在写的时候,定义了两个空数组,一个用来存储之前的顺序,且不改变这个数组的结构和顺序,排序完成之后的元素依次插入到第二个数组中。

let tempTabPageBox = []; let movedTabPageBox = []; 

这里还考察到了一个知识点,JS 中对象通过等号赋值是地址引用,而不是拷贝。所以,我这里用的new Array(...arr)来拷贝了一份新的数组对象给第二个数组。我推荐看对象引用和复制来理解对象的拷贝知识。

原本我用的是JSON.parse(JSON.stringfy(arr))拷贝一个数组,但是这种方式会丢失很多 DOM 原本的字段。因为,数组里面存储的是一个个 DOM 对象,所以这种方式拷贝不适用于现在的情况。所以,我就改用了new Array(...arr)来 new 一个新数组,可能会导致性能降低,因为一直都在 new 数组,不过 JS 应该会给我回收垃圾吧?

tempTabPageBox 存储上一次的数组顺序和结构,movedTabPageBox 保存排序之后的顺序和结构。

function movePageToRight() {   for (let i = 0; i < tempTabPageBox.length; i++) {     if (i > 0) movedTabPageBox[i - 1] = tempTabPageBox[i];     else movedTabPageBox[tempTabPageBox.length - 1] = tempTabPageBox[i];   }    for (let i = 0; i < movedTabPageBox.length; i++) {     $(movedTabPageBox[i]).css({ transform: `translate(${tabPageTranslateX[i]}%, 0px) translateZ(0px)`, "z-index": 999 });   }    tempTabPageBox = new Array(...movedTabPageBox); }  $(".tab-page") .find(".page-item") .each((index, elem) => {   // 初始化每一个 page 到暂存容器中   tempTabPageBox.push(elem);    // 初始化 page-item   if (index == 0) {     $(elem).css({ transform: `translate(${tabPageTranslateX[0]}%, 0px) translateZ(0px)`, "z-index": 999 });   } else {     $(elem).css({ transform: `translate(${tabPageTranslateX[index]}%, 0px) translateZ(0px)`, "z-index": 999 });   }    $(elem).on("mousedown", e => {     e.preventDefault();      $(elem).on("mouseup", e => {       if (/* 往右拉 */) {         movePageToRight();       } else if (/* 往左拉 */) {         movePageToLeft();       }       $(elem).unbind("mousemove");     });   }); }); 

这里贴上的代码省略了很多细节啊!但主要还是看movePageToRight()函数。结合上面说的和图片,每一次都是第一个元素被直接放在了末尾处,其余的元素依次往前移动。函数中第二个 for 是根据排序好的 movedTabPageBox 依次设置页面的 translate,实现平移。最后再把这一次的排序结果拷贝给 tempTabPageBox,然后等待下一次的循环。

往后滑动

06#Web 实战:实现可滑动的标签页

如上图,往后滑动(也就是往左拉)每一行的数组变化情况。只有最后一个元素排在了第一位,其余的元素依次往后 +1。

function movePageToLeft() {   for (let i = 0; i < tempTabPageBox.length; i++) {     if (i >= 0 && i < tempTabPageBox.length - 1) {       movedTabPageBox[i + 1] = tempTabPageBox[i];     } else if (i === tempTabPageBox.length - 1) {       movedTabPageBox[0] = tempTabPageBox[i];     }   }    for (let i = 0; i < movedTabPageBox.length; i++) {     $(movedTabPageBox[i]).css({ transform: `translate(${tabPageTranslateX[i]}%, 0px) translateZ(0px)`, "z-index": 999 });   }    tempTabPageBox = new Array(...movedTabPageBox); } 

其余的代码和movePageToRight()函数是一样的,没有什么变化。

连接滑块和标签页

到目前为止,滑块的移动和标签页的移动都是独立的,现在要做的就是把他们连接起来。后面我也不想多写了,滑块移动的时候要把这个 index 赋值给一个全局的 index,标签页移动也是一样,而且还要记录上一次的 index。

通过一顿骚操作,把代码写出来,就实现了这个小案例了。具体还是看我仓库的代码吧👉 GiteeGitHub 地址。

发表评论

相关文章