1

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

 1 year ago
source link: https://www.cnblogs.com/Enziandom/p/16926630.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

实现效果图

image

本随笔只是记录一下大概的实现思路,如果感兴趣的小伙伴可以通过代码和本随笔的说明去理解实现过程。👉我的 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。要让这个滑块处于标签的中间,就得让滑块左边缘定在如下图所示的地方:

image

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

let sliderTranslateX = barItemPerWidth / 2 - 35 / 2;

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

image

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

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

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

image

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

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。

image

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

image

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

image

实现页面的滑动

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

image

继续往前拉,标签页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,然后等待下一次的循环。

image

如上图,往后滑动(也就是往左拉)每一行的数组变化情况。只有最后一个元素排在了第一位,其余的元素依次往后 +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 地址。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK