当前位置: 首页 > 工具软件 > Loop Picker > 使用案例 >

mobile-picker.js

吕自明
2023-12-01
(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();
 //获取选中的值
 类似资料: