仿iPhone通訊錄制作小程序自定義選擇組件的實(shí)現(xiàn)
前言
近期閑來(lái)無(wú)事,想著閑著也是閑著,不如給自己搞點(diǎn)事情做!敢想敢做,于是選擇了給微信小程序做個(gè) 仿iPhone通訊錄 效果的自定義組件。
先來(lái)整理一下,瞧瞧需要實(shí)現(xiàn)的核心功能。
- 按照第一個(gè)字的首字母排序;
- 實(shí)現(xiàn)輸入搜索功能;
- 側(cè)邊欄字母導(dǎo)航;
基本上分為3塊:
- 頂部的搜索區(qū)域;
- 內(nèi)容的展示區(qū)域;
- 側(cè)邊字母導(dǎo)航欄區(qū)域;
// index.wxml <view class="main"> <!-- 頂部搜索區(qū)域 --> <view class="header"> </view> <!-- 內(nèi)容區(qū)域 --> <scroll-view class="scroll"> </scroll-view> <!-- 側(cè)邊導(dǎo)航 --> <view class="sub_nav"> </view> </view>
【頂部的搜索區(qū)域】
一目了然就直接貼代碼了。
<view class="header"> // 這里或許有人要問(wèn),為啥不用小程序的label組件呢。?_? // 原因就是...我就不用,你還能咬我?!^(oo)^ // 哈哈哈哈~開(kāi)個(gè)玩笑,其實(shí)是小程序的label組件還沒(méi)支持input! <view class="label"> <icon></icon> <input type="text" placeholder="搜索" /> </view> </view>
【內(nèi)容的展示區(qū)域】
再說(shuō)一目了然會(huì)不會(huì)被打呢?:joy:
根據(jù)圖片就可以看出來(lái),存在2個(gè)區(qū)域。
- 紅框包圍的外框,負(fù)責(zé)圈定展示的范圍;
- 綠框包圍的范圍,包含有字母標(biāo)題和對(duì)應(yīng)的子項(xiàng)。
代碼如下:
<scroll-view class="scroll"> <view class="dl"> <view class="dt">這里是字母標(biāo)題。</view> <view class="dd"> <span>這里當(dāng)然是展示的內(nèi)容啦。</span> </view> </view> </scroll-view>
【側(cè)邊字母導(dǎo)航欄區(qū)域】
為了節(jié)省一下文章的篇幅,這里就不貼圖了,很簡(jiǎn)單,就是并排下來(lái)就好了。
<view class="sub_nav"> <view class="option">這里是輸出字母。</view> </view>
接下來(lái)是wxss的樣式了。
考慮到wxss的樣式較多,我就直接貼 代碼鏈接 吧,有興趣的童鞋可以瞧瞧。
完成之后,是時(shí)候貼個(gè)效果圖了。(不許吐槽丑,寶寶會(huì)不開(kāi)心的!:pensive:)
結(jié)構(gòu)樣式弄完了,也貼一下自定組件的基礎(chǔ)文件
// index.json { "component": true }
// index.js Component({ properties: {}, // 組件的對(duì)外屬性 data: {}, // 組件的內(nèi)部數(shù)據(jù) lifetimes: {}, // 生命周期 methods: {} // 事件 });
現(xiàn)在開(kāi)始實(shí)現(xiàn)功能了?。?!
按照第一個(gè)字的首字母排序
說(shuō)實(shí)話(huà),實(shí)現(xiàn)這塊功能呢,我是沒(méi)啥頭緒的,所以這個(gè)時(shí)候就要求助偉大的“度娘/Google”了。
經(jīng)過(guò)樓主“遍尋網(wǎng)絡(luò)”,查找到如下頁(yè)面的源碼參考:
因樓主問(wèn)題,遺忘了該網(wǎng)址,如有知道的童鞋,貼個(gè)鏈接告訴下樓主,樓主立馬麻溜的加上。 源碼的原理大概描述下:
收錄 20902 個(gè)漢字和 375 個(gè)多音字的 Unicode 編碼,然后用JS切割首字母并轉(zhuǎn)換成 Unicode 進(jìn)行對(duì)比,最后返回對(duì)應(yīng)首字母的拼音。
// 漢字對(duì)應(yīng)的Unicode編碼文件 // oMultiDiff = 多音字 | firstLetterMap = 漢字 import firstStore from './firstChineseLetter'; // 獲取首字母拼音 function getFirstLetter (val) { const firstVal = val.charAt(0); if (/.*[\u4e00-\u9fa5]+.*/.test(firstVal)) { // 處理中文字符 // 轉(zhuǎn)換成Unicode編碼,與firstStore里面的數(shù)據(jù)進(jìn)行對(duì)比,然后返回對(duì)應(yīng)的參數(shù) const code = firstVal.charCodeAt(0); // 轉(zhuǎn)換成Unicode編碼 return code in firstStore.oMultiDiff ? firstStore.oMultiDiff[code] : firstStore.firstLetterMap.charAt(code - 19968); } else { // 這里處理非中文 // 檢測(cè)是否字母,如果是就直接返回大寫(xiě)的字母 // 不是的話(huà),返回“#” return /^[a-zA-Z]+$/.test(firstVal) ? firstVal.toUpperCase() : '#'; } } getFirstLetter('東城區(qū)'); // 輸出結(jié)果:D
獲取首字母的方法有了之后,就該對(duì)數(shù)據(jù)進(jìn)行處理了。
首先定義一下組件所需要的參數(shù)。
Component({ // 組件的對(duì)外屬性 properties: { data: { type: Array, value: [], }, // 組件外傳遞進(jìn)來(lái)的數(shù)據(jù) attr: { type: String, value: 'label' }, // 需要進(jìn)行首字母處理的屬性,默認(rèn)是"label" }, ... })
然后,針對(duì)組件外傳遞進(jìn)來(lái)的數(shù)據(jù),做一次轉(zhuǎn)換。
// 靜態(tài)數(shù)據(jù)的存儲(chǔ) const Static = { list: [] } Component({ ... methods: { // 初始/重置數(shù)據(jù) init () { const { data, attr } = this.properties; let changeData = [], // 轉(zhuǎn)換后的數(shù)據(jù) inChangeData = {}; // 存儲(chǔ)轉(zhuǎn)換后的數(shù)據(jù)對(duì)應(yīng)字母的索引值 data.map(v => { // 獲取首字母拼音 let firstLetter = this.getFirstLetter(v[attr]); // 循環(huán)對(duì)比檢測(cè) firstLetter.split('').map(str => { if (str in inChangeData) { // 有首字母相同的項(xiàng), // 則添加入已有的項(xiàng)里面 changeData[inChangeData[str]].list.push(v); } else { // 沒(méi)有首字母相同的項(xiàng), // 則在尾部追加一條新的數(shù)據(jù), // 儲(chǔ)存對(duì)應(yīng)的字母值(firstLetter), // 同時(shí)存儲(chǔ)該字母對(duì)應(yīng)的索引 changeData.push({ firstLetter: str, list: [v] }); inChangeData[str] = changeData.length - 1; } }); }); // 此時(shí)轉(zhuǎn)換后的數(shù)組屬于亂序, // 需要對(duì)亂序的數(shù)組進(jìn)行排序 changeData.sort((pre, next) => pre.firstLetter < next.firstLetter ? -1 : 1); // 若存在“#”項(xiàng),將位置位移至底部 if (changeData[0].firstLetter === '#') { const firstArr = changeData.splice(0, 1); changeData = [...changeData, ...firstArr]; } // 存儲(chǔ)轉(zhuǎn)換后的數(shù)據(jù), // this.data.list的數(shù)據(jù)對(duì)應(yīng)頁(yè)面的展示數(shù)據(jù),因?yàn)橛兴阉鞴δ?,?shù)據(jù)可能會(huì)變更, // 在靜態(tài)的數(shù)據(jù)里面,也存儲(chǔ)1份數(shù)據(jù),方便后續(xù)的搜索等功能。 this.setData({ list: changeData }); Static.list = changeData; }, } ... });
初始化函數(shù)有了之后呢,當(dāng)然是調(diào)用它啦。
Component({ lifetimes: { // 在組件實(shí)例進(jìn)入頁(yè)面節(jié)點(diǎn)樹(shù)時(shí)執(zhí)行初始化數(shù)據(jù) attached () { this.init(); } }, observers: { // 考慮到組件傳遞的數(shù)據(jù)存在變更的可能, // 在數(shù)據(jù)變更的時(shí)候,也要做一次初始化 'data, attr, icon' (data, attr) { this.init(); } }, })
接下來(lái)是搜索功能啦~
先給頁(yè)面搜索框加個(gè)監(jiān)聽(tīng)事件(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; // 用戶(hù)輸入的值 const { list } = Static; // init存儲(chǔ)的靜態(tài)數(shù)據(jù),用來(lái)做數(shù)據(jù)對(duì)比 const { attr } = this.properties; // 要對(duì)比的屬性值 let result = [], tem = {}; // 沒(méi)有搜索內(nèi)容,返回全部?jī)?nèi)容 if (value.length === 0) { this.setData({ list: Static.list }); return; } // 檢索搜索內(nèi)容 list.map(v => { // 獲取所有跟value匹配上的數(shù)據(jù) const searchList = v.list.filter(v => v[attr].indexOf(value) !== -1); if (searchList.length > 0) { // 此處原理類(lèi)似樓上init的對(duì)比,此處不細(xì)說(shuō), // 反正我懶我有理(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; } } }); // 存儲(chǔ)數(shù)據(jù) this.setData({ list: result, search: value }); } }, ... });
側(cè)邊欄字母導(dǎo)航
(突然覺(jué)得,寫(xiě)文好累啊?。。。?/p>
寫(xiě)這塊的時(shí)候呢,樓主發(fā)現(xiàn)了iPhone通訊錄側(cè)邊導(dǎo)航欄有個(gè)問(wèn)題, 手指在字母導(dǎo)航欄上滑動(dòng)的時(shí)候,有時(shí)候很難確認(rèn)自己滑到了哪個(gè)區(qū)域?!
然鵝這個(gè)問(wèn)題呢,樓主發(fā)現(xiàn)了微信的通訊錄,針對(duì)這塊添加了手指滑動(dòng)的時(shí)候,添加了個(gè)結(jié)構(gòu)來(lái)幫助用戶(hù)確認(rèn)目前所處的區(qū)域。
樓主本著學(xué)習(xí)的精神,借(chao)鑒(xi)了這個(gè)效果,來(lái)個(gè)效果圖。
貼一下新的wxml結(jié)構(gòu)
<!-- 側(cè)邊導(dǎo)航 --> <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 }} <!-- 以下這塊就是新增的結(jié)構(gòu)啦 S --> <view class="max {{ item.firstLetter === scrollIntoView && subNavHint ? 'show' : '' }}" data-desc="{{ item.firstLetter }}" ></view> <!-- 以上這塊就是新增的結(jié)構(gòu)啦 E --> </view> </view>
const Static = { list: [], timer: null } Component({ ... data: { scrollIntoView: '', // 標(biāo)記當(dāng)前處于哪個(gè)字母 subNavHint: false, // 控制借(chao)鑒(xi)微信效果的元素 }, methods: { subTouchStart () { this.setData({ subNavHint: true, scrollIntoView: '' }); }, subTouchEnd () { this.setData({ subNavHint: false }); }, subTouchMove (e) { // 獲取字母導(dǎo)航欄元素對(duì)應(yīng)的值 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; // 導(dǎo)航元素距離頂部的位置 const { list } = this.data; // 計(jì)算索引, // 或許看到這里有人會(huì)疑問(wèn),為什么是除以20? // 因?yàn)闃邮嚼锩妫覍?xiě)的高度是20px,所以每個(gè)字母的區(qū)域是20px。 let index = Math.round((clientY - DomTop) / 20); index = index >= list.length ? list.length - 1 : index; // 限制索引大于0 index = index < 0 ? 0 : index; // 限制索引小于0 // 限制結(jié)果重復(fù)賦值 if (list[index].firstLetter !== this.data.scrollIntoView) { this.setData({ scrollIntoView: list[index].firstLetter }); // 加個(gè)抖動(dòng)效果 wx.vibrateShort(); } }); } }, } ... });
結(jié)語(yǔ)
文章寫(xiě)到這呢,基本上核心的功能都已經(jīng)實(shí)現(xiàn)啦~ :stuck_out_tongue_closed_eyes:(終于寫(xiě)完了...)
通過(guò)自己封裝組件,樓主還是有挺大收獲的!
當(dāng)然,這個(gè)組件還有很多可以繼續(xù)完善的地方,有興趣的童鞋呢,可以提出你的優(yōu)化建議,樓主有時(shí)(xing)間(qu)的話(huà),會(huì)繼續(xù)完善下去。
最后,還是推一下這個(gè)組件啦,希望它能幫到有需要的童鞋。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
javascript手機(jī)驗(yàn)證、郵箱驗(yàn)證、密碼驗(yàn)證的正則表達(dá)式簡(jiǎn)單封裝實(shí)例
正則表達(dá)式在日常的數(shù)據(jù)驗(yàn)證中是必不可少的驗(yàn)證方式,這篇文章主要給大家介紹了關(guān)于javascript手機(jī)驗(yàn)證、郵箱驗(yàn)證、密碼驗(yàn)證的正則表達(dá)式簡(jiǎn)單封裝的相關(guān)資料,需要的朋友可以參考下2022-09-09js解決event.keyCode在Firefox中失效的問(wèn)題
這篇文章主要介紹了js解決event.keyCode在Firefox中失效的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12微信小程序?qū)崿F(xiàn)下載進(jìn)度條的方法
本篇文章主要介紹了微信小程序?qū)崿F(xiàn)下載進(jìn)度條的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12JavaScript跳出循環(huán)的三種方法(break, return, continue)
這篇文章主要介紹了JavaScript跳出循環(huán)的三種方法(break, return, continue),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07詳釋JavaScript執(zhí)行環(huán)境與執(zhí)行棧
一句話(huà)就可以概括:代碼 ( 包括函數(shù) ) 執(zhí)行時(shí)所需要的所有信息就是執(zhí)行環(huán)境。由于 ES 歷經(jīng)多個(gè)版本,所以執(zhí)行環(huán)境的標(biāo)準(zhǔn)也一直在變。文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04詳解js的事件處理函數(shù)和動(dòng)態(tài)創(chuàng)建html標(biāo)記方法
本文主要對(duì)javascript的事件處理函數(shù),動(dòng)態(tài)創(chuàng)建html標(biāo)記的兩種方法進(jìn)行詳細(xì)介紹,具有很好的參考價(jià)值,需要的朋友一起來(lái)看下吧2016-12-12Taro?小程序持續(xù)集成實(shí)現(xiàn)及原理
這篇文章主要為大家介紹了Taro?小程序持續(xù)集成實(shí)現(xiàn)及原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Peer.js 構(gòu)建視頻聊天應(yīng)用使用詳解
這篇文章主要為大家介紹了Peer.js 構(gòu)建視頻聊天應(yīng)用使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Javascript 完美運(yùn)動(dòng)框架(逐行分析代碼,讓你輕松了運(yùn)動(dòng)的原理)
這篇文章主要介紹了Javascript 完美運(yùn)動(dòng)框架,逐行分析代碼,讓你輕松了運(yùn)動(dòng)的原理,需要的朋友可以參考下2015-01-01