原文: https://www.luoyangfu.com/art...
一個移動端的touch 事件或者 mouse 事件,具體看看怎麼玩。javascript
先看看效果:css
這裏年月日都是使用建立好的Picker組件來實現的,在以前感謝博客園 @糊糊糊糊糊了, 原文地址.html
原文中講了實現Picker核心思路,我也是受益頗多,而後根據思路以及Github源碼,終於寫了本身想要的Picker,因而就有了記錄,再次感謝.java
開發這個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 中寫死爲 50px.app
咱們這裏就寫了關於 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
組件,如今咱們開始進行一個初始化。讓第一個元素顯示在正確的位置。
這裏有兩個基本問題須要先考慮一下: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;
這裏再開頭申明瞭一些內容,主要說明一下 momentumRatio
和 velocityTranslate
這兩個,前者是動力系數用於短期修改位移後慣性移動,後者是速度位移用於在用戶移動過程當中移動的量。
如今就完成了一個基本的Picker。看圖:
目前只是一個最基本的例子。若是說須要選擇兩側有一個縮放呢。