仿iPhone通讯录制作小程序自定义选择组件的实现


Posted in Javascript onMay 23, 2019

前言

近期闲来无事,想着闲着也是闲着,不如给自己搞点事情做!敢想敢做,于是选择了给微信小程序做个 仿iPhone通讯录 效果的自定义组件。

先来整理一下,瞧瞧需要实现的核心功能。

仿iPhone通讯录制作小程序自定义选择组件的实现

  1. 按照第一个字的首字母排序;
  2. 实现输入搜索功能;
  3. 侧边栏字母导航;

仿iPhone通讯录制作小程序自定义选择组件的实现

基本上分为3块:

  1. 顶部的搜索区域;
  2. 内容的展示区域;
  3. 侧边字母导航栏区域;
// index.wxml
<view class="main">
 <!-- 顶部搜索区域 -->
 <view class="header">
 </view>
 <!-- 内容区域 -->
 <scroll-view class="scroll">
 </scroll-view>
 <!-- 侧边导航 -->
 <view class="sub_nav">
 </view>
</view>

【顶部的搜索区域】

仿iPhone通讯录制作小程序自定义选择组件的实现

一目了然就直接贴代码了。

<view class="header">
 // 这里或许有人要问,为啥不用小程序的label组件呢。?_?
 // 原因就是...我就不用,你还能咬我?!^(oo)^
 // 哈哈哈哈~开个玩笑,其实是小程序的label组件还没支持input!
 <view class="label"> 
  <icon></icon>
  <input type="text" placeholder="搜索" />
 </view>
</view>

【内容的展示区域】

仿iPhone通讯录制作小程序自定义选择组件的实现

再说一目了然会不会被打呢?:joy:

根据图片就可以看出来,存在2个区域。

  1. 红框包围的外框,负责圈定展示的范围;
  2. 绿框包围的范围,包含有字母标题和对应的子项。

代码如下:

<scroll-view class="scroll">
 <view class="dl">
  <view class="dt">这里是字母标题。</view>
  <view class="dd">
  <span>这里当然是展示的内容啦。</span>
  </view>
 </view>
 </scroll-view>

【侧边字母导航栏区域】

为了节省一下文章的篇幅,这里就不贴图了,很简单,就是并排下来就好了。

<view class="sub_nav">
 <view class="option">这里是输出字母。</view>
</view>

接下来是wxss的样式了。

考虑到wxss的样式较多,我就直接贴 代码链接 吧,有兴趣的童鞋可以瞧瞧。

完成之后,是时候贴个效果图了。(不许吐槽丑,宝宝会不开心的!:pensive:)

仿iPhone通讯录制作小程序自定义选择组件的实现

结构样式弄完了,也贴一下自定组件的基础文件

// index.json
{
 "component": true
}
// index.js
Component({
 properties: {}, // 组件的对外属性
 data: {},  // 组件的内部数据
 lifetimes: {}, // 生命周期
 methods: {}  // 事件
});

现在开始实现功能了!!!

按照第一个字的首字母排序

说实话,实现这块功能呢,我是没啥头绪的,所以这个时候就要求助伟大的“度娘/Google”了。

经过楼主“遍寻网络”,查找到如下页面的源码参考:

仿iPhone通讯录制作小程序自定义选择组件的实现

因楼主问题,遗忘了该网址,如有知道的童鞋,贴个链接告诉下楼主,楼主立马麻溜的加上。 源码的原理大概描述下:

收录 20902 个汉字和 375 个多音字的 Unicode 编码,然后用JS切割首字母并转换成 Unicode 进行对比,最后返回对应首字母的拼音。

// 汉字对应的Unicode编码文件
// oMultiDiff = 多音字 | firstLetterMap = 汉字
import firstStore from './firstChineseLetter'; 

// 获取首字母拼音
function getFirstLetter (val) {
 const firstVal = val.charAt(0);
 if (/.*[\u4e00-\u9fa5]+.*/.test(firstVal)) {
 // 处理中文字符
 // 转换成Unicode编码,与firstStore里面的数据进行对比,然后返回对应的参数
 const code = firstVal.charCodeAt(0); // 转换成Unicode编码
 return code in firstStore.oMultiDiff ? firstStore.oMultiDiff[code] : firstStore.firstLetterMap.charAt(code - 19968);
 } else {
 // 这里处理非中文
 // 检测是否字母,如果是就直接返回大写的字母
 // 不是的话,返回“#”
 return /^[a-zA-Z]+$/.test(firstVal) ? firstVal.toUpperCase() : '#';
 }
}

getFirstLetter('东城区');
// 输出结果:D

firstChineseLetter.js地址

获取首字母的方法有了之后,就该对数据进行处理了。

首先定义一下组件所需要的参数。

Component({
 // 组件的对外属性
 properties: {
 data: { type: Array, value: [], }, // 组件外传递进来的数据
 attr: { type: String, value: 'label' }, // 需要进行首字母处理的属性,默认是"label"
 },
 ...
})

然后,针对组件外传递进来的数据,做一次转换。

// 静态数据的存储
const Static = {
 list: []
}

Component({
 ...
 methods: {
 // 初始/重置数据
 init () {
  const { data, attr } = this.properties;

  let changeData = [], // 转换后的数据
   inChangeData = {}; // 存储转换后的数据对应字母的索引值
   
  data.map(v => {
  // 获取首字母拼音
  let firstLetter = this.getFirstLetter(v[attr]); 
  
  // 循环对比检测
  firstLetter.split('').map(str => {
   if (str in inChangeData) {
   // 有首字母相同的项,
   // 则添加入已有的项里面
   changeData[inChangeData[str]].list.push(v);
   } else {
   // 没有首字母相同的项,
   // 则在尾部追加一条新的数据,
   // 储存对应的字母值(firstLetter),
   // 同时存储该字母对应的索引
   changeData.push({ firstLetter: str, list: [v] });
   inChangeData[str] = changeData.length - 1;
   }
  });
  });
  
  // 此时转换后的数组属于乱序,
  // 需要对乱序的数组进行排序
  changeData.sort((pre, next) => pre.firstLetter < next.firstLetter ? -1 : 1);
  
  // 若存在“#”项,将位置位移至底部
  if (changeData[0].firstLetter === '#') {
  const firstArr = changeData.splice(0, 1);
  changeData = [...changeData, ...firstArr];
  }

  // 存储转换后的数据,
  // this.data.list的数据对应页面的展示数据,因为有搜索功能,数据可能会变更,
  // 在静态的数据里面,也存储1份数据,方便后续的搜索等功能。
  this.setData({ list: changeData });
  Static.list = changeData;
 },
 }
 ...
});

初始化函数有了之后呢,当然是调用它啦。

Component({
 lifetimes: {
 // 在组件实例进入页面节点树时执行初始化数据
 attached () {
  this.init();
 }
 },
 observers: {
 // 考虑到组件传递的数据存在变更的可能,
 // 在数据变更的时候,也要做一次初始化
 'data, attr, icon' (data, attr) {
  this.init();
 }
 },
})

接下来是搜索功能啦~

先给页面搜索框加个监听事件(input)

<view class="main">
 ...
 <view class="header">
 <view class="label">
  <icon></icon>
  <input type="text" placeholder="搜索" value="{{ search }}" bindinput="searchData" />
 </view>
 </view>
 ...
</view>

接着是JS的事件

const Static = {
 list: []
}

Component({
 ...
 methods: {
 searchData (e) {
  const { value } = e.detail; // 用户输入的值
  const { list } = Static; // init存储的静态数据,用来做数据对比
  const { attr } = this.properties; // 要对比的属性值
  let result = [], tem = {};
  
  // 没有搜索内容,返回全部内容
  if (value.length === 0) { this.setData({ list: Static.list }); return; }

  // 检索搜索内容
  list.map(v => {
  // 获取所有跟value匹配上的数据
  const searchList = v.list.filter(v => v[attr].indexOf(value) !== -1);
  
  if (searchList.length > 0) {
   // 此处原理类似楼上init的对比,此处不细说,
   // 反正我懒我有理(0.0)
   if (v.firstLetter in tem) {
   const _list = result[tem[v.firstLetter]].lish;
   result[tem[v.firstLetter]].lish = [..._list, ...searchList];
   } else {
   result.push({ firstLetter: v.firstLetter, list: [...searchList] });
   tem[v.firstLetter] = result.length - 1;
   }
  }
  });
  
  // 存储数据
  this.setData({ list: result, search: value });
 }
 },
 ...
});

侧边栏字母导航

(突然觉得,写文好累啊!!!)

写这块的时候呢,楼主发现了iPhone通讯录侧边导航栏有个问题, 手指在字母导航栏上滑动的时候,有时候很难确认自己滑到了哪个区域?!

然鹅这个问题呢,楼主发现了微信的通讯录,针对这块添加了手指滑动的时候,添加了个结构来帮助用户确认目前所处的区域。

楼主本着学习的精神,借(chao)鉴(xi)了这个效果,来个效果图。

仿iPhone通讯录制作小程序自定义选择组件的实现

贴一下新的wxml结构

<!-- 侧边导航 -->
 <view class="sub_nav" id="subNav" catchtouchstart="subTouchStart" catchtouchmove="subTouchMove" catchtouchend="subTouchEnd">
 <view class="option" wx:for="{{ list }}" data-firstLetter="{{ item.firstLetter }}" wx:key="firstLetter">
  {{ item.firstLetter }}
  <!-- 以下这块就是新增的结构啦 S -->
  <view 
  class="max {{ item.firstLetter === scrollIntoView && subNavHint ? 'show' : '' }}" 
  data-desc="{{ item.firstLetter }}"
  ></view>
  <!-- 以上这块就是新增的结构啦 E -->
 </view>
 </view>
const Static = {
 list: [],
 timer: null
}

Component({
 ...
 data: {
 scrollIntoView: '', // 标记当前处于哪个字母
 subNavHint: false, // 控制借(chao)鉴(xi)微信效果的元素
 },
 methods: {
 subTouchStart () {
  this.setData({ subNavHint: true, scrollIntoView: '' });
 },
 subTouchEnd () {
  this.setData({ subNavHint: false });
 },
 subTouchMove (e) {
  // 获取字母导航栏元素对应的值
  const query = this.createSelectorQuery();
  query.select('#subNav').boundingClientRect();
  query.selectViewport().scrollOffset();
  query.exec(res => {
  const { clientY } = e.touches[0]; // Y轴的位置
  const DomTop = res[0].top; // 导航元素距离顶部的位置
  const { list } = this.data;
  
  // 计算索引,
  // 或许看到这里有人会疑问,为什么是除以20?
  // 因为样式里面,我写的高度是20px,所以每个字母的区域是20px。
  let index = Math.round((clientY - DomTop) / 20); 
  index = index >= list.length ? list.length - 1 : index; // 限制索引大于0
  index = index < 0 ? 0 : index; // 限制索引小于0
  // 限制结果重复赋值
  if (list[index].firstLetter !== this.data.scrollIntoView) {
   this.setData({ scrollIntoView: list[index].firstLetter });
   // 加个抖动效果
   wx.vibrateShort(); 
  }
  });
  }
 },
 }
 ...
});

结语

文章写到这呢,基本上核心的功能都已经实现啦~ :stuck_out_tongue_closed_eyes:(终于写完了...)

通过自己封装组件,楼主还是有挺大收获的!

当然,这个组件还有很多可以继续完善的地方,有兴趣的童鞋呢,可以提出你的优化建议,楼主有时(xing)间(qu)的话,会继续完善下去。

最后,还是推一下这个组件啦,希望它能帮到有需要的童鞋。

github地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
node.js开机自启动脚本文件
Dec 24 Javascript
DOM基础教程之使用DOM + Css
Jan 20 Javascript
JS+CSS实现TreeMenu二级树形菜单完整实例
Sep 18 Javascript
javascript模块化简单解析
Apr 07 Javascript
实例讲解JavaScript的Backbone.js框架中的View视图
May 05 Javascript
jQuery实现点击弹出背景变暗遮罩效果实例代码
Jun 24 Javascript
js放到head中失效的原因与解决方法
Mar 07 Javascript
Angular2.0/4.0 使用Echarts图表的示例代码
Dec 07 Javascript
基于Vue的SPA动态修改页面title的方法(推荐)
Jan 02 Javascript
JS实现DOM节点插入操作之子节点与兄弟节点插入操作示例
Jul 30 Javascript
Vue的H5页面唤起支付宝支付功能
Apr 18 Javascript
原生js实现分页效果
Sep 23 Javascript
判断js数据类型的函数实例详解
May 23 #Javascript
JS定义函数的几种常用方法小结
May 23 #Javascript
vue-test-utils初使用详解
May 23 #Javascript
了解前端理论:rscss和rsjs
May 23 #Javascript
微信小程序使用字体图标的方法
May 23 #Javascript
个人小程序接入支付解决方案
May 23 #Javascript
一篇文章介绍redux、react-redux、redux-saga总结
May 23 #Javascript
You might like
PHP 增加了对 .ZIP 文件的读取功能
2006/10/09 PHP
PHP字符串函数系列之nl2br(),在字符串中的每个新行 (\n) 之前插入 HTML 换行符br
2011/11/10 PHP
PHP UTF8中文字符截断函数代码
2012/09/11 PHP
利用jQuery的$.event.fix函数统一浏览器event事件处理
2009/12/21 Javascript
jQuery对象数据缓存Cache原理及jQuery.data方法区别介绍
2013/04/07 Javascript
jQuery中removeProp()方法用法实例
2015/01/05 Javascript
纯JS打造网页中checkbox和radio的美化效果
2016/10/13 Javascript
jQuery与JavaScript节点创建方法的对比
2016/11/18 Javascript
AngularJS全局scope与Isolate scope通信用法示例
2016/11/22 Javascript
深入学习jQuery中的data()
2016/12/22 Javascript
vue.js在标签属性中插入变量参数的方法
2018/03/06 Javascript
Vue ElementUi同时校验多个表单(巧用new promise)
2018/06/06 Javascript
Vuex 模块化使用详解
2019/07/31 Javascript
JS合并两个数组的3种方法详解
2019/10/24 Javascript
[04:07]显微镜下的DOTA2第八期——英雄复活动作
2014/06/24 DOTA
[01:36:17]DOTA2-DPC中国联赛 正赛 Ehome vs iG BO3 第一场 1月31日
2021/03/11 DOTA
python实现简单的TCP代理服务器
2014/10/08 Python
Python中的迭代器漫谈
2015/02/03 Python
详解Python2.x中对Unicode编码的使用
2015/04/03 Python
tensorflow创建变量以及根据名称查找变量
2018/03/10 Python
Django 后台获取文件列表 InMemoryUploadedFile的例子
2019/08/07 Python
详解python列表(list)的使用技巧及高级操作
2019/08/15 Python
Python中函数的返回值示例浅析
2019/08/28 Python
Python中xml和dict格式转换的示例代码
2019/11/07 Python
如何通过命令行进入python
2020/07/06 Python
python 使用建议与技巧分享(四)
2020/08/18 Python
python MD5加密的示例
2020/10/19 Python
CSS3的颜色渐变效果的示例代码
2017/09/29 HTML / CSS
美国摩托车头盔、零件、齿轮及配件商店:Cycle Gear
2019/06/12 全球购物
全球领先的在线cosplay服装商店:RoleCosplay
2020/01/18 全球购物
琳达·法罗眼镜英国官网:Linda Farrow英国
2021/01/19 全球购物
公司周年庆典邀请函
2014/01/12 职场文书
化工生产实习心得体会
2016/01/22 职场文书
教你使用pyinstaller打包Python教程
2021/05/27 Python
Win11 Build 21996.1 Dev版怎么样? win11系统截图欣赏
2021/11/21 数码科技
Windows 11上手初体验:任务栏和开始菜单等迎来大改
2021/11/21 数码科技