(function(global){
var PICKERCOUNT = 0;
var body = document.getElementsByTagName("body")[0];
var coordinate = {start: {y:0},end: {y:0,status:true}, move: {y:0}};
var Util = {
removeClass: function(el, className) {
var reg = new RegExp('(\\s|^)' + className + '(\\s|$)');
el.className = el.className.replace(reg, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, '');
},
addClass: function(el, className) {
!Util.hasClass(el,className) && (el.className += (el.className ? ' ' : '') + className);
},
hasClass: function(el, className) {
return !!el.className && new RegExp('(^|\\s)' + className + '(\\s|$)').test(el.className);
},
loop: function(start,end,handle){
for(var i = start; i < end; i++){
Util.isFunc(handle) && handle(i);
}
},
isFunc: function(name){
return typeof name === 'function';
},
isArray: function(o) {
return Object.prototype.toString.call(o) === '[object Array]';
},
isObject: function(o) {
return typeof o === 'object';
},
damping: function (value) {//阻尼运算
var steps = [20, 40, 60, 80, 100],rates = [0.5, 0.4, 0.3, 0.2, 0.1];
var result = value,len = steps.length;
while (len--) {
if (value > steps[len]) {
result = (value - steps[len]) * rates[len];
for (var i = len; i > 0; i--) {
result += (steps[i] - steps[i - 1]) * rates[i - 1];
}
result += steps[0] * 1;
break;
}
}
return result;
},
createEle: function(parent, tag, className, html) {
var ele = document.createElement(tag);
className && Util.addClass(ele,className);
html && (ele.innerHTML = html);
parent && parent.appendChild(ele);
return ele;
},
getEle: function(ctx, selector) {
return ctx.querySelector(selector);
},
setTransform: function(el,y) {
el.style.transform = 'translate3d(0,'+ y +'px,0)';
}
}
function Picker(config){
this.index = ++PICKERCOUNT;//当前选择器的索引
this.target = config.target instanceof HTMLElement ? config.target : typeof config.target === "string" ? Util.getEle(document,config.target) : null;//触发选择器的dom元素
this.data = config.data || [];//需要显示的数据
this.value = config.value ? (Util.isArray(config.value) ? config.value : config.value.split(',')) : [];//选择器默认值
this.childKey = config.childKey || 'child';//子数据索引名
this.valueKey = config.valueKey || 'value';//用于索引初始值的key
this.textKey = config.textKey || 'value';//用于显示的key
this.autoFill = !(config.autoFill === false);//选择确定后是否填充到目标元素
this.confirm = config.confirm;//确定选择的回调
this.cancel = config.cancel;//取消回调
this.initCallback = config.initCallback;//实例化完成的回调
this.select = config.select;//单个列表选择后的回调
this.lock = config.lock === true;//锁定确定按钮,用于异步加载时等待使用
this.className = config.className || '';//定制的类名
this.init();
}
Picker.prototype = {
constructor: Picker,
init: function(){
this.initResult();
var html = '<div class="mp-container"><div class="mp-header"><span class="mp-cancel">取消</span><span class="mp-confirm'+(this.lock ? ' disabled' : '')+'">确定</span></div><div class="mp-content"><div class="mp-shadowup"></div><div class="mp-shadowdown"></div><div class="mp-line"></div><div class="mp-box"></div></div>';
var container = Util.createEle(body,'div','mp-mask',html);
this.className && Util.addClass(container,this.className);
container.id = 'mobilePicker'+this.index;
this.container = container;
this.box = Util.getEle(container, '.mp-box')//用于包含滚动元素的容器
this.createScroll(this.data);//核心方法:创建滚动的元素
this.value = [];
this.bindEvent();//绑定事件
this.finisInit();
},
initResult: function(config){
this.scrollCount = 0;//已渲染的数据层级数
this.selectIndex = [];//每个层级选中的索引集合
this.result = [];//选择器最终的结果
this.offset = [];//每个层级滚动的偏移量集合
},
finisInit: function(){
var value = this.fillResult();
Util.isFunc(this.initCallback) && this.initCallback(value,this.result);
},
update: function(options){
for(var i in options) {
this[i] = options[i];
}
this.initResult()
this.box.innerHTML = '';
this.createScroll(this.data);
this.value = [];
},
getData: function(indexes){//获取数据集合
var arr = [];
for(var i = 0; i < indexes.length; i++){
arr = i == 0 ? this.data : arr[indexes[i -1]][this.childKey];
}
return arr;
},
setResult: function(data){
if(!Util.isObject(data)) return data;
var temp = {};
for(var key in data){
key != this.childKey && (temp[key] = data[key]);
}
return temp;
},
createScroll: function(data){
var scroll = Util.createEle(this.box,'div','mp-list-wrap','<ul></ul>');
scroll.scrollIndex = this.scrollCount++;
this.addList(Util.getEle(scroll, 'ul'), data);
},
getText: function(data){
return Util.isObject(data) ? data[this.textKey] : data;
},
addList: function(parent, data){
var html = '',that = this;
var index = 0,scrollIndex = parent.parentNode.scrollIndex,text = '';
Util.loop(0,data.length,function(i){
text = that.getText(data[i]);
html += '<li>'+text+'</li>';
//初始化时有默认值,应该选中当前值,否则index就会为0,即选中第一个
if(that.value.length && that.value[scrollIndex] && (Util.isObject(data[i]) && data[i][that.valueKey] == that.value[scrollIndex][that.valueKey] || data[i] == that.value[scrollIndex])){
index = i;
}
});
parent.innerHTML = html;
this.offset.push(0);
this.selectItem(data, index, scrollIndex);//选中并创建下一级选择器
},
updateList: function(index,data){
var dom = this.box.childNodes[index];
if(!dom){
this.createScroll(data);
return;
}
dom = Util.getEle(dom,'ul');
this.addList(dom, data);
},
setScroll: function(index,data,value,callback) {
value && (this.value[index] = value);
this.offset.length = this.selectIndex.length = this.result.length = this.selectIndex.length = index;
if(index == 0){
this.data = data;
} else {
var temp = this.data[this.selectIndex[0]];
for(var i = 1, len = index; i < len; i++){
temp = temp[this.childKey][this.selectIndex[i]];
}
temp && (temp[this.childKey] = data);
}
this.updateList(index,data);
this.value = [];
Util.isFunc(callback) && callback(index,this.result);
},
removeScroll: function(index){
var that = this;
var node = this.box.childNodes[index];
if(node){
this.box.removeChild(node);
this.scrollCount--;
this.calcWidth();
}
},
calcWidth: function() {
var wraps = this.box.querySelectorAll('.mp-list-wrap');
for(var m = 0; m < wraps.length; m++){
wraps[m].style.width = (100 / this.scrollCount) + '%';
}
},
selectItem:function(data, index, scrollIndex){//params: 数据,选中的索引,当前scroll的索引
var that = this;
var oldScrollCount = this.scrollCount;
this.selectIndex.length = this.result.length = scrollIndex + 1;
this.selectIndex[scrollIndex] = index;
this.result[scrollIndex] = this.setResult(data[index]);
this.setOffset(scrollIndex, index);
if(data[index] && data[index][that.childKey] && Util.isArray(data[index][that.childKey]) && data[index][that.childKey].length){//存在子元素
if(that.scrollCount < scrollIndex + 2){//如果上一次的ul个数少于当前需要的个数,则创建新的ul
that.createScroll(data[index][that.childKey]);
} else {
that.updateList(scrollIndex + 1, data[index][that.childKey]);
}
} else {//说明当前的滚动器数目多于需要的,移除多余的
for ( var j = oldScrollCount - 1, len = that.selectIndex.length; j >= len; j-- ) {//删除多余的ul
that.removeScroll(j);
}
}
// this.scrollIndex = this.offset.length = this.selectIndex.length;
this.offset.length = this.selectIndex.length;
this.calcWidth();//计算滚动对象的宽度
Util.isFunc(that.select) && that.select(scrollIndex,this.result,index,data[index] && data[index][that.childKey] && Util.isArray(data[index][that.childKey]) && data[index][that.childKey].length);
},
fillContent: function(content){
var tagName = this.target.tagName.toLowerCase();
if(['input','select','textarea'].indexOf(tagName) != -1) {
this.target.value = content;
} else {
this.target.innerText = content;
}
},
fillResult: function(){
var value = '';
for(var i = 0,len = this.result.length; i < len; i++){
if(Util.isObject(this.result[i])){
this.result[i][this.textKey] && (value += this.result[i][this.textKey]);
} else {
value += this.result[i];
}
}
this.autoFill && this.fillContent(value);
return value;
},
hide: function(){
var that = this;
Util.getEle(this.container,'.mp-container').style.transform = 'translate3d(0,100%,0)';
Util.removeClass(body, 'mp-body');
setTimeout(function(){
that.container.style.visibility = 'hidden';
},250)
},
show: function(){
var that = this;
that.container.style.visibility = 'visible';
Util.addClass(body, 'mp-body');
setTimeout(function(){
Util.getEle(that.container,'.mp-container').style.transform= 'translate3d(0,0,0)';
},0)
},
setOffset: function(scrollIndex, index){
var scroll = this.box.childNodes[scrollIndex].querySelector('ul');
var offset = scroll.childNodes[0] ? scroll.childNodes[0].offsetHeight * index : 0;
Util.setTransform(scroll, -offset)
this.offset[scrollIndex] = offset;
},
setLock: function(value){
var confirm = Util.getEle(this.container,'.mp-confirm'),old = this.lock;
this.lock = value !== false;
if(old !== this.lock) {
this.lock ? Util.addClass(confirm,'disabled') : Util.removeClass(confirm, 'disabled');
}
},
bindEvent: function(){
var that = this;
that.target.disabled = true;
['touchstart','touchend','touchmove'].forEach(function(action){
that.box.parentNode.addEventListener(action,function(event){
event = event || window.event;
event.preventDefault();
var target = event.target;
var index = target.parentNode.scrollIndex;
var child = target.childNodes;
var liHeight = child[child.length - 1].offsetHeight;
var scrollHeight = child[child.length - 2].offsetTop;
if(target.tagName.toLowerCase() != 'ul') return;
switch(action) {
case 'touchstart':
if(coordinate.end.status){
coordinate.end.status = !coordinate.end.status;
coordinate.start.y = event.touches[0].clientY;
coordinate.start.time = Date.now();
}
break;
case 'touchmove':
coordinate.move.y = event.touches[0].clientY;
var distance = coordinate.start.y - coordinate.move.y;
var os = distance + that.offset[index];
if(os < 0){//已经滑到最顶部
Util.setTransform(target, Util.damping(-os));
} else if(os <= scrollHeight){
Util.setTransform(target, -os);
} else {//超过了整体的高度
Util.setTransform(target, -(scrollHeight + Util.damping(os-scrollHeight)));
}
break;
case 'touchend':
coordinate.end.y = event.changedTouches[0].clientY;
var count = Math.floor((that.offset[index] + (coordinate.start.y - coordinate.end.y))/liHeight + 0.5)
count = count < 0 ? 0 : Math.min(count, target.childNodes.length - 1);
var temp = that.offset[index];
that.offset[index] = count < 0 ? 0 : Math.min(count * liHeight,target.offsetHeight - 5 * liHeight)
Util.setTransform(target, -that.offset[index]);
coordinate.end.status = true;
that.selectIndex.length = index + 1;
that.selectIndex[index] = count;
that.selectItem(that.getData(that.selectIndex),count,index)
break;
}
},false)
});
that.target.addEventListener('touchstart',function(event){
(event || window.event).preventDefault();
//记录旧结果,用于取消恢复
that.oldResult = that.result.slice(0);
that.show();
});
// 用click事件代替touchstart防止点透
Util.getEle(that.container,'.mp-cancel').addEventListener('click',function(){
that.hide();
//恢复旧的结果
that.update({
value: that.oldResult,
valueKey: that.textKey
});
Util.isFunc(that.cancel) && that.cancel();
},false);
Util.getEle(that.container,'.mp-confirm').addEventListener('click',function(){
if(that.lock) return;
var value = that.fillResult();
that.hide();
Util.isFunc(that.confirm) && that.confirm(value, that.result);
});
}
}
if (typeof module !== 'undefined' && typeof exports === 'object') {
module.exports = Picker;
} else if (typeof define === 'function' && (define.amd || define.cmd)) {
define(function() { return Picker; });
} else {
global.Picker = Picker;
}
})(this);
html,body,div,li,ul {padding: 0; margin: 0;}
body{position: relative;}
li,ul {list-style: none;}
.mp-body {
overflow: hidden;
}
.mp-mask {
position: fixed;
left:0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,.4);
/* display: none; */
visibility: hidden;
overflow: hidden;
}
.mp-shadowup,.mp-shadowdown {
height: 5em;
width: 100%;
position: absolute;
left: 0;
z-index: 50;
}
.mp-shadowup {
top: -1px;
background:-webkit-linear-gradient(to bottom, #FFF, rgba(255, 255, 255, .3));
background:linear-gradient(to bottom, #FFF, rgba(255, 255, 255, .3));
pointer-events: none;
}
.mp-shadowdown {
bottom: 0;
background: -webkit-linear-gradient(to top, #FFF, rgba(255,255,255,.3));
background: linear-gradient(to top, #FFF, rgba(255,255,255,.3));
pointer-events: none;
}
.mp-line {
position: absolute;
top: 5em;
left: 0;
width: 100%;
height: 2.5em;
border-top: 1px solid #acacac;
border-bottom: 1px solid #acacac;
}
.mp-container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background: #FFF;
font-size: 16px;
-webkit-transition: transform .25s;
-webkit-transform: translate3d(0,100%,0);
transition: transform .25s;
transform: translate3d(0,100%,0);
}
.mp-header {
background: #e2e2e2;
color: #249ff1;
line-height: 2.5em;
}
.mp-cancel {
padding-left: 1em;
}
.mp-confirm {
float: right;
padding-right: 1em;
}
.mp-confirm.disabled {
opacity: .3;
}
.mp-content {
position: relative;
height: 12.5em;
}
.mp-box {
height: 12.5em;
overflow: hidden;
}
.mp-box:after {
display: table;
height: 0;
clear: both;
content: '';
}
.mp-list-wrap ul {
position: relative;
padding: 5em 0;
-webkit-transition: transform .1s ease-out;
transition: transform .1s ease-out;
}
.mp-list-wrap ul:after {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 55;
content: '';
}
.mp-list-wrap li {
line-height: 2.5em;
font-size: 1em;
text-align: center;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.mp-list-wrap {
float: left;
height: 12.5em;
overflow: hidden;
font-size: 16px;
-webkit-transition: width .2s;
transition: width .2s;
}
<input type="text" id="cardWord" value="" placeholder="选择祝福语"/>
const Picker = require("@/picker/mobile-picker.js");
let fruit = ['新春快乐,虎年大吉!', '万事顺意,虎气冲天!', '好运连连,虎气生财!', '身体健康,虎虎生威!'
, '阖家欢乐,虎年顺意!', '喜气盈门,虎年快乐!'];
new Picker({
target: "#cardWord",
data: fruit,
value: '新春快乐,虎年大吉!',
});
let word = $("#cardWord").val();
//获取选中的值