首頁 > web前端 > js教程 > 主體

行動端效果中Picke的實作方法

一个新手
發布: 2017-10-12 09:41:55
原創
2790 人瀏覽過

寫在前面

接著前面的行動端效果的研究,這次來看看行動端效果中Picke的實作方法選擇器的實作原理

行動端效果之Swiper

程式碼看這裡:github

行動端效果中Picke的實作方法

1. 核心解析

1.1 基本HTML結構


<!--     说明:    
1. 类 行動端效果中Picke的實作方法-3d 是为了提供3d视角,如果不需要可以去掉    
2. 类 行動端效果中Picke的實作方法-slot-absolute 在3d视角中需要加上,因为下面相对定位的 行動端效果中Picke的實作方法-items 是要相对父容器进行    transform的,如果不加,就会造成位移不正确    3. DOM中所有的style样式都是在初始化的时候加上的--><p class="行動端效果中Picke的實作方法 行動端效果中Picke的實作方法-3d">
    <p class="行動端效果中Picke的實作方法-items">
        <p class="行動端效果中Picke的實作方法-slot 行動端效果中Picke的實作方法-slot-absolute" style="flex:1;">
            <p class="行動端效果中Picke的實作方法-slot-wrapper" id="wrapper" style="height: 108px;">
                <p class="行動端效果中Picke的實作方法-item 行動端效果中Picke的實作方法-selected" style="height:36px;line-height: 36px">1981</p>
                <!-- ... -->
                <p class="行動端效果中Picke的實作方法-item" style="height:36px;line-height: 36px">1999</p>
            </p>
        </p>
    </p>
    <p class="行動端效果中Picke的實作方法-center-highlight" style="height:36px;margin-top:-18px;"></p></p>
登入後複製

1.2 初始化DOM

由於餓了麼源碼中的行動端效果中Picke的實作方法是採用v-for指令產生的DOM,因此我這裡只是簡單的用javascript來模擬DOM的生成。


var el = document.querySelector(&#39;#wrapper&#39;);
var animationFrameId = null;
var currentValue;
var itemHeight = 36;
var visibleItemCount = 3;
var valueIndex = 0;
var rotateEffect = true;
var datas = [&#39;1981&#39;, &#39;1982&#39;, &#39;1983&#39;, &#39;...&#39;, &#39;1999&#39;];// 如果支持3d视角,则给<p class="行動端效果中Picke的實作方法"></p>加上类"行動端效果中Picke的實作方法-3d"// <p class="行動端效果中Picke的實作方法-slot" style="flex:1;">加上类"行動端效果中Picke的實作方法-slot-absolute"if (rotateEffect) {
    var 行動端效果中Picke的實作方法 = document.querySelector(&#39;.行動端效果中Picke的實作方法&#39;);
    var 行動端效果中Picke的實作方法Slot = document.querySelector(&#39;.行動端效果中Picke的實作方法-slot&#39;);
    行動端效果中Picke的實作方法.classList.add(&#39;行動端效果中Picke的實作方法-3d&#39;);
    行動端效果中Picke的實作方法Slot.classList.add(&#39;行動端效果中Picke的實作方法-slot-absolute&#39;);}// 限定容器高度el.style.height = `${visibleItemCount * itemHeight}px`;// 生成DOMvar html = &#39;&#39;;datas.forEach(function(data, index) {
    html += `<p class="行動端效果中Picke的實作方法-item" style="height:36px;line-height:36px;">${data}</p>`;});el.innerHTML = html;// 激活当前itemvar 行動端效果中Picke的實作方法Items = document.querySelectorAll(&#39;.行動端效果中Picke的實作方法-item&#39;);行動端效果中Picke的實作方法Items[valueIndex].classList.add(&#39;行動端效果中Picke的實作方法-selected&#39;);
登入後複製

1.3 初始化事件

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


/**  * draggable.js  * 只是起到一定的兼容性 * 实质和直接调用 el.addEventListener(&#39;touchstart&#39;, startFn); 并没有多大差别 */// 滑动开始// touchstart 和 mousedown 可见对PC端的兼容// onselectstart/ondragstart 直接return 可见排除了拖动和选择element.addEventListener(supportTouch ? &#39;touchstart&#39; : &#39;mousedown&#39;, function(event) {
    if (isDragging) return;
    document.onselectstart = function() { return false; };
    document.ondragstart = function() { return false; };

    // ...});// 滑动结束var endFn = function(event) {
    // 注销事件
    if (!supportTouch) {
        document.removeEventListener(&#39;mousemove&#39;, moveFn);
        document.removeEventListener(&#39;mouseup&#39;, 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 ).

  • 第一個函數的作用是取得行動端效果中Picke的實作方法能夠滑動的最小和最大的位移,這將會在滑動結束事件中使用到。關於如何計算,這裡簡單提一下,向下滑動,最大不能超過最中間的item的最上方,這也就是為什麼itemHeight * Math.floor(visibleItemCount / 2) ,而向上滑動,最大不能超過中間item的最下方,-itemHeight * (valuesLength - Math.ceil(visibleItemCount / 2)),細細想一下就好了。

  • 第二個函數的作用是取得目前行動端效果中Picke的實作方法transform值,作為下一次滑動計算的依據。其實感覺這樣挺費事,因為在touchend中最後肯定會計算translate值,我們只需要每次保存最後滑動的移動值就好了,而不要每次都要在DOM中取。


/** * translateUtil * 对浏览器对前缀支持的一些判断 * 检测浏览器对3d属性的支持情况 * 获取当前的translate值/清空行動端效果中Picke的實作方法的translate值/移动行動端效果中Picke的實作方法 * 对于浏览器的检测方面,这也算是一个比较好的工具类 */var docStyle = document.documentElement.style;var engine;var translate3d = false;// 浏览器判断if (window.opera && Object.prototype.toString.call(opera) === &#39;[object Opera]&#39;) {
    engine = &#39;presto&#39;;} else if (&#39;MozAppearance&#39; in docStyle) {
    engine = &#39;gecko&#39;;} else if (&#39;WebkitAppearance&#39; in docStyle) {
    engine = &#39;webkit&#39;;} else if (typeof navigator.cpuClass === &#39;string&#39;) {
    engine = &#39;trident&#39;;}// css前缀var cssPrefix = {
    trident: &#39;-ms-&#39;,        // IE
    gecko: &#39;-moz-&#39;,         // FireFox
    webkit: &#39;-webkit-&#39;,     // Chrome/Safari/etc...
    presto: &#39;-o-&#39;           // Opera}[engine];// style前缀var vendorPrefix = {
    trident: &#39;ms&#39;,
    gecko: &#39;Moz&#39;,
    webkit: &#39;Webkit&#39;,
    presto: &#39;O&#39;}[engine];var helpElem = document.createElement(&#39;p&#39;);var perspectiveProperty = vendorPrefix + &#39;Perspective&#39;;var transformProperty = vendorPrefix + &#39;Transform&#39;;var transformStyleName = cssPrefix + &#39;transform&#39;;var transitionProperty = vendorPrefix + &#39;Transition&#39;;var transitionStyleName = cssPrefix + &#39;transition&#39;;var transitionEndProperty = vendorPrefix.toLowerCase() + &#39;TransitionEnd&#39;;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(&#39;dragging&#39;);

    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, 行動端效果中Picke的實作方法Items);
    }}
登入後複製

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


end: function() {
    // 添加过渡
    el.classList.remove(&#39;dragging&#39;);
    // 惯性值
    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(&#39;momentumTranslate&#39;, 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 = {};}
登入後複製

這就是整個行動端效果中Picke的實作方法的實作流程,撇開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度,反之下一個itemX軸逆時針旋轉45度。另外在其中有一段程式碼特別繞,根據我的理解是這樣的:


// 当前item相对于顶部原本应该有的位移值var itemOffsetTop = index * itemHeight; // 滑动过程中,相对于最开始的位置滑动的位移值var translateOffset = dragRange[1] - currentTranslate;// 当应该有的位移值和滑动的位移值相等的时候,也就说明了当前的`item`被选中// 也就是说此时当前的角度为0var 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. 總結

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

以上是行動端效果中Picke的實作方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!