目录
写在前面
1. 核心解析
1.1 基本HTML结构
1.2 初始化DOM
1.3 初始化事件
2. 总结
首页 web前端 js教程 移动端效果中Picke的实现方法

移动端效果中Picke的实现方法

Oct 12, 2017 am 09:41 AM
实现 方法

写在前面

接着前面的移动端效果的研究,这次来看看picker选择器的实现原理

移动端效果之Swiper

代码看这里:github

picker

1. 核心解析

1.1 基本HTML结构


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

1.2 初始化DOM

由于饿了么源码中的picker是采用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="picker"></p>加上类"picker-3d"// <p class="picker-slot" style="flex:1;">加上类"picker-slot-absolute"if (rotateEffect) {
    var picker = document.querySelector(&#39;.picker&#39;);
    var pickerSlot = document.querySelector(&#39;.picker-slot&#39;);
    picker.classList.add(&#39;picker-3d&#39;);
    pickerSlot.classList.add(&#39;picker-slot-absolute&#39;);}// 限定容器高度el.style.height = `${visibleItemCount * itemHeight}px`;// 生成DOMvar html = &#39;&#39;;datas.forEach(function(data, index) {
    html += `<p class="picker-item" style="height:36px;line-height:36px;">${data}</p>`;});el.innerHTML = html;// 激活当前itemvar pickerItems = document.querySelectorAll(&#39;.picker-item&#39;);pickerItems[valueIndex].classList.add(&#39;picker-selected&#39;);
登录后复制

1.3 初始化事件

总体上来说,picker的事件也包括滑动开始、滑动中、滑动结束。因为毕竟是移动端,滑动不可避免。这次,源码中的对滑动事件进行了封装,兼容了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).

  • 第一个函数的作用是获取picker能够滑动的最小和最大的位移,这将会在滑动结束事件中用到。关于如何计算,这里简单提一下,向下滑动,最大不能超过最中间的item的最上方,这也就是为什么itemHeight * Math.floor(visibleItemCount / 2),而向上滑动,最大不能超过中间item的最下方,-itemHeight * (valuesLength - Math.ceil(visibleItemCount / 2)),细细想一下就好了。

  • 第二个函数的作用是获取当前pickertransform值,作为下一次滑动计算的依据。其实感觉这样挺费事,因为在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) === &#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, pickerItems);
    }}
登录后复制

看到以上的代码中有一个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 = {};}
登录后复制

这就是整个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度,反之下一个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. 总结

关于饿了么中的picker组件就看了这么多,整体来说跟swiper中的滑动十分相似,其中的关键点在于最后的计算位移值来根据位移值滑动到正确的位置,至于怎么计算值,其实每个人的实现方式可能都是大同小异的,也没要必要一定要按照源码来,可以适当加入自己的理解,这样或许写起代码来更加得心应手。这里只是个人的一点理解,希望能够给自己也给大家提供一点帮助。

以上是移动端效果中Picke的实现方法的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1674
14
CakePHP 教程
1429
52
Laravel 教程
1333
25
PHP教程
1278
29
C# 教程
1257
24
微信删除的人如何找回(简单教程告诉你如何恢复被删除的联系人) 微信删除的人如何找回(简单教程告诉你如何恢复被删除的联系人) May 01, 2024 pm 12:01 PM

而后悔莫及、人们常常会因为一些原因不小心将某些联系人删除、微信作为一款广泛使用的社交软件。帮助用户解决这一问题,本文将介绍如何通过简单的方法找回被删除的联系人。1.了解微信联系人删除机制这为我们找回被删除的联系人提供了可能性、微信中的联系人删除机制是将其从通讯录中移除,但并未完全删除。2.使用微信内置“通讯录恢复”功能微信提供了“通讯录恢复”节省时间和精力,用户可以通过该功能快速找回之前被删除的联系人,功能。3.进入微信设置页面点击右下角,打开微信应用“我”再点击右上角设置图标、进入设置页面,,

七彩虹主板怎么进入bios?教你两种方法 七彩虹主板怎么进入bios?教你两种方法 Mar 13, 2024 pm 06:01 PM

  七彩虹主板在中国国内市场享有较高的知名度和市场占有率,但是有些七彩虹主板的用户还不清楚怎么进入bios进行设置呢?针对这一情况,小编专门为大家带来了两种进入七彩虹主板bios的方法,快来试试吧!  方法一:使用u盘启动快捷键直接进入u盘装系统  七彩虹主板一键启动u盘的快捷键是ESC或F11,首先使用黑鲨装机大师制作一个黑鲨U盘启动盘,然后开启电脑,当看到开机画面的时候,连续按下键盘上的ESC或F11键以后将会进入到一个启动项顺序选择的窗口,将光标移动到显示“USB”的地方,然

怎么在番茄免费小说app中写小说 分享番茄小说写小说方法教程 怎么在番茄免费小说app中写小说 分享番茄小说写小说方法教程 Mar 28, 2024 pm 12:50 PM

  番茄小说是一款非常热门的小说阅读软件,我们在番茄小说中经常会有新的小说和漫画可以去阅读,每一本小说和漫画都很有意思,很多小伙伴也想着要去写小说来赚取赚取零花钱,在把自己想要写的小说内容编辑成文字,那么我们要怎么样在这里面去写小说呢?小伙伴们都不知道,那就让我们一起到本站本站中花点时间来看写小说的方法介绍吧。分享番茄小说写小说方法教程  1、首先在手机上打开番茄免费小说app,点击个人中心——作家中心  2、跳转到番茄作家助手页面——点击创建新书在小说的结

手机版龙蛋孵化方法大揭秘(一步一步教你如何成功孵化手机版龙蛋) 手机版龙蛋孵化方法大揭秘(一步一步教你如何成功孵化手机版龙蛋) May 04, 2024 pm 06:01 PM

手机游戏成为了人们生活中不可或缺的一部分,随着科技的发展。它以其可爱的龙蛋形象和有趣的孵化过程吸引了众多玩家的关注,而其中一款备受瞩目的游戏就是手机版龙蛋。帮助玩家们在游戏中更好地培养和成长自己的小龙,本文将向大家介绍手机版龙蛋的孵化方法。1.选择合适的龙蛋种类玩家需要仔细选择自己喜欢并且适合自己的龙蛋种类,根据游戏中提供的不同种类的龙蛋属性和能力。2.提升孵化机的等级玩家需要通过完成任务和收集道具来提升孵化机的等级,孵化机的等级决定了孵化速度和孵化成功率。3.收集孵化所需的资源玩家需要在游戏中

华为手机如何实现双微信登录? 华为手机如何实现双微信登录? Mar 24, 2024 am 11:27 AM

华为手机如何实现双微信登录?随着社交媒体的兴起,微信已经成为人们日常生活中不可或缺的沟通工具之一。然而,许多人可能会遇到一个问题:在同一部手机上同时登录多个微信账号。对于华为手机用户来说,实现双微信登录并不困难,本文将介绍华为手机如何实现双微信登录的方法。首先,华为手机自带的EMUI系统提供了一个很便利的功能——应用双开。通过应用双开功能,用户可以在手机上同

手机字体大小设置方法(轻松调整手机字体大小) 手机字体大小设置方法(轻松调整手机字体大小) May 07, 2024 pm 03:34 PM

字体大小的设置成为了一项重要的个性化需求,随着手机成为人们日常生活的重要工具。以满足不同用户的需求、本文将介绍如何通过简单的操作,提升手机使用体验,调整手机字体大小。为什么需要调整手机字体大小-调整字体大小可以使文字更清晰易读-适合不同年龄段用户的阅读需求-方便视力不佳的用户使用手机系统自带字体大小设置功能-如何进入系统设置界面-在设置界面中找到并进入"显示"选项-找到"字体大小"选项并进行调整第三方应用调整字体大小-下载并安装支持字体大小调整的应用程序-打开应用程序并进入相关设置界面-根据个人

PHP编程指南:实现斐波那契数列的方法 PHP编程指南:实现斐波那契数列的方法 Mar 20, 2024 pm 04:54 PM

编程语言PHP是一种用于Web开发的强大工具,能够支持多种不同的编程逻辑和算法。其中,实现斐波那契数列是一个常见且经典的编程问题。在这篇文章中,将介绍如何使用PHP编程语言来实现斐波那契数列的方法,并附上具体的代码示例。斐波那契数列是一个数学上的序列,其定义如下:数列的第一个和第二个元素为1,从第三个元素开始,每个元素的值等于前两个元素的和。数列的前几个元

快速掌握:华为手机开启两个微信账号方法大揭秘! 快速掌握:华为手机开启两个微信账号方法大揭秘! Mar 23, 2024 am 10:42 AM

在当今社会,手机已经成为我们生活中不可或缺的一部分。而微信作为我们日常沟通、工作、生活的重要工具,更是经常被使用。然而,在处理不同事务时可能需要分开两个微信账号,这就要求手机能够支持同时登录两个微信账号。华为手机作为国内知名品牌,很多人使用,那么华为手机开启两个微信账号的方法是怎样的呢?下面就来揭秘一下这个方法。首先,要在华为手机上同时使用两个微信账号,最简

See all articles