學習移動端組件 Picker一下

原文: https://www.luoyangfu.com/art...

前言

一個移動端的touch 事件或者 mouse 事件,具體看看怎麼玩。javascript

先看看效果:css

2019-06-13 20.12.53

這裏年月日都是使用建立好的Picker組件來實現的,在以前感謝博客園 @糊糊糊糊糊了, 原文地址.html

原文中講了實現Picker核心思路,我也是受益頗多,而後根據思路以及Github源碼,終於寫了本身想要的Picker,因而就有了記錄,再次感謝.java

單獨Picker

HTML 結構

開發這個Picker, 觀察Picker節點結構,Picker 是由定高隱藏元素的塊, 一個定高不隱藏元素的的塊,以及一個選擇列表組成這三個部分。咱們得知了這個Picker組成後很容易就能夠寫出來下面HTML的結構:git

<div class="picker">
    <div class="picker-wrapper" id="wrapper">
        <div>2016</div>
        <div>2017</div>
        <div>2018</div>
        <div>2019</div>
        <div>2020</div>
    </div>
    <div class="center-highlight"></div>
</div>

這個就是最簡單的 picker 結構了。github

CSS 樣式

我們給這塊結構添加樣式,顯示以下, 在這裏咱們默認元素的高度在css 中寫死爲 50px.app

-w123

咱們這裏就寫了關於 Picker 基礎樣式。函數

.picker {
  overflow: hidden;
  position: relative;
  z-index: 1;
}

.picker-wrapper {
  overflow: visible;
  height: calc(50px * 3);
}

.picker-item {
  height: 50px;
  line-height: 50px;
  text-align: center;
  color: #999;
}

.picker-item.active {
  color: #000;
}

.picker-center-highlight {
  height: 50px;
  position: absolute;
  width: 100%;
  top: 50%;
  margin-top: -25px;
  z-index: 2;
}

.picker-center-highlight::before, .picker-center-highlight::after {
  content: '';
  display: block;
  height: 2px;
  background-color: #000;
  width: 100%;
  transform: scaleY(0.5);
  position: absolute;
}

.picker-center-highlight::before {
  top: 0;
}

.picker-center-highlight::after {
  bottom: 0;
}

上面僅僅僅僅包含圖片樣式組成,後續會逐漸添加各類樣式。動畫

初始化Picker組件

已經有了基本的 Picker 組件,如今咱們開始進行一個初始化。讓第一個元素顯示在正確的位置。
這裏有兩個基本問題須要先考慮一下:ui

  • 元素的移動範圍
  • 元素的下標和位置交換

元素的移動範圍

咱們元素移動範圍用下標來講的話,應該是 1 到 length, 這裏length 就是傳入Picker數據長度。

由於咱們第一個元素須要向下移動一列,因此第一列應該是空的, 這個時候位置也是咱們位移最大位置。當不斷將Picker 向上位移何時爲最小的位置呢?應該是有這樣計算 (length - Math.ceil(count / 2)) * 50. 這裏咱們說的是顯示3列狀況, length 爲數據長度, 50爲單個Picker高度,上文已有。

下面直接看元素結算範圍:

const getMoveRange = function() {
  const max = Math.floor(visibleCount / 2) * itemHeight;
  const min = (itemLength - Math.ceil(visibleCount / 2)) * -itemHeight;
  return [min, max]
}

這裏就解決了第一個元素的範圍問題。

這裏使用兩個數學函數:
Math.floor 取不大於該數的最大整數
Math.ceil 取不小於該數的最小整數

元素的下標和位置交換

這裏須要兩個函數分別來計算使用下標獲取位置,使用位置來獲取下標的。

經過下標獲取元素位置時候有一點須要注意的是,咱們元素位置是須要根據當前顯示的個數進行偏移的,也就說,在計算以前,須要減去偏移量。偏移量恰好等於 Math.floor(count / 2)

const getTranslByIndex = function(index) {
  const offset = Math.floor(visibleCount / 2)
  
  if(index >= 0) {
    return (index - offset) * -itemHeight
  }
}

經過位移獲取元素位置就簡單不少了。

const getIndexByTransl = function(transl) {
  transl = Math.round(transl / itemHeight) * itemHeight;
  const index = - (transl - Math.floor(visibleCount / 2) * itemHeight) / itemHeight;
  
  return index;
}

計算用了三行代碼, 第一行主要是轉化當前位置靠近哪個元素,獲得具體translate,第二行 計算具體的 index, 這裏計算語句前面使用 -減號, 主要由於 translate 減去顯示個數的1/2後,老是負值,這裏須要將負值轉正,也能夠使用 Math.abs 來替代。

上面咱們就解決了兩個問題。

初始化組件

首先須要組件初始化爲上圖的樣子,這個時候,咱們開始監聽事件,這了咱們主要監聽 touchstart, touchmove, touchend 事件。

const translateEl = function(transl) {
  el.style.transform = `translateY(${transl}px)`;
};

const setSelectedEl = function(index) {
  Array.prototype.forEach.call(pickerItems, (item, idx) => {
    item.classList.remove("active");
  });
  pickerItems[index].classList.add("active");
};

function initEvent() {
  el.addEventListener("touchstart", function(e) {
    console.log("touchstart", e);
  });
  el.addEventListener("touchmove", function(e) {
    console.log("touchmove", e);
  });
  el.addEventListener("touchend", function(e) {
    console.log("touchenv", e);
  });
}

window.onload = function onload() {
  translateY = getTranslByIndex(0);
  setSelectedEl(0);
  translateEl();
  initEvent();
};

上面的 pickerItems 就是選中的picker 元素集合.

這裏就對元素進行了touchstart,touchmove, touchend 監聽。下面我進一步對事件作處理,讓picker動起來。
爲了讓picker 在移動過程當中有過分效果,增長以下css

.picker-wrapper {
    overflow: visible;
    height: calc(50px * 3);
    transition: all 0.3s ease-in-out; /* 這裏就對 元素作了過渡動畫處理*/
}

開始對事件進行處理

el.addEventListener('touchstart', function(e) {
    startAt = Date.now();
    startTop = e.touches[0].pageY;
    // 開始滾動的位置
    startTranslateY = translateY;
  });
  el.addEventListener('touchmove', function(e) {
    const deltaY = e.touches[0].pageY - startTop;
    translateY = startTranslateY + deltaY;
    velocityTranslate = translateY - prevTranslateY || translateY;

    prevTranslateY = translateY;
    translateEl();
  });
  el.addEventListener('touchend', function(e) {
    let momentumTranslate = 0;
    // 小於 300 就開始彈性滾動
    if (Date.now() - startAt < 300) {
      momentumTranslate = translateY + velocityTranslate * momentumRatio;
    }

    let translate = Math.round(translateY / itemHeight) * itemHeight;

    if (momentumTranslate) {
      translate = Math.round(momentumTranslate / itemHeight) * itemHeight;
    }

    const range = getMoveRange();
    translateY = Math.max(Math.min(translate, range[1]), range[0]);
    translateEl();
    const index = getIndexByTransl(translateY);
    setSelectedEl(index);
  });

  // 每一個item 點擊生效
  Array.prototype.forEach.call(pickerItems, function(item, index) {
    item.addEventListener('click', function(e) {
      setSelectedEl(index);
      translateY = getTranslByIndex(index);
      translateEl();
    });
  });

這裏就對觸摸事件作了處理,也對單個元素的點擊作了監聽。

const el = document.querySelector('#wrapper');
const itemHeight = 50;
const visibleCount = 3;
const itemLength = 5;
const pickerItems = document.querySelectorAll('.picker-item');
let startAt = Date.now();
let startTop = 0;
let translateY = 0;
let startTranslateY = 0;
let prevTranslateY = 0;
// 動力參數
let momentumRatio = 7;
// 速度速度位移
let velocityTranslate = 0;

這裏再開頭申明瞭一些內容,主要說明一下 momentumRatiovelocityTranslate 這兩個,前者是動力系數用於短期修改位移後慣性移動,後者是速度位移用於在用戶移動過程當中移動的量。

如今就完成了一個基本的Picker。看圖:

2019-07-03 12.59.21

最後

目前只是一個最基本的例子。若是說須要選擇兩側有一個縮放呢。

相關文章
相關標籤/搜索