自定义控件 - 基于swiper插件的底部多级选择器

昨天在完善抢座程序的时候,遇到一个问题:当用户自由选择座位的时候,需要多级向下选择,比如先选择校区->选择房间->选择对应的座位,类似于发快递的时候选择收件地址一样,是一个多级选择效果。
在网上没有找到适合自己需求的选择器,有一个是基于mui的,不太适合,所以基于这个需求,自己封装了一个比较简易的小控件以满足这里的需求。

控件的截图大致如下:没有做过多的样式效果,这个可以在每次使用的时候再自定义,现在加了效果很可能与以后的需求风格不匹配。


控件截图

大致的功能为 : 选择地区 -> 选择歌手 ->选择他的歌
这是一种常见的多级选择器的样式,可以应用在很多场景下。

接下来简单记录一下封装所对应的代码(部分代码):

首先是html代码:

<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
        <title>嗯.....</title>

        <link rel="stylesheet" href="css/swiper.min.css">
        <link rel="stylesheet" href="css/myPublic.css">
        
        <script src="js/jquery-1.10.2.js"></script>
        <script src="js/swiper.min.js"></script>
        <script src="js/mySelect.js"></script>

    </head>
    <body>
        <button class="updateData">更新数据</button>
        <div id="app" class="w750">
            <button class="btn_open">打开按钮</button>
            <button class="btn_close">关闭按钮</button>
            <!--选择器代码开始-->
            <div class="mt-select select1">
                <div class="select_box"></div>
            </div>
            <!--选择器代码结束-->
        </div>

        <script src="js/myIndex.js"></script>
    </body>
</html>

封装后对应的代码如下:

$(function() {
    var swiper_test;
    // data内部可以是字符串  也可以是键值对对象
    var data1 = [{key:'北京',value:1},{key:'西安',value:2},{key:'湖南',value:3}];
    var data2 = ['王菲','窦唯','宋冬野','赵雷','房东的猫','安九'];
    var data3 = ['无法长大', '不开的唇', '八十年代的歌', '十九岁', '已是两条路上的人', '阿刁'];
    $('.btn_open').on('click', function() {
        swiper_test = new selectSwiper({
            el: '.select1',
            colNum:3,
            data: data1,
            init: function(index) {
                // 初始化成功
            },
            updated: function(index){
                // 更新数据成功
                console.log("数据更新成功"+index);
            },
            mtSlideChangeEnd:function(swiper,activeIndex){
                // 移动停止
                // 列下标  swiper.myIndex
                // 滑动选择的下标  activeIndex

                // 简单用数据测试一下
                switch (swiper.myIndex) {
                    case 0:
                        // 第一列 滚动完毕 得到对应的值
                        if(activeIndex === -1){
                            return;
                        }
                        if(data1[activeIndex].value !== 1){
                            data2 = [];
                        }else{
                            data2 = ['王菲','窦唯','宋冬野','赵雷','房东的猫','安九'];
                        }
                        swiper_test.update_data(1,data2);
                        // 需要更新第三级
                        swiper_test.update_data(2,[]);
                        break;
                    case 1:
                        // 第二列 滚动完毕 得到对应的值
                        if(activeIndex === -1){
                            return;
                        }
                        if(data2[activeIndex] !== "赵雷"){
                            data3 = [];
                        }else{
                            data3 = ['无法长大', '不开的唇', '八十年代的歌', '十九岁', '已是两条路上的人', '阿刁'];
                        }
                        swiper_test.update_data(2,data3);
                        break;
                    case 2:
                        break;
                }
            },
            submit:function(data){
                // 提交成功
                swiper_test.close();
            },
            cancel:function(){
                // 取消提交
                console.log("取消成功");
            }
        });
        // 打开底部选择器
        swiper_test.open();
    });

    $(".btn_close").click(()=>{
        // 关闭底部选择器
        swiper_test.close();
    });

    $(".updateData").click(()=>{
        // 模拟更新数据
        var newData = ["111","222","333","444",];
        //  更新数据 - (待更新的列下标 , 更新的数据)
        swiper_test.update_data(2,newData);
    })
});

将swiper对应的原生方法进行了一些简单的封装,创建了一个selectSwiper对象,用户提供对应的一些参数和节点选择器就可以创建对象,使用open()方法打开,close()方法关闭,update_data()方法更新某一列的数据,并通过几个回调函数进行事件的捕捉:
init(初始化回调函数);
updated(数据更新完成回调函数);
mtSlideChangeEnd(滑动结束回调函数);
submit(数据提交成功回调函数);
cancel(取消提交回调函数)。

在这个层面上就可以很简单的创建这个底部选择器了,省去了很多繁琐的步骤,繁琐的步骤就是如下所示:

// 设置样式
(function(doc, win, page_width, font_size) {
    var docEl = doc.documentElement,
        resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
        recalc = function() {
            var clientWidth = docEl.clientWidth;
            if (!clientWidth) return;
            docEl.style.fontSize = clientWidth > page_width ? (font_size * 2) + 'px' : font_size * (clientWidth / (page_width /
                2)) + 'px';
        };
    if (!doc.addEventListener) return;
    win.addEventListener(resizeEvt, function() {
        recalc();
    }, false);
    doc.addEventListener('DOMContentLoaded', function() {
        recalc();
    }, false);
})(document, window, 750, 50);


// 对象 - 自定义
function selectSwiper(obj) {
    var _self = this;
    // 选择器
    _self.el = $(obj.el);
    // 底部列数
    _self.colNum =  obj.colNum || obj.data.length;
    // 自定义的swiperData对象
    _self.swiperData = {};
    // 初始化成功回调函数
    _self.swiperData.init = obj.init || function (){};
    // 用户传入的数据
    _self.swiperData.data = obj.data || [];
    // 用户手指滑动当前所选中的当前列的下标
    _self.swiperData.activeIndex = (typeof obj.activeIndex === 'number' && obj.activeIndex >= -1) ? obj.activeIndex : -1;
    // 自定义用于收集各个swiper对象
    _self.swiperData.mySwipperArray = [];
    // 回调函数 - 当滑动停止时
    _self.swiperData.mtSlideChangeEnd = obj.mtSlideChangeEnd || function(){};
    // 回调函数 - 用户点击提交按钮时
    _self.swiperData.submit = obj.submit || function(){};
    // 回调函数 - 用户点击取消按钮时
    _self.swiperData.cancel = obj.cancel || function(){};
    // 自定义对象 - 用户最后点击提交时所有滑动的下标
    _self.swiperData.resultIndexObj = {};
    // 回调函数 - 用户更新数据成功后
    _self.swiperData.updated = obj.updated || function(){};

    // 动态拼写上每一个子项的包裹代码
    _self.innerHtmlText = "";

    for(let i = 0;i<_self.colNum;i++){
        _self.innerHtmlText+=`
        <div class="select_box_item item`+i+`">
            <div class="selectData">
                <div class="cloth"></div>
                <div class="swiper-container">   
                    <div class="swiper-wrapper"></div>
                </div>
            </div>
        </div>
        `
    }

    /**
     * 初始化swiper
     */
    _self.init = function() {
        // 添加 “取消”、“确定” 按钮
        $(".mt-select"+_self.el.selector).prepend(`
            <div class="select-btn">
                <button class="btn-cancel">取消</button>
                <button class="btn-submit">确定</button>
            <div>`);
        // 添加遮罩层
        $("body").prepend('<div class="click_no_panel"></div>');
        // 将上面动态拼接的代码插入到dom中
        $(".mt-select"+_self.el.selector).find('.select_box').html(_self.innerHtmlText);
        // 动态为每一个子项内部赋值 - 用户传入的数据 - 进行解析 - 获得swiper对象 - 并收集
        for (let num = 0; num < this.colNum; num++) {
            // 为每个子项生成swiper对象
            var  mySwipper = new Swiper(_self.el.selector+" .item"+num+" .swiper-container", {
                direction: 'vertical',
                slidesPerView: 8,
                centeredSlides: true,
                slideToClickedSlide: true,
                onInit: function(swiper) {
                    // 获得当前列对应的数据
                    var data = _self.swiperData.data;
                    // 将生成的数据放入对象中
                    if(num === 0){
                        swiper.appendSlide(init_data_for_swiper(data));
                    }else{
                        swiper.appendSlide(init_data_for_swiper(undefined));
                    }
                },
                // 当滑动停止时的回调函数
                onSlideChangeEnd: function(swiper) {
                    // 得到当先选择器的下标 - 就可以知道是哪一个了
                    _self.swiperData.activeIndex = swiper.activeIndex - 1;
                    // 当前列的对应的某一项值
                    var activeIndex = _self.swiperData.activeIndex;
                    _self.swiperData.mtSlideChangeEnd(swiper,activeIndex);
                    // 滑动结束 - 将 {选择器下标 : 数据下标} 格式存入
                    _self.swiperData.resultIndexObj[swiper.myIndex]= activeIndex;
                },
            });
            // 初始化成功回调函数
            _self.swiperData.init(_self.swiperData.activeIndex);
            // 自定义的一个属性 - myIndex - 目的是为了能得到是哪一个项在滑动结束
            mySwipper.myIndex = num;
            // 收集到生成的对象
            this.swiperData.mySwipperArray.push(mySwipper);
        }
    }

    /**
     * 显示
     */
    _self.open = function(){
        var _self = this;
        $(".mt-select"+_self.el.selector).addClass('open');
        $(".mt-select"+_self.el.selector).addClass('open_ani');
        // 遮罩层点击事件
        $(".click_no_panel").click(()=>{
            // 关闭底部选择器
            _self.close();
        });
        // 初始化事件 - 取消
        $(".mt-select"+_self.el.selector).find(".btn-cancel").click(()=>{
            _self.close();
            // 调用取消的回调函数
            _self.swiperData.cancel();
        });
        // 初始化事件 - 提交
        $(".mt-select"+_self.el.selector).find(".btn-submit").click(()=>{
            // 调用提交的回调函数
            _self.swiperData.submit(_self.swiperData.resultIndexObj);
        });
    }

    /**
     * 关闭
     */
    _self.close = function() {
        // 点击遮罩层 - 去掉遮罩层
        $(".click_no_panel").remove();
        $(".mt-select"+_self.el.selector).removeClass('open');
        $(".mt-select"+_self.el.selector).removeClass('open_ani');

        // 清除掉对象 - 释放浏览器对其缓存
        for(let swiper of _self.swiperData.mySwipperArray){
            swiper.destroy(true);
        }
    };

    /**
     * 更新某一项的data数据
     * @param {Object} myIndex 项下标
     * @param {Object} data 数据本体
     */
    _self.update_data = function(myIndex,data){
        // 更新数据
        myIndex = myIndex || -1;
        data = data || [];
        if(myIndex === -1){
            throw "待更新的列下标不正确";
        }
        // 将待更新的列表的内部代码替换掉 - 否则会造成滑动有误
        let htmlStr =
            `<div class="selectData">
                <div class="cloth"></div>
                <div class="swiper-container">   
                    <div class="swiper-wrapper"></div>
                </div>
            </div>`;
        // 先清掉数据
        $(_self.el.selector+" .item"+myIndex).html(htmlStr);
        // 更新某个swiper的逻辑 - 即新建一个;对应的swiper对象
        var mySwipper = new Swiper(_self.el.selector+" .item"+myIndex+" .swiper-container", {
            direction: 'vertical',
            slidesPerView: 8,
            centeredSlides: true,
            slideToClickedSlide: true,
            onInit: function(swiper) {
                swiper.appendSlide(init_data_for_swiper(data));
                // 更新数据成功回调函数
                _self.swiperData.updated(myIndex);
            },
            onSlideChangeEnd: function(swiper) {
                // 得到当先选择器的下标 - 就可以知道是哪一个了
                _self.swiperData.activeIndex = swiper.activeIndex - 1;
                var activeIndex = _self.swiperData.activeIndex;
                _self.swiperData.mtSlideChangeEnd(swiper,activeIndex);
                // 滑动结束 - 将 {选择器下标 : 数据下标} 格式存入
                _self.swiperData.resultIndexObj[swiper.myIndex]= activeIndex;
            },
        });
        mySwipper.myIndex = myIndex;
        this.swiperData.mySwipperArray[myIndex] = mySwipper;
    };

    /**
     * 公用的方法
     * @param {Object} data 数据
     */
    function init_data_for_swiper(data){
        var s = [];
        s[0] = '<div class="swiper-slide">请选择</div>';
        if(data === undefined){
            return s;
        }
        for (i = 0; i < data.length; i++) {
            // 如果需要的是键值对 - 那么就在data中传入对象集合
            if(typeof data[i] === "object"){
                s[i + 1] = '<div class="swiper-slide" data="'+data[i].value+'">' + data[i].key + '</div>';
            }else{
                s[i + 1] = '<div class="swiper-slide">' + data[i] + '</div>';
            }
        }
        return s;
    }

    // 调用初始化方法
    _self.init();
}

以上代码为面向swiper封装的一些方法,比较简单,均可以在swiper官网查询到。

最后贴上对应的一些对应的css样式:

* {
   -webkit-box-sizing: border-box;
   -moz-box-sizing: border-box;
   box-sizing: border-box;
   appearance: none;
   -moz-appearance: none;
   -webkit-appearance: none
}
body{
   padding: 0;
   margin: 0;
}
.mt-select{
   position: fixed;
   height: 300px;
   width:100%;
   max-height: 400px;
   bottom: -300px;
   z-index: 11;
   background-color: white;
}
.select_box {
   /*伸缩布局*/
   display: flex;
   position: absolute;
   width:100%; 
   height: 250px;
   bottom:0;
}

.select_box .select_box_item {
   display: flex;
   height: 100%;
   flex: 1;
   flex-direction: column;
   align-items: center;
   justify-content: center;

   background-color: white;
   border: 1px solid rgba(100,100,100,0.1);
}


/* 字体大小 */
.select_box_item {
   font-size: 20px;
}

/* 打开时候的动画 */
.open{
   visibility: visible;
}
.open_ani{
   animation: fadeInUp .3s ease-out;
   animation-fill-mode: forwards
}
/* 遮罩层 */
.click_no_panel {
   width:100%;
   height: 100%;
   background-color:rgba(100,100,100,0.2);
   position: absolute;
   z-index: 10;
}

/* 确定-取消按钮 - 样式*/
.select-btn{
   height: 50px;
   width: 100%;
}
.select-btn button{
   position: absolute;
   width:50px;
   height: 30px;
   font-size: 15px;
   background-color: white;
   border: 0px;
   border-radius: 5px;
}
.select-btn button:active{
   background-color: rgba(100,100,100,0.2);
   color:white;
}
.select-btn .btn-cancel{
   left: 10px;
}
.select-btn .btn-cancel,.select-btn .btn-submit{
   top:10px;
}
.select-btn .btn-submit{
   right: 10px;
}

/* 滑动 - 效果 */
.swiper-container,
.selectData {
   height: 250px;
}

.swiper-slide {
   height: .7rem;
   line-height: .7rem;
   font-size: .4rem;
   color: #ccc;
   overflow: hidden;
   
   text-align: center;
}

.swiper-slide:first-child {
   color: #b7babf
}

.swiper-slide-prev,
.swiper-slide-next {
   font-size: .4rem;
}

.swiper-slide-active {
   font-size: .4rem;
   color: #191919
}

/* 选中行样式 */
.cloth {
   position: absolute;
   height: .625rem;
   top: 110px;
   left: 0;
   right: 0;
   background-color: rgba(100, 100, 100, .03)
}
/* 模拟更新数据 */
.updateData{
   position: absolute;
   z-index: 10000; 
}

@keyframes fadeInUp {
   0% {
       bottom: -300px;
   }
   85% {
       bottom: 10px;
   }
   100% {
       bottom: 0;
   }
}

以上是简单的做一些记录,完整代码在下面的github地址上面,以后有时间会持续更新完善,这是自己从网上copy控件到自己手动创建控件的一个转折点,哈哈哈,开心。

github地址:
https://github.com/xicunyang/bottom-select/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,092评论 1 32
  • 他去了远方,一个没有痛苦和悲伤的地方,从此以后都以神的姿态,融入我的生命中。同生共死。
    甜小草阅读 193评论 0 0
  • 生命里,你最重要的是什么? 这个问题,我想一千个人有一千个答案。 哪怕就是同一个人,不同时期也有不同时期的答案。 ...
    季米花儿阅读 419评论 0 0
  • 如果此生,不能遇见你 我该以怎样的姿态去度过 那每一个春花秋月的浪漫时光里 有谁来共赏我的女儿情怀 还有那孤独与悲...
    小宝林子阅读 578评论 4 18