06#Web 實戰:實現可滑動的標籤頁

Enziandom發表於2022-11-25

實現效果圖

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 地址。

相關文章