资讯专栏INFORMATION COLUMN

学习移动端组件 Picker一下

iOS122 / 2990人阅读

摘要:原文前言一个移动端的事件或者事件,具体看看怎么玩。初始化组件已经有了基本的组件,现在我们开始进行一个初始化。因为我们第一个元素需要向下移动一列,所以第一列应该是空的这个时候位置也是我们位移最大位置。看图最后目前只是一个最基本的例子。

原文: https://www.luoyangfu.com/art...
前言

一个移动端的touch 事件或者 mouse 事件,具体看看怎么玩。

先看看效果:

这里年月日都是使用创建好的Picker组件来实现的,在之前感谢博客园 @糊糊糊糊糊了, 原文地址.

原文中讲了实现Picker核心思路,我也是受益颇多,然后根据思路以及Github源码,终于写了自己想要的Picker,于是就有了记录,再次感谢.

多带带Picker HTML 结构

开发这个Picker, 观察Picker节点结构,Picker 是由定高隐藏元素的块, 一个定高不隐藏元素的的块,以及一个选择列表组成这三个部分。我们得知了这个Picker组成后很容易就可以写出来下面HTML的结构:

2016
2017
2018
2019
2020

这个就是最简单的 picker 结构了。

CSS 样式

咱们给这块结构添加样式,显示如下, 在这里我们默认元素的高度在css 中写死为 50px.

我们这里就写了关于 Picker 基础样式。

.picker {
  overflow: hidden;
  position: relative;
  z-index: 1;
}

.picker-wrapper {
  overflow: visible;
  height: calc(50px * 3);
}

.picker-item {
  height: 50px;
  line-height: 50px;
  text-align: center;
  color: #999;
}

.picker-item.active {
  color: #000;
}

.picker-center-highlight {
  height: 50px;
  position: absolute;
  width: 100%;
  top: 50%;
  margin-top: -25px;
  z-index: 2;
}

.picker-center-highlight::before, .picker-center-highlight::after {
  content: "";
  display: block;
  height: 2px;
  background-color: #000;
  width: 100%;
  transform: scaleY(0.5);
  position: absolute;
}

.picker-center-highlight::before {
  top: 0;
}

.picker-center-highlight::after {
  bottom: 0;
}

上面仅仅仅仅包含图片样式组成,后续会逐渐添加各种样式。

初始化Picker组件

已经有了基本的 Picker 组件,现在我们开始进行一个初始化。让第一个元素显示在正确的位置。
这里有两个基本问题需要先考虑一下:

元素的移动范围

元素的下标和位置交换

元素的移动范围

我们元素移动范围用下标来说的话,应该是 1 到 length, 这里length 就是传入Picker数据长度。

因为我们第一个元素需要向下移动一列,所以第一列应该是空的, 这个时候位置也是我们位移最大位置。当不断将Picker 向上位移什么时候为最小的位置呢?应该是有这样计算 (length - Math.ceil(count / 2)) * 50. 这里我们说的是显示3列情况, length 为数据长度, 50为单个Picker高度,上文已有。

下面直接看元素结算范围:

const getMoveRange = function() {
  const max = Math.floor(visibleCount / 2) * itemHeight;
  const min = (itemLength - Math.ceil(visibleCount / 2)) * -itemHeight;
  return [min, max]
}

这里就解决了第一个元素的范围问题。

这里使用两个数学函数:
Math.floor 取不大于该数的最大整数
Math.ceil 取不小于该数的最小整数
元素的下标和位置交换

这里需要两个函数分别来计算使用下标获取位置,使用位置来获取下标的。

通过下标获取元素位置时候有一点需要注意的是,我们元素位置是需要根据当前显示的个数进行偏移的,也就说,在计算之前,需要减去偏移量。偏移量刚好等于 Math.floor(count / 2)

const getTranslByIndex = function(index) {
  const offset = Math.floor(visibleCount / 2)
  
  if(index >= 0) {
    return (index - offset) * -itemHeight
  }
}

通过位移获取元素位置就简单很多了。

const getIndexByTransl = function(transl) {
  transl = Math.round(transl / itemHeight) * itemHeight;
  const index = - (transl - Math.floor(visibleCount / 2) * itemHeight) / itemHeight;
  
  return index;
}

计算用了三行代码, 第一行主要是转化当前位置靠近哪一个元素,得到具体translate,第二行 计算具体的 index, 这里计算语句前面使用 -减号, 主要因为 translate 减去显示个数的1/2后,总是负值,这里需要将负值转正,也可以使用 Math.abs 来替代。

上面我们就解决了两个问题。

初始化组件

首先需要组件初始化为上图的样子,这个时候,我们开始监听事件,这了我们主要监听 touchstart, touchmove, touchend 事件。

const translateEl = function(transl) {
  el.style.transform = `translateY(${transl}px)`;
};

const setSelectedEl = function(index) {
  Array.prototype.forEach.call(pickerItems, (item, idx) => {
    item.classList.remove("active");
  });
  pickerItems[index].classList.add("active");
};

function initEvent() {
  el.addEventListener("touchstart", function(e) {
    console.log("touchstart", e);
  });
  el.addEventListener("touchmove", function(e) {
    console.log("touchmove", e);
  });
  el.addEventListener("touchend", function(e) {
    console.log("touchenv", e);
  });
}

window.onload = function onload() {
  translateY = getTranslByIndex(0);
  setSelectedEl(0);
  translateEl();
  initEvent();
};

上面的 pickerItems 就是选中的picker 元素集合.

这里就对元素进行了touchstart,touchmove, touchend 监听。下面我进一步对事件做处理,让picker动起来。
为了让picker 在移动过程中有过度效果,增加如下css

.picker-wrapper {
    overflow: visible;
    height: calc(50px * 3);
    transition: all 0.3s ease-in-out; /* 这里就对 元素做了过渡动画处理*/
}

开始对事件进行处理

el.addEventListener("touchstart", function(e) {
    startAt = Date.now();
    startTop = e.touches[0].pageY;
    // 开始滚动的位置
    startTranslateY = translateY;
  });
  el.addEventListener("touchmove", function(e) {
    const deltaY = e.touches[0].pageY - startTop;
    translateY = startTranslateY + deltaY;
    velocityTranslate = translateY - prevTranslateY || translateY;

    prevTranslateY = translateY;
    translateEl();
  });
  el.addEventListener("touchend", function(e) {
    let momentumTranslate = 0;
    // 小于 300 就开始弹性滚动
    if (Date.now() - startAt < 300) {
      momentumTranslate = translateY + velocityTranslate * momentumRatio;
    }

    let translate = Math.round(translateY / itemHeight) * itemHeight;

    if (momentumTranslate) {
      translate = Math.round(momentumTranslate / itemHeight) * itemHeight;
    }

    const range = getMoveRange();
    translateY = Math.max(Math.min(translate, range[1]), range[0]);
    translateEl();
    const index = getIndexByTransl(translateY);
    setSelectedEl(index);
  });

  // 每个item 点击生效
  Array.prototype.forEach.call(pickerItems, function(item, index) {
    item.addEventListener("click", function(e) {
      setSelectedEl(index);
      translateY = getTranslByIndex(index);
      translateEl();
    });
  });

这里就对触摸事件做了处理,也对单个元素的点击做了监听。

const el = document.querySelector("#wrapper");
const itemHeight = 50;
const visibleCount = 3;
const itemLength = 5;
const pickerItems = document.querySelectorAll(".picker-item");
let startAt = Date.now();
let startTop = 0;
let translateY = 0;
let startTranslateY = 0;
let prevTranslateY = 0;
// 动力参数
let momentumRatio = 7;
// 速度速度位移
let velocityTranslate = 0;

这里再开头申明了一些内容,主要说明一下 momentumRatiovelocityTranslate 这两个,前者是动力系数用于短时间修改位移后惯性移动,后者是速度位移用于在用户移动过程中移动的量。

现在就完成了一个基本的Picker。看图:

最后

目前只是一个最基本的例子。如果说需要选择两侧有一个缩放呢。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105269.html

相关文章

  • 记 vue 移动开发 中的经验(2)

    摘要:官网还不断的访问不了。在此推荐一个移动端库按需引入二次封装组件列表的下拉刷新和上拉加载更多是移动端必须的组件。不用写死高度了,并且兼容对外提供了更加简明易用的刷新,回到顶部,获得和设置滚动条位置的方法统一的提示,免去重复代码。 按需引入mint-ui 本项目用了 mint-ui 作为基础ui框架,在使用中遇到不少问题。官网doc 还不断的访问不了。不过还是很感谢 mint-ui 团队。...

    Flands 评论0 收藏0
  • 地区picker 各选择器,优劣分析

    摘要:移动端选择器有很多,各大组件都有自己的,比如,,,等等。这次的地区选择,需要地区的省份市经纬度,还要设置第一次点开的时候是特定城市。引入样式和文件地区选择级联地区选择设定默认选项省份城市代码很简单,不懂的百度一下。移动端选择器picker有很多,各大ui组件都有自己的picker,比如light7,HUI,MUI,jqueryUI等等。但是,我发现他们都有各种各样的问题。这次的地区选择,需要...

    番茄西红柿 评论0 收藏0
  • 基于 vue 的 picker 组件 vue-awesome-picker

    摘要:本组件停止维护组件库请移步小程序组件库请移步有赞前端大量坑位,内推私信基于的移动端组件支持单列多列和联级数据内置时间日期数据滚轮效果颜色可配置已启用试试离线访问吧点击查看详细使用方法参照源码参数描述可选类型默认详细描述 ⚠️ DEPRECATED 本组件停止维护 ⚠️ Vue 组件库请移步 Vant ⚠️ 小程序组件库请移步 Vant Weapp ⚠️ 有赞前端大量坑位,内推私信 v...

    Lsnsh 评论0 收藏0
  • UI组件库从1到N开发心得-组件

    摘要:正文距离第一篇组件库文章发布已经过去个月了,在此期间利用零零散散的时间持续更新组件库,目前移动端组件库已经更新大类基础表单弹出层种组件供使用。链接组件库从到开发心得主页更改版版方案祝工作顺利邓文斌年月日正文 距离第一篇UI组件库文章发布已经过去3个月了,在此期间利用零零散散的时间持续更新owl-ui组件库,目前owl-ui移动端组件库已经更新3大类(基础、表单、弹出层)9种组件(Button...

    hzc 评论0 收藏0
  • 基于zepto的移动轻量级日期插件

    摘要:本文简单介绍近来写的一款基于的移动端轻量级日期插件。再来看看兼容性对于移动开发足矣最后就是在绑定事件的兼容性问题,不同厂商对于这个事件的定义并不一致,比如里面监听的是事件,但是在安卓里面监听事件完全没反应,经过一番,发现安卓需要监听事件。 前言 做过移动Web开发的同学都知道,移动端日期选择是很常见的需求。在PC端,我们有很丰富的选择,比较出名的就有Mobiscroll和jQuery ...

    ranwu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<