大家好,由于最近从事的是微信公众号和APP内嵌 H5开发,避免不了开发一些和native相同的操作功能,就如接下来说的 仿IOS滚轮选择器。

先来个截图:

接下来具体介绍如何实现的。能力有限避免不了错误请指出,有问题QQ邮箱 1766597067@qq.com

先来屡一下需求:

1.移动端用户手上下滑动,内容上下移动,用户手离开数字按照惯性移动一段距离。

2.当停止移动后,选中一个文字并且文字高亮,上面的值会变成你选中的文字。

3.可以连续滚动。

好了我们知道需求了,开始写吧。

写之前,想来一句 “上海天真蓝,可我在写代码”。

说起滚动,不得不提css3的  transform-style: preserve-3d;  backface-visibility: hidden;

(1)transform-style 属性规定如何在 3D 空间中呈现被嵌套的元素。值如下图:

我们使用preserve-3d 是让我们的值列表呈现3d效果,他是写在列表父级;

(2)backface-visivility 属性定义当元素不面向屏幕时是否可见。

我们使用hidden是背面不可见的,他是写在列表上

不过只有他们是无法完成这个艰巨界面的。只是这两个比较少见并少用,在此记录一下。

结合上面的知识点那我们怎么实现滚筒呢?

我实现的方法如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
  <style>
    *{
      padding: 0;
      margin: 0;
      list-style: none;
    }
    .wrapper{
      margin: 200px auto;
      width: 200px;
      position: relative;
    }
    ul{
      width: 100%;
      transform-style: preserve-3d;
      position: absolute;
      top: 40%;
      left:0;
      height: 34px;
    }
    li{
      backface-visibility: hidden;
      position: absolute;
      left: 0;
      top: 0;
      height: 34px;
      text-align: center;
      width: 100%;
    }
  </style>
</head>
<body>
<div class="wrapper">
  <ul>
    <li style="transform: rotate3d(1, 0, 0, 80deg) translate3d(0, 0, 100px)">27</li>
    <li style="transform: rotate3d(1, 0, 0, 60deg) translate3d(0, 0, 100px)">28</li>
    <li style="transform: rotate3d(1, 0, 0, 40deg) translate3d(0, 0, 100px)">29</li>
    <li style="transform: rotate3d(1, 0, 0, 20deg) translate3d(0, 0, 100px)">30</li>
    <li style="transform: rotate3d(1, 0, 0, 0deg) translate3d(0, 0, 100px)">1</li>
    <li style="transform: rotate3d(1, 0, 0, -20deg) translate3d(0, 0, 100px)">2</li>
    <li style="transform: rotate3d(1, 0, 0, -40deg) translate3d(0, 0, 100px)">3</li>
    <li style="transform: rotate3d(1, 0, 0, -60deg) translate3d(0, 0, 100px)">4</li>
    <li style="transform: rotate3d(1, 0, 0, -80deg) translate3d(0, 0, 100px)">5</li>
  </ul>
</div>
</body>
</html>

可以看到我是用到了定位,rotate3d 和 translate3d, 可能你会问为什么要用到translate3d 并且第三个参数写100px?

  我主要是用到的定位,都定位到一起了,也就是一个黑点了,哈哈。。。 然后用 transform 的 rotate3d  统一 沿X轴旋转元素 到一定的角度,然而我们要做滚筒,滚筒需要半径,所以我用translate3d 拉伸 Z 轴 (垂直屏幕)100px,

这样元素就沿着我拉伸前的原点旋转,半径是 100px; 大家可以复制代码运行一下,看看效果,如何有其他方法分享出来吧,共同学习进步。

说了这么多,跟vue有什么关系呢? 哈哈。。。你猜?

滚动用什么呢? 我之前用过 scroll  ios 需要加上  -webkit-overflow-scrolling: touch; 才能触发onscroll, 但是那种做法我试了一下,太麻烦,有滚动条,太垃圾。

这里我们用 touchstart / touchmove / touchend

 mounted() {
      this.$el.addEventListener('touchstart', this.listenerTouchStart, false);
      this.$el.addEventListener('touchmove', this.listenerTouchMove, false);
      this.$el.addEventListener('touchend', this.listenerTouchEnd, false);
 },
 methods: {
    listenerTouchStart(ev) {
        ev.stopPropagation();
        ev.preventDefault();
        isInertial = false;
        this.finger.startY = ev.targetTouches[0].pageY;
        this.finger.prevMove = this.finger.currentMove;
        this.finger.startTime = Date.now();
      },
      listenerTouchMove(ev) {
        ev.stopPropagation();
        ev.preventDefault();
        const move = (this.finger.startY - ev.targetTouches[0].pageY) + this.finger.prevMove;
        this.finger.currentMove = move;
        this.$refs.wheel.style.transform = `rotate3d(1, 0, 0, ${(move / lineHeight) * singleDeg}deg)`;
        this.updateRange(Math.round(move / lineHeight));
      },
      listenerTouchEnd(ev) {
        ev.stopPropagation();
        ev.preventDefault();
        this.finger.endY = ev.changedTouches[0].pageY;
        this.finger.endTime = Date.now();
        this.getInertiaDistance();
      },
}

我们在 start 时,缓存手触摸的的Y轴坐标 ,startTime 是为了后面touchend时,计算初速度  (一定距离 时间越短 速度越大,惯性滑动越长)

/**
       * 求移动速度(v = s / t),判断用户操作快慢,从而得到惯性的滑动距离
       */
      getInertiaDistance() {
        // 移动距离
        const s = this.finger.startY - this.finger.endY;
        // 移动时间
        const t = this.finger.endTime - this.finger.startTime;
        // 移动速度
        const v = s / t;
        const absV = Math.abs(v);
        isInertial = true;
        this.inertia(absV, Math.floor(absV / v), 0);
      },
      /**
       * 用户结束滑动,应该慢慢放慢,最终停止。从而需要 a(加速度)
       * @param start 开始速度
       * @param position 速度方向,值: 正负1
       * @param target 结束速度
       */
      inertia(start, position, target) {
        if (start <= target || !isInertial) {
          this.animate.stop();
          this.finger.prevMove = this.finger.currentMove;
          this.updateRange(Math.round(this.finger.currentMove / lineHeight));
          this.getSelectValue(this.finger.currentMove);
          return;
        }
        // 这段时间走的位移 S = vt + 1/2at^2;
        const move = (position * start * (1000 / 60)) + (0.5 * a * (1000 / 60) * (1000 / 60)) + this.finger.currentMove;
        // 根据求末速度公式: v末 = v初 + at
        const newStart = (position * start) + (a * (1000 / 60));
        let moveDeg = (move / lineHeight) * singleDeg;
        let actualMove = move;
        // 已经到达目标
        if (newStart <= target) {
          moveDeg = Math.round(move / lineHeight) * singleDeg;
          actualMove = Math.round(move / lineHeight) * lineHeight;
          this.$refs.wheel.style.transition = 'transform 700ms cubic-bezier(0.19, 1, 0.22, 1)';
        } else {
          this.$refs.wheel.style.transition = '';
        }
        this.finger.currentMove = actualMove;
        this.$refs.wheel.style.transform = `rotate3d(1, 0, 0, ${moveDeg}deg)`;
        this.updateRange(Math.round(this.finger.currentMove / lineHeight));
        this.animate.start(this.inertia.bind(this, newStart, position, target));
      }

这里动画是 requestAnimationFrame  我稍微封装了一下,里面用到的公式我已经标注;

我们需要连续滚动,所以界面需要连续刷新,不断更新数字(可能有更好的方法吧)

computed: {
      scrollValues() {
        const result = [];
        for (let i = this.range.start; i <= this.range.end; i += 1) {
          result.push({
            value: this.getRangeData(i),
            index: i, // 这里是旋转参数
          });
        }
        return result;
      },
      getListTop() {
        return {
          top: `${radius - Math.round(lineHeight / 2)}px`,
          height: `${lineHeight}px`
        };
      },
      getWrapperHeight() {
        return {
          height: `${2 * radius}px`,
        };
      },
      getCoverStyle() {
        return {
          backgroundSize: `100% ${radius - Math.round(lineHeight / 2)}px`,
        };
      },
      getDividerStyle() {
        return {
          top: `${radius - Math.round(lineHeight / 2)}px`,
          height: `${lineHeight - 2}px`,
        };
      },
      animate() {
        return new Animate();
      }
    },

最后我把所有的变量提取出来,到时候能根据用户要求显示不同情况

 const a = -0.003; // 加速度
  const radius = 100; // 半径
  const lineHeight = 36; // 文字行高
  let isInertial = false; // 是否正在惯性滑动
  // 根据三角形余弦公式
  // 反余弦得到弧度再转换为度数,这个度数是单行文字所占有的。
  let deg = Math.round((Math.acos((((radius * radius) + (radius * radius)) - (lineHeight * lineHeight)) / (2 * radius * radius)) * 180) / Math.PI);
  // deg这个值须360能整除,因为当滚动列占满一周后可以再次均匀的覆盖在上一周文字上;滚动时不会出现错位
  while (360 % deg !== 0 && deg <= 360) {
    deg += 1;
  }
  const singleDeg = deg;
  // 半圆下的内容条数
  const space = Math.floor((360 / singleDeg) / 2);

最后附上github源码链接 大家可以下载运行

vue 2 仿IOS 滚轮选择器 从入门到精通 (一)的更多相关文章

  1. iOS开发-UI 从入门到精通(三)

    iOS开发-UI 从入门到精通(三)是对 iOS开发-UI 从入门到精通(一)知识点的综合练习,搭建一个简单地登陆界面,增强实战经验,为以后做开发打下坚实的基础! ※在这里我们还要强调一下,开发环境和 ...

  2. iOS开发-UI 从入门到精通(二)

    iOS开发-UI 从入门到精通(二)是对 iOS开发-UI 从入门到精通(一)知识点的巩固,主要以习题练习为主,增强实战经验,为以后做开发打下坚实的基础! ※开发环境和注意事项: 1.前期iOS-UI ...

  3. JavaScript 仿ios滑动选择器

    从git上找了一个,不过不是我想要的,更改了许多.到了我想要的效果: roller_selector_0_9.js 首先上js: "use strict"; /* * Author ...

  4. iOS开发-UI 从入门到精通(四)

    一.UITextField 1.UITextField是什么? (1)UITextField(输入框):是控制文本输入和显示的控件.在App中UITextField出现频率也比较高: (2)iOS系统 ...

  5. iOS开发-UI 从入门到精通(一)

    一.UI概述 (1)UI(User Interface)用户界面,用户能看到的各种各样的页面元素: (2)iOS App = 各种各样的UI控件+业务逻辑和算法: (3)想要开发出一款精美的应用程序, ...

  6. vue项目实基础到实战,入门到精通,移动商城

    最近发现许多的朋友都问我有没有vue项目的案例学习,最近正在学习vue,在这可以分享给大家,希望大家学有所成,相互交流共同进步,先不说了,吃个宵夜. 就这么多吧,需要的可以在下方留言或者加qq:116 ...

  7. iOS开发-UI 从入门到精通(五)

    近日在做项目的时候,为了快捷适配屏幕采用了Storyboard,添加约束以后运行后发现一个问题(下面将以普通案例展示该问题);在4.7 甚至更大的屏幕下是没有问题的,如下图(4.7屏幕): 但是放到更 ...

  8. Android-PickerView【仿iOS的PickerView控件,并封装了时间选择和选项选择这两种选择器】使用

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 本文主要演示Android-PickerView的选项选择器.时间选择器的简单运用.由于每一个版本略有不用,所以实际使用方式以git ...

  9. JS实战 · 仿css样式选择器

    代码如下: <html> <head>     <meta http-equiv="Content-Type" content="text/ ...

随机推荐

  1. pongo英雄会-幸运数题解

    显然我们只要知道1~x范围有多少幸运数(用f(x)表示),lucky(x,y)=f(y)-f(x-1). 解法1. 计算排列数 由于y<=1000000000这个规模,我们不能暴力验证每个数是否 ...

  2. JavaWeb(一)Servlet中乱码解决与转发和重定向的区别

    前言 前面其实已经把Servlet中所有的内容都介绍完了,这篇讲补充一点乱码和重定向与转发之间的区别! 一.request请求参数出现乱码问题 1.1.get请求 1)乱码示例 get请求的参数是在u ...

  3. HDU1305 Immediate Decodability(水题字典树)

    巧了,昨天刚刚写了个字典树,手到擒来,233. Problem Description An encoding of a set of symbols is said to be immediatel ...

  4. Android使用RxJava+Retrofit2+Okhttp+MVP练习的APP

    Android使用RxJava+Retrofit2+Okhttp+MVP练习的APP 项目截图     这是我的目录结构 五步使用RxJava+Retrofit2+Okhttp+RxCache 第一步 ...

  5. 组件 layui 常用控件输入框

    一.普通输入框 input <div class="layui-form-item"> <label class="layui-form-label&q ...

  6. github部分有意思的库记录

    1.MMDrawerController (抽屉框架) https://github.com/mutualmobile/MMDrawerController 2.ijkplayer视频直播框架 htt ...

  7. [置顶] win10 uwp 参考

    态度随意申请专栏,没想到通过 看了我的博客,都是在别的大神博客看到,然后修改他们的 我看到的大神博客 东邪独孤 http://www.cnblogs.com/tcjiaan/ 老周,买了他的<W ...

  8. 从一个简单案例上手Spring MVC,同时分析Spring MVC面试问题

    很多公司都会用Spring MVC,而且初级程序员在面试时,一定会被问到这方面的问题,所以这里我们来通过一个简单的案例来分析Spring MVC,事实上,我们在培训中就用这个举例,很多零基础的程序员能 ...

  9. IntelliJ IDEA 2016.2激活

    激活码 43B4A73YYJ-eyJsaWNlbnNlSWQiOiI0M0I0QTczWVlKIiwibGljZW5zZWVOYW1lIjoibGFuIHl1IiwiYXNzaWduZWVOYW1lI ...

  10. (转)Nginx与tomcat组合的简单使用

    原文出自:http://www.cnblogs.com/naaoveGIS/ 1.背景 项目中瓦片资源越来越多,如果提高瓦片的访问效率是一个需要解决的问题.这里,我们考虑使用Nginx来代理静态资源进 ...