日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

移动端效果之Picker

發布時間:2023/12/20 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 移动端效果之Picker 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

寫在前面

接著前面的移動端效果的研究,這次來看看picker選擇器的實現原理

移動端效果之Swiper

移動端效果之CellSwiper

移動端效果之IndexList

代碼看這里:github

1. 核心解析

1.1 基本HTML結構

<!-- 說明:1. 類 picker-3d 是為了提供3d視角,如果不需要可以去掉2. 類 picker-slot-absolute 在3d視角中需要加上,因為下面相對定位的 picker-items 是要相對父容器進行transform的,如果不加,就會造成位移不正確3. DOM中所有的style樣式都是在初始化的時候加上的 --> <div class="picker picker-3d"><div class="picker-items"><div class="picker-slot picker-slot-absolute" style="flex:1;"><div class="picker-slot-wrapper" id="wrapper" style="height: 108px;"><div class="picker-item picker-selected" style="height:36px;line-height: 36px">1981</div><!-- ... --><div class="picker-item" style="height:36px;line-height: 36px">1999</div></div></div></div><div class="picker-center-highlight" style="height:36px;margin-top:-18px;"></div> </div>

1.2 初始化DOM

由于餓了么源碼中的picker是采用v-for指令生成的DOM,因此我這里只是簡單的用javascript來模擬一下DOM的生成。

var el = document.querySelector('#wrapper'); var animationFrameId = null; var currentValue; var itemHeight = 36; var visibleItemCount = 3; var valueIndex = 0; var rotateEffect = true;var datas = ['1981', '1982', '1983', '...', '1999'];// 如果支持3d視角,則給<div class="picker"></div>加上類"picker-3d" // <div class="picker-slot" style="flex:1;">加上類"picker-slot-absolute" if (rotateEffect) {var picker = document.querySelector('.picker');var pickerSlot = document.querySelector('.picker-slot');picker.classList.add('picker-3d');pickerSlot.classList.add('picker-slot-absolute'); }// 限定容器高度 el.style.height = `${visibleItemCount * itemHeight}px`;// 生成DOM var html = ''; datas.forEach(function(data, index) {html += `<div class="picker-item" style="height:36px;line-height:36px;">${data}</div>`; }); el.innerHTML = html;// 激活當前item var pickerItems = document.querySelectorAll('.picker-item'); pickerItems[valueIndex].classList.add('picker-selected');

1.3 初始化事件

總體上來說,picker的事件也包括滑動開始、滑動中、滑動結束。因為畢竟是移動端,滑動不可避免。這次,源碼中的對滑動事件進行了封裝,兼容了PC端以及排除了拖動和選擇造成的影響,具體看一下分析。`

/** * draggable.js * 只是起到一定的兼容性* 實質和直接調用 el.addEventListener('touchstart', startFn); 并沒有多大差別*/// 滑動開始 // touchstart 和 mousedown 可見對PC端的兼容 // onselectstart/ondragstart 直接return 可見排除了拖動和選擇 element.addEventListener(supportTouch ? 'touchstart' : 'mousedown', function(event) {if (isDragging) return;document.onselectstart = function() { return false; };document.ondragstart = function() { return false; };// ... });// 滑動結束 var endFn = function(event) {// 注銷事件if (!supportTouch) {document.removeEventListener('mousemove', moveFn);document.removeEventListener('mouseup', endFn);}document.onselectstart = null;document.ondragstart = null;isDragging = false;if (options.end) {options.end(supportTouch ? event.changedTouches[0] || event.touches[0] : event);} }

要是DOM跟隨自己在手機屏幕上的滑動而滑動,方法大同小異,無非就是在開始滑動記錄開始位置,滑動中實時計算位移,滑動結束之后將DOM滑動應該滑動的位置。這點可以參看前面一篇文章移動端效果之Swiper,這篇文章中有著相同的方法。這里重點講一下其中的區別

// 滑動開始的執行事件方法 start: function(event) {dragState = {range: getDragRange(),// ...startTranslateTop: translateUtil.getElementTranslate(el).top}; }

這其中有兩個方法,第一個getDragRange和第二個getElementTranslate(el).

  • 第一個函數的作用是獲取picker能夠滑動的最小和最大的位移,這將會在滑動結束事件中用到。關于如何計算,這里簡單提一下,向下滑動,最大不能超過最中間的item的最上方,這也就是為什么itemHeight * Math.floor(visibleItemCount / 2),而向上滑動,最大不能超過中間item的最下方,-itemHeight * (valuesLength - Math.ceil(visibleItemCount / 2)),細細想一下就好了。
  • 第二個函數的作用是獲取當前picker的transform值,作為下一次滑動計算的依據。其實感覺這樣挺費事,因為在touchend中最后肯定會計算translate值,我們只需要每次保存最后滑動的移動值就好了,而不要每次都要在DOM中取。
/*** translateUtil* 對瀏覽器對前綴支持的一些判斷* 檢測瀏覽器對3d屬性的支持情況* 獲取當前的translate值/清空picker的translate值/移動picker* 對于瀏覽器的檢測方面,這也算是一個比較好的工具類*/ var docStyle = document.documentElement.style; var engine; var translate3d = false;// 瀏覽器判斷 if (window.opera && Object.prototype.toString.call(opera) === '[object Opera]') {engine = 'presto'; } else if ('MozAppearance' in docStyle) {engine = 'gecko'; } else if ('WebkitAppearance' in docStyle) {engine = 'webkit'; } else if (typeof navigator.cpuClass === 'string') {engine = 'trident'; }// css前綴 var cssPrefix = {trident: '-ms-', // IEgecko: '-moz-', // FireFoxwebkit: '-webkit-', // Chrome/Safari/etc...presto: '-o-' // Opera }[engine];// style前綴 var vendorPrefix = {trident: 'ms',gecko: 'Moz',webkit: 'Webkit',presto: 'O' }[engine];var helpElem = document.createElement('div'); var perspectiveProperty = vendorPrefix + 'Perspective'; var transformProperty = vendorPrefix + 'Transform'; var transformStyleName = cssPrefix + 'transform'; var transitionProperty = vendorPrefix + 'Transition'; var transitionStyleName = cssPrefix + 'transition'; var transitionEndProperty = vendorPrefix.toLowerCase() + 'TransitionEnd';if (helpElem.style[perspectiveProperty] !== undefined) {translate3d = true; }// 講一下這個正則 // \s*(-?\d+(\.\d+?)?)px 這是一個單元,匹配這樣的 -23.15px, 剩下的應該就好理解了 var regexp = /translate\(\s*(-?\d+(\.\d+?)?)px,\s*(-?\d+(\.\d+?)?)px\)\s*translateZ\(0px\)/ig;

接下來看看滑動中

drag: function(event) {// 加上 dragging 類是為了清除過渡效果,在swiper中也有同樣的應用el.classList.add('dragging');dragState.left = event.pageX;dragState.top = event.pageY;var deltaY = dragState.top - dragState.startTop;// 計算當前的滑動位移var translate = dragState.startTranslateTop + deltaY;// 滑動元素translateUtil.translateElement(el, null, translate);velocityTranslate = translate - prevTranslate || translate;prevTranslate = translate;if (rotateEffect) {updateRotate(prevTranslate, pickerItems);} }

看到以上的代碼中有一個velocityTranslate,這個值有神馬作用,最開始我也不清楚,后面發現在滑動結束之后用到了,才明白了它代表了一個速率的位移值。什么是速率?就好比你快速滑動的時候,總希望它能夠慣性滑動一下,這個值乘以一個慣性值就可以得出一個慣性位移。看end中的代碼。

end: function() {// 添加過渡el.classList.remove('dragging');// 慣性值var momentumRatio = 7;var currentTranslate = translateUtil.getElementTranslate(el).top;var duration = new Date() - dragState.start;var momentumTranslate;if (duration < 300) {momentumTranslate = currentTranslate + velocityTranslate * momentumRatio;}// 加上慣性速率之后的位移值console.log('momentumTranslate', momentumTranslate);dragRange = dragState.range;setTimeout(function() {var translate;if (momentumTranslate) {translate = Math.round(momentumTranslate / itemHeight) * itemHeight;} else {translate = Math.round(currentTranslate / itemHeight) * itemHeight;}// 取得最終的位移值,// 必須為itemHeight的倍數// 在范圍的最大值和最小值中取translate = Math.max(Math.min(translate, dragRange[1]), dragRange[0]);translateUtil.translateElement(el, null, translate);// 計算得出當前位移下應該對應的實際值currentValue = translate2Value(translate);// 3d效果if (rotateEffect) {planUpdateRotate();}}, 10);dragState = {}; }

這就是整個picker的實現流程,撇開3d效果就可以使用了。下面看一下如何實現的3D效果。在doOnValuesChange中有一個最開始的初始化。

[].forEach.call(items, function(item, index) {translateUtil.translateElement(item, null, itemHeight * index); });

給每一個item設置了根據索引來的位移值,此時的每一個item的定位都必須是absolute的,這樣位移下來才是緊挨著的,不然可能中間都會有一個itemHeight的空格。

3D效果中最關鍵的一點就是如何進行翻轉角度的計算。在源碼中定義了一個常量對象:

var VISIBEL_ITEMS_ANGLE_MAP = {3: -45,5: -20,7: -15 };

可以看到,當只有3個可見元素的時候,高亮部分相對于X軸平行,而上一個item就必須繞X軸順時針旋轉45度,反之下一個item繞X軸逆時針旋轉45度。另外在其中有一段代碼特別繞,根據我的理解是這樣的:

// 當前item相對于頂部原本應該有的位移值 var itemOffsetTop = index * itemHeight; // 滑動過程中,相對于最開始的位置滑動的位移值 var translateOffset = dragRange[1] - currentTranslate;// 當應該有的位移值和滑動的位移值相等的時候,也就說明了當前的`item`被選中 // 也就是說此時當前的角度為0 var itemOffset = itemOffsetTop - translateOffset; var percentage = itemOffset / itemHeight;var angle = angleUnit * percentage; if (angle > 180) angle = 180; if (angle < -180) angle = -180;rotateElement(item, angle);

如果覺得太繞,其實也沒有必要按照他的這種做法來,我們只要想辦法確定每一個item相對于當前選中的item是處于上一個還是下一個,就可以根據此來計算角度。

2. 總結

關于餓了么中的picker組件就看了這么多,整體來說跟swiper中的滑動十分相似,其中的關鍵點在于最后的計算位移值來根據位移值滑動到正確的位置,至于怎么計算值,其實每個人的實現方式可能都是大同小異的,也沒要必要一定要按照源碼來,可以適當加入自己的理解,這樣或許寫起代碼來更加得心應手。這里只是個人的一點理解,希望能夠給自己也給大家提供一點幫助。

轉載于:https://www.cnblogs.com/rynxiao/p/7646879.html

總結

以上是生活随笔為你收集整理的移动端效果之Picker的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。