zoukankan      html  css  js  c++  java
  • vue 2 仿IOS 滚轮选择器 从入门到精通 (一)

    大家好,由于最近从事的是微信公众号和APP内嵌 H5开发,避免不了开发一些和native相同的操作功能,就如接下来说的 仿IOS滚轮选择器。github源码链接 https://github.com/zhangKunUserGit/vue-component大家可以下载运行

    先来个截图:

    接下来具体介绍如何实现的。能力有限避免不了错误请指出,有问题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是背面不可见的,他是写在列表上

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

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

    我实现的方法如下:(transform-style为什么子元素需要定位?

    <!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  统一 沿Y轴旋转元素 到一定的角度,然而我们要做滚筒,滚筒需要半径,所以我用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源码链接 https://github.com/zhangKunUserGit/vue-component大家可以下载运行

  • 相关阅读:
    vue 如何点击按钮返回上一页
    vue遍历数组和对象的方法以及他们之间的区别
    css隐藏滚动条
    DOM编程以及domReady加载的几种方式
    修改默认滚动条默认样式
    面试题集锦
    正则表达式
    闭包及应用以及顺序处理ajax请求
    实现自己的(模仿jquery)toggle函数
    Asp.Net与SEO Viewstate优化终极解决方案
  • 原文地址:https://www.cnblogs.com/zhangkunweb/p/iosSelect.html
Copyright © 2011-2022 走看看