├── README.md
├── contacts.css
├── contacts.js
└── index.html
/README.md:
--------------------------------------------------------------------------------
1 | # contacts 联系人/通讯录 插件
2 |
3 | javascript 实现微信通讯录,联系人按拼音首字母从A到Z分组排序/搜索过滤联系人
4 |
5 | 
6 |
7 | ## 状态
8 |
9 | 开发中,进度看下面
10 |
11 | ## 进度
12 | - [x] 正确分类数据
13 | - [x] 点击索引跳转
14 | - [x] 顶栏显示
15 | - [x] 滚动流畅不卡
16 | - [x] 搜索联系人
17 | - [x] 做成原生插件
18 | - [x] 仿照微信联系人UI
19 | - [x] 对输入数据排序
20 |
21 | ## 用法
22 |
23 | 参照 index.html
24 |
25 | **license**
26 |
27 | MIT license
28 |
--------------------------------------------------------------------------------
/contacts.css:
--------------------------------------------------------------------------------
1 | .hidden {
2 | display: none!important;
3 | }
4 |
5 | .wx-contacts-container {
6 | font-family: 'Microsoft YaHei';
7 | }
8 |
9 | .wx-contacts-shortcuts-ctn {
10 | position: fixed;
11 | top: 50%;
12 | transform: translateY(-50%);
13 | right: 0;
14 | display: flex;
15 | flex-direction: column;
16 | text-align: center;
17 | font-family: 'Microsoft YaHei';
18 | }
19 | .wx-contacts-shortcuts-ctn > a {
20 | width: 30px;
21 | height: 20px;
22 | text-align: center;
23 | text-decoration: none;
24 | color: #999;
25 | }
26 | .wx-contacts-shortcuts-ctn > a[href="#wx-contacts-hook-search"] {
27 | border: 2px solid #999;
28 | width: 7px;
29 | height: 7px;
30 | border-radius: 50%;
31 | transform: translate(80%,-50%);
32 | }
33 |
34 | .wx-contacts-shortcuts-ctn > a[href="#wx-contacts-hook-search"]::after {
35 | content: '';
36 | display: block;
37 | width: 5px;
38 | height: 0px;
39 | border: 1px solid #999;
40 | transform: rotate(45deg) translate(140%,110%);
41 | }
42 |
43 | .wx-contacts-list {
44 | position: relative;
45 | padding: 0px;
46 | list-style-type: none;
47 | }
48 | .wx-contacts-list li {
49 | display: flex;
50 | align-items: center;
51 | height: 40px;
52 | padding-left: 10px;
53 | border-top: 1px solid #EFEFF4;
54 | }
55 | .wx-contacts-list li.wx-contacts-hooks {
56 | display: block;
57 | height: 20px;
58 | line-height: 20px;
59 | background-color: #EFEFF4;
60 | border-top: none;
61 | }
62 | .wx-contacts-list li.wx-contacts-hooks + li {
63 | border-top: none;
64 | }
65 |
66 | .wx-contacts-list li.wx-contacts-on-top {
67 | position: fixed;
68 | top: 0;
69 | left: 0;
70 | width: 100%;
71 | }
72 | .wx-contacts-list li.wx-contacts-on-top + li {
73 | padding-top: 20px;
74 | }
75 |
76 | input.wx-contacts-search {
77 | width: 100%;
78 | height: 40px;
79 | line-height: 40px;
80 | border: none;
81 | border-bottom: 1px solid #EFEFF4;
82 | font-size: 16px;
83 | outline: none;
84 | }
85 | .wx-contacts-search-result .wx-contacts-list li {
86 | border-top: none;
87 | border-bottom: 1px solid #EFEFF4;
88 | }
89 |
90 | .wx-contacts-badge {
91 | display: inline-block;
92 | width: 30px;
93 | height: 30px;
94 | margin-right: 10px;
95 | }
96 |
97 | .wx-contacts-search-bar {
98 | position: relative;
99 | display: flex;
100 | box-sizing: border-box;
101 | background-color: #EFEFF4;
102 | }
103 |
104 | .wx-contacts-search-bar:before {
105 | content: " ";
106 | position: absolute;
107 | left: 0;
108 | top: 0;
109 | right: 0;
110 | height: 1px;
111 | border-top: 1px solid #D7D6DC;
112 | color: #D7D6DC;
113 | transform: scaleY(0.5);
114 | }
115 |
116 |
117 | .wx-contacts-search-bar:after {
118 | content: " ";
119 | position: absolute;
120 | left: 0;
121 | bottom: 0;
122 | right: 0;
123 | height: 1px;
124 | border-bottom: 1px solid #D7D6DC;
125 | color: #D7D6DC;
126 | transform: scaleY(0.5);
127 | }
128 |
129 | .wx-contacts-search-inner {
130 | position: relative;
131 | box-sizing: border-box;
132 | margin: 7px 10px;
133 | padding-left: 30px;
134 | padding-right: 30px;
135 | height: 26px;
136 | width: 100%;
137 | border-radius: 3px;
138 | background: #FFFFFF;
139 | z-index: 1;
140 | }
141 |
142 | .wx-contacts-search-inner .wx-contacts-search-input {
143 | box-sizing: border-box;
144 | width: 100%;
145 | height: 26px;
146 | line-height: 26px;
147 | padding: 4px 0;
148 | border: 0;
149 | font-size: 14px;
150 | background: transparent;
151 | outline: none;
152 | }
153 |
154 | /* webkit 内核浏览器 type=search 的 input 自带 取消 按钮*/
155 | .wx-contacts-search-input::-webkit-search-cancel-button {
156 | display: none;
157 | }
158 |
159 | .wx-contacts-icon-clear {
160 | position: absolute;
161 | right: 5px;
162 | top: 50%;
163 | transform: translateY(-50%);
164 | display: inline-block;
165 | width: 12px;
166 | height: 12px;
167 | border: 1px solid #999;
168 | border-radius: 50%;
169 | background: #999;
170 | z-index: 1;
171 | }
172 |
173 | .wx-contacts-icon-clear::before {
174 | content: "";
175 | width: 1px;
176 | height: 80%;
177 | background-color: white;
178 | position: absolute;
179 | transform: translate(-50%, -50%) rotate(45deg);
180 | left: 50%;
181 | top: 50%;
182 | }
183 |
184 | .wx-contacts-icon-clear::after {
185 | content: "";
186 | width: 1px;
187 | height: 80%;
188 | background-color: white;
189 | position: absolute;
190 | transform: translate(-50%, -50%) rotate(-45deg);
191 | left: 50%;
192 | top: 50%;
193 | }
194 |
195 | .wx-contacts-search-text {
196 | position: absolute;
197 | top: 1px;
198 | right: 1px;
199 | bottom: 1px;
200 | left: 1px;
201 | margin: 8px 10px;
202 | line-height: 22px;
203 | z-index: 2;
204 | border-radius: 3px;
205 | text-align: center;
206 | color: #9B9B9B;
207 | background: #FFFFFF;
208 | }
209 |
210 | .wx-contacts-search-focusing .wx-contacts-search-text {
211 | display: none;
212 | }
--------------------------------------------------------------------------------
/contacts.js:
--------------------------------------------------------------------------------
1 | (function(global, undefined){
2 | function Contacts(opts){
3 | if(!(this instanceof Contacts)){
4 | return new Contacts(opts);
5 | }
6 | this.merge(this.opts, opts);
7 | this.init();
8 | }
9 |
10 | Contacts.prototype = {
11 | opts: {
12 | appendTo: '',
13 | generateListItem: null,
14 | data: []
15 | },
16 | merge: function(defaultOpts, userOpts){
17 | if(userOpts){
18 | Object.assign(defaultOpts, userOpts);
19 | }
20 | },
21 | init: function(){
22 | this.parseData();
23 | this.generateShortcuts();
24 |
25 | var list = this.generateList();
26 | var ctn = this.generateCtn();
27 | ctn.querySelector('.wx-contacts-all-result').appendChild(list);
28 |
29 | var appendTo = this.opts.appendTo || 'body';
30 | document.querySelector(appendTo).appendChild(ctn);
31 |
32 | this.getAllAnchorPositions();
33 | this.addListener();
34 | },
35 | parseData: function(){
36 | var self = this;
37 | var data = this.opts.data;
38 | var map = {};
39 | var c = 'A'.charCodeAt();
40 | for(; c <= 'Z'.charCodeAt(); c++ ){
41 | map[String.fromCharCode(c)] = [];
42 | }
43 | map['#'] = [];
44 | var firstCharUpper;
45 | data.forEach(function(item){
46 | firstCharUpper = self.getFirstUpperChar(item.name);
47 | if (map.hasOwnProperty(firstCharUpper)) {
48 | map[firstCharUpper].push(item);
49 | } else {
50 | map['#'].push(item);
51 | }
52 | });
53 |
54 | //排序
55 | for(var key in map) {
56 | if( map.hasOwnProperty( key ) && (map[key].length != 0)) {
57 | map[key].sort(function(a, b){
58 | return a.name.localeCompare(b.name, 'zh-CN-u-co-pinyin');
59 | });
60 | }
61 | }
62 |
63 | this.dictMap = map;
64 | return map;
65 | },
66 | generateShortcuts: function(){
67 | var items = [];
68 | var map = this.dictMap;
69 | for(var key in map) {
70 | if( map.hasOwnProperty( key ) && (map[key].length != 0)) {
71 | items.push(key);
72 | }
73 | }
74 | var ctn = document.createElement('div');
75 | ctn.classList.add('wx-contacts-shortcuts-ctn');
76 | var test,a;
77 | items.forEach(function(item){
78 | text = document.createTextNode(item);
79 | a = document.createElement('a');
80 | a.setAttribute('href', '#wx-contacts-hook-' + item);
81 | a.setAttribute('rel', 'internal');
82 | a.appendChild(text);
83 | ctn.appendChild(a);
84 | });
85 | a = document.createElement('a');
86 | a.setAttribute('href', '#wx-contacts-hook-search');
87 | a.setAttribute('rel', 'internal');
88 | ctn.insertBefore(a, ctn.firstChild);
89 | document.body.appendChild(ctn);
90 | },
91 | generateList: function(){
92 | var self = this;
93 | var map = self.dictMap;
94 | var formerKey = null;
95 | var list = document.createElement('ul');
96 | list.classList.add('wx-contacts-list');
97 | for(var key in map) {
98 | if( map.hasOwnProperty( key ) && (map[key].length != 0)) {
99 | var items = map[key];
100 | items.forEach(function(item){
101 | var text,li,a;
102 | if(key != formerKey){
103 | a = document.createElement('a');
104 | a.setAttribute('id', 'wx-contacts-hook-' + key);
105 | text = document.createTextNode(key);
106 | li = document.createElement('li');
107 | li.classList.add('wx-contacts-hooks');
108 | li.appendChild(a);
109 | li.appendChild(text);
110 | list.appendChild(li);
111 | formerKey = key;
112 | }
113 | list.appendChild(self.generateListItem(item));
114 | });
115 | }
116 | }
117 | return list;
118 | },
119 | generateListItem: function(item){
120 | if(this.opts.generateListItem && typeof this.opts.generateListItem == 'function'){
121 | return this.opts.generateListItem(item);
122 | }
123 | var tpl = ''+ item.name + '';
124 | var li = document.createElement('li');
125 | li.innerHTML = tpl;
126 | return li;
127 | },
128 | generateCtn: function(){
129 | var ctn = document.createElement('div');
130 | ctn.classList.add('wx-contacts-container');
131 | ctn.appendChild(this.generateInputCtn());
132 | var searchResult = document.createElement('div');
133 | searchResult.classList.add('wx-contacts-search-result');
134 | ctn.appendChild(searchResult);
135 | var allResult = document.createElement('div');
136 | allResult.classList.add('wx-contacts-all-result');
137 | ctn.appendChild(allResult);
138 | return ctn;
139 | },
140 | generateInputCtn: function(){
141 | var div = document.createElement('div');
142 | div.classList.add('wx-contacts-search-bar');
143 | div.setAttribute('id','wx-contacts-hook-search');
144 | var innerHtml =
145 | '