- 用javascript实现一个与微信通讯录效果相似的插件
- 易用
- 尽量遵守 weui 设计规范
- 最好是原生的,不依赖 jQuery
- 页面滚动要流畅
- 正确分类数据
- 点击索引跳转
- 顶栏显示
- 滚动流畅不卡
- 搜索联系人
- 对输入数据排序
MIT license
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width"> | |
<meta name=”viewport” content=”initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0″> | |
<title>Contacts</title> | |
<style> | |
.page { | |
height: 10000px; | |
} | |
.shortcuts_ctn { | |
position: fixed; | |
top: 50%; | |
transform: translateY(-50%); | |
right: 0; | |
width: 20px; | |
display: flex; | |
flex-direction: column; | |
text-align: center; | |
} | |
.list { | |
position: relative; | |
padding: 0px; | |
list-style-type: none; | |
} | |
.list li { | |
height: 40px; | |
line-height: 40px; | |
padding-left: 10px; | |
border-top: 1px solid #eee; | |
} | |
.list li[id*=hook] { | |
height: 20px; | |
line-height: 20px; | |
background-color: #eee; | |
border-top: none; | |
} | |
.list li[id*=hook] + li { | |
border-top: none; | |
} | |
.list li.on_top { | |
position: fixed; | |
top: 0; | |
width: 100%; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="page"> | |
</div> | |
<script> | |
var raw_data = ['banana','cat','egg','farmer','food','jeep','joke','lisa','low','jude','apple',1,'2','谭小生', '张中','李大哥']; | |
var dict_map = parseData(raw_data); | |
generate_shortcuts(dict_map); | |
var list = generate_list(dict_map); | |
var ctn = generate_ctn_input(); | |
ctn.appendChild(list); | |
document.querySelector('.page').appendChild(ctn); | |
function generate_shortcuts(map){ | |
var items = []; | |
for(var key in map) { | |
if( map.hasOwnProperty( key ) && (map[key].length != 0)) { | |
items.push(key); | |
} | |
} | |
var ctn = document.createElement('div'); | |
ctn.classList.add('shortcuts_ctn'); | |
items.forEach(function(item){ | |
var text = document.createTextNode(item); | |
var a = document.createElement('a'); | |
a.setAttribute('href', '#hook_' + item); | |
a.setAttribute('rel', 'internal'); | |
a.appendChild(text); | |
ctn.appendChild(a); | |
}); | |
document.body.appendChild(ctn); | |
} | |
function generate_list(map){ | |
var former_key = null; | |
var list = document.createElement('ul'); | |
list.classList.add('list'); | |
for(var key in map) { | |
if( map.hasOwnProperty( key ) && (map[key].length != 0)) { | |
var items = map[key]; | |
items.forEach(function(item){ | |
var text,li; | |
if(key != former_key){ | |
text = document.createTextNode(key); | |
li = document.createElement('li'); | |
li.classList.add('hooks'); | |
li.setAttribute('id', 'hook_' + key); | |
li.appendChild(text); | |
list.appendChild(li); | |
former_key = key; | |
} | |
text = document.createTextNode(item); | |
li = document.createElement('li'); | |
li.appendChild(text); | |
list.appendChild(li); | |
}); | |
} | |
} | |
return list; | |
} | |
function generate_ctn_input(){ | |
var ctn = document.createElement('div'); | |
ctn.classList.add('container'); | |
var input = document.createElement('input'); | |
input.classList.add('search'); | |
ctn.appendChild(input); | |
return ctn; | |
} | |
function parseData(data){ | |
var map = {}; | |
var c = 'A'.charCodeAt(); | |
for(; c <= 'Z'.charCodeAt(); c++ ){ | |
map[String.fromCharCode(c)] = []; | |
} | |
map['#'] = []; | |
var first_char_upper; | |
data.forEach(function(item){ | |
first_char_upper = getFirstUpperChar(item); | |
if (map.hasOwnProperty(first_char_upper)) { | |
map[first_char_upper].push(item); | |
} else { | |
map['#'].push(item); | |
} | |
}); | |
return map; | |
} | |
function getFirstUpperChar(str){ | |
string = String(str); | |
var c = string[0]; | |
if (/[^\u4e00-\u9fa5]/.test(c)) { | |
return c.toUpperCase(); | |
} | |
else { | |
return chineseToEnglish(c); | |
} | |
} | |
// copy from https://ruby-china.org/topics/29026 | |
function chineseToEnglish(c){ | |
var idx = -1; | |
var MAP = 'ABCDEFGHJKLMNOPQRSTWXYZ'; | |
var boundaryChar = '驁簿錯鵽樲鰒餜靃攟鬠纙鞪黁漚曝裠鶸蜶籜鶩鑂韻糳'; | |
if (!String.prototype.localeCompare) { | |
throw Error('String.prototype.localeCompare not supported.'); | |
} | |
if (/[^\u4e00-\u9fa5]/.test(c)) { | |
return c; | |
} | |
for (var i = 0; i < boundaryChar.length; i++) { | |
if (boundaryChar[i].localeCompare(c, 'zh-CN-u-co-pinyin') >= 0) { | |
idx = i; | |
break; | |
} | |
} | |
return MAP[idx]; | |
} | |
// get positions | |
var positions = []; | |
function getAllAnchorPositions(){ | |
var anchors = document.querySelectorAll('.hooks'); | |
anchors = [].slice.call(anchors); | |
anchors.forEach(function(anchor){ | |
positions.push({ | |
anchor: anchor, | |
pos: anchor.offsetTop | |
}); | |
}); | |
} | |
getAllAnchorPositions(); | |
// get positions | |
function getTopbarElement(scroll_position){ | |
var i = 0; | |
while((i < positions.length - 1) && scroll_position > positions[i].pos){ | |
i ++; | |
} | |
var index = Math.min(Math.max(0, i-1), positions.length - 1 ); | |
return positions[index].anchor; | |
} | |
// scroll optimization | |
// see https://developer.mozilla.org/en-US/docs/Web/Events/scroll | |
var last_known_scroll_position = 0; | |
var ticking = false; | |
var top_bar_element = null; | |
window.addEventListener('scroll', function(e){ | |
last_known_scroll_position = window.scrollY; | |
if(!ticking) { | |
window.requestAnimationFrame(function(){ | |
//do some thing here | |
positions.forEach(function(item){ | |
item.anchor.classList.remove('on_top'); | |
}); | |
top_bar_element = getTopbarElement(last_known_scroll_position); | |
top_bar_element.classList.add('on_top'); | |
ticking = false; | |
}); | |
} | |
ticking = true; | |
}); | |
</script> | |
</body> | |
</html> |