仿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 相关文章推荐
javaScript NameSpace 简单说明介绍
Jul 18 Javascript
js 定时器setTimeout无法调用局部变量的解决办法
Nov 28 Javascript
两种方法基于jQuery实现IE浏览器兼容placeholder效果
Oct 14 Javascript
JQuery右键菜单插件ContextMenu使用指南
Dec 19 Javascript
JS函数this的用法实例分析
Feb 05 Javascript
vuejs2.0运用原生js实现简单的拖拽元素功能示例
Feb 24 Javascript
通过cordova将vue项目打包为webapp的方法
Feb 02 Javascript
vue router动态路由设置参数可选问题
Aug 21 Javascript
Vue实现点击当前元素以外的地方隐藏当前元素(实现思路)
Dec 04 Javascript
vue+canvas实现拼图小游戏
Sep 18 Javascript
如何手写一个简易的 Vuex
Oct 10 Javascript
如何使用原生Js实现随机点名详解
Jan 06 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生成网页快照 不用COM不用扩展.
2010/02/11 PHP
php中日期加减法运算实现代码
2011/12/08 PHP
PHP filesize函数用法浅析
2019/02/15 PHP
Prototype Template对象 学习
2009/07/19 Javascript
javascript中length属性的探索
2011/07/31 Javascript
完美解决IE低版本不支持call与apply的问题
2013/12/05 Javascript
jQuery产品间断向下滚动效果核心代码
2014/05/08 Javascript
一个JavaScript获取元素当前高度的实例
2014/10/29 Javascript
javascript实现在网页任意处点左键弹出隐藏菜单的方法
2015/05/13 Javascript
jQuery的ajax和遍历数组json实例代码
2016/08/01 Javascript
jQueryUI Datepicker组件设置日期高亮
2016/10/13 Javascript
create-react-app构建项目慢的解决方法
2018/03/14 Javascript
vue项目base64字符串转图片的实现代码
2018/07/13 Javascript
React实现全局组件的Toast轻提示效果
2018/09/21 Javascript
JavaScript ES6中的简写语法总结与使用技巧
2018/12/30 Javascript
如何在Vue.js中实现标签页组件详解
2019/01/02 Javascript
JS实现页面鼠标点击出现图片特效
2020/08/19 Javascript
[01:14:41]DOTA2-DPC中国联赛定级赛 iG vs Magma BO3第一场 1月8日
2021/03/11 DOTA
详解使用python crontab设置linux定时任务
2016/12/08 Python
Python实现MySQL操作的方法小结【安装,连接,增删改查等】
2017/07/12 Python
Python lambda函数基本用法实例分析
2018/03/16 Python
python实现向微信用户发送每日一句 python实现微信聊天机器人
2019/03/27 Python
多个python文件调用logging模块报错误
2020/02/12 Python
利用HTML5 Canvas制作键盘及鼠标动画的实例分享
2016/03/15 HTML / CSS
英国在线潜水商店:Simply Scuba
2019/03/25 全球购物
Europcar比利时:租车
2019/08/26 全球购物
几道PHP的面试题
2012/05/19 面试题
班级聚会策划书
2014/01/16 职场文书
自我反省检讨书
2014/01/23 职场文书
公司收款委托书范本
2014/09/20 职场文书
乡镇领导班子四风对照检查材料
2014/09/27 职场文书
写给纪委的违纪检讨书
2015/05/05 职场文书
就业意向书范本
2015/05/11 职场文书
2015秋季小学开学寄语
2015/05/27 职场文书
吃通javascript正则表达式
2021/04/21 Javascript
聊聊CSS粘性定位sticky案例解析
2022/06/01 HTML / CSS