原文: 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。看图:
目前只是一个最基本的例子。若是说须要选择两侧有一个缩放呢。