移动端上下滚动版级联选择器原生js实现 超详细

直接上组件代码:

<template>
    <div>
        <div class="van-picker__columns">
            <div class="van-picker-column">
                <ul class="van-picker-column__wrapper" ref="sheng" @touchstart="TouchStart"
                    @touchmove="TouchMove($event, 'sheng', 'shengmove', 'shengY')"
                    @touchend="TouchEnd('sheng', 'shengmove', 'shengY', 'shenglist')">
                    <li class="van-picker-column__item van-picker-column__item--selected">
                        <div class="van-ellipsis">请选择省份</div>
                    </li>
                    <li class="van-picker-column__item van-picker-column__item--selected" v-for="item in shenglist"
                        :key="item">
                        <div class="van-ellipsis">{{ item }}</div>
                    </li>
                </ul>
            </div>
            <div class="van-picker-column">
                <ul class="van-picker-column__wrapper" ref="shi" @touchstart="TouchStart"
                    @touchmove="TouchMove($event, 'shi', 'shimove', 'shiY')"
                    @touchend="TouchEnd('shi', 'shimove', 'shiY', 'shilist')">
                    <li class="van-picker-column__item van-picker-column__item--selected">
                        <div class="van-ellipsis">请选择市区</div>
                    </li>
                    <li class="van-picker-column__item van-picker-column__item--selected" v-for="item in shilist"
                        :key="item">
                        <div class="van-ellipsis">{{ item }}</div>
                    </li>
                </ul>
            </div>
            <div class="van-picker-column">
                <ul class="van-picker-column__wrapper" ref="qu" @touchstart="TouchStart"
                    @touchmove="TouchMove($event, 'qu', 'qumove', 'quY')"
                    @touchend="TouchEnd('qu', 'qumove', 'quY', 'qulist')">
                    <li class="van-picker-column__item van-picker-column__item--selected">
                        <div class="van-ellipsis">请选择区域</div>
                    </li>
                    <li class="van-picker-column__item van-picker-column__item--selected" v-for="item in qulist"
                        :key="item">
                        <div class="van-ellipsis">{{ item }}</div>
                    </li>
                </ul>
            </div>
            <div class="van-picker__mask"></div>
            <div class="van-hairline-unset--top-bottom van-picker__frame"></div>
        </div>
    </div>
</template>
<script>
import axios from "axios"
export default {
    data() {
        return {
            startY: 0,
            fuzhi: 110,
            shenglist: [],
            shilist: [],
            qulist: [],
            shengY: 0,     
            
            shengmove: 0,

            shimove: 0,
            shiY: 0,

            qumove: 0,
            quY: 0,
            // 每个滚动区域的变量
        }
    },
    methods: {
        TouchStart(e) {
            // 获取鼠标距离顶部px
            this.startY = e.changedTouches[0].pageY;
        },
        TouchMove(e, params, move, Y) {
            // 滑动鼠标 得出距离差 加上 上次滑动的距离 就等于该位移多少
            this[move] = this[Y] + e.changedTouches[0].pageY - this.startY
            // 位移
            this.$refs[params].style = `transform:translate3d(0px, ${this[move] + this.fuzhi}px, 0px)`;
        },
        TouchEnd(params, move, Y, list) {
            // 传入四个参数,parmas是滑动的哪个区域,move是需要保存下来距离顶部的位置,Y是根据距离计算得出的下标,list是区域数组
            // 边距判断 判断是不是滑出小于0的下标了,是就让他回去,不是不管
            this[Y] = Math.round(this[move] / 44) * 44 >= 0 ? 0 : Math.round(this[move] / 44) * 44
            // 判断是不是超出了数组下标,是就回去,不是不管
            if (Math.round(this[Y] / 44) * 44 <= -(this[list].length + 2) * 44 + this.fuzhi - 22) {
                this[Y] = -(this[list].length + 2) * 44 + this.fuzhi - 22
            }
            // 根据算出的下标 四舍五入得出距离最近的是哪个下标也就是选的哪个区域, 让他位移加上过度
            this.$refs[params].style = `transform:translate3d(0px, ${Math.round(this[Y] / 44) * 44 + this.fuzhi}px, 0px); transition: all 0.3s;`;
            // 判断选的哪个区域渲染列表
            if (params === 'sheng') {
                axios.get('http://hmajax.itheima.net/api/city', {
                    params: {
                        pname: this.shenglist[-Math.round(this.shengY / 44) - 1]
                    }
                }).then(res => {
                    this.shilist = res.data.list
                    // 掉玩接口让他变成请选则 大于0就不是请选择三个字,
                    this.shimove = 0
                    this.shiY = 0
                    this.$refs.shi.style = `transform:translate3d(0px, 110px, 0px); transition: all 0.3s;`;
                    this.qumove = 0
                    this.quY = 0
                    this.$refs.qu.style = `transform:translate3d(0px, 110px, 0px); transition: all 0.3s;`;
                })
            }
            if (params === 'shi') {
                axios.get('http://hmajax.itheima.net/api/area', {
                    params: {
                        // 获取数组下标属性
                        pname: this.shenglist[-Math.round(this.shengY / 44) - 1],
                        cname: this.shilist[-Math.round(this.shiY / 44) - 1],
                    }
                }).then(res => {
                    this.qulist = res.data.list;
                    console.log(this.qulist);
                    this.qumove = 0
                    this.quY = 0
                    this.$refs.qu.style = `transform:translate3d(0px, 110px, 0px); transition: all 0.3s;`;
                    // 传过去的val值

                })
            }
            this.$emit('val',
                [this.shenglist[-Math.round(this.shengY / 44) - 1]
                    , this.shilist[-Math.round(this.shiY / 44) - 1],
                this.qulist[-Math.round(this.quY / 44) - 1]])

        },

    },
    created() {
        axios.get('http://hmajax.itheima.net/api/province').then(res => {
            this.shenglist = res.data.list
        })
    },

}
</script>
<style scoped lang="scss">
$r: 37.5;

.van-picker__columns {
    height: 264rem/$r;
    position: relative;
    display: -webkit-box;
    display: -webkit-flex;
    display: flex;
    cursor: grab;
}

.van-picker-column {
    -webkit-box-flex: 1;
    -webkit-flex: 1;
    flex: 1;
    overflow: hidden;
    font-size: 0.42667rem;
}

.van-picker-column__wrapper {
    -webkit-transition-timing-function: cubic-bezier(.23, 1, .68, 1);
    transition-timing-function: cubic-bezier(.23, 1, .68, 1);
    transform: translateY(110px);

}


ol,
ul {
    margin: 0;
    padding: 0;
    list-style: none;
}

.van-picker-column__item {
    height: 44rem/$r;
    display: -webkit-box;
    display: -webkit-flex;
    display: flex;
    -webkit-box-align: center;
    -webkit-align-items: center;
    align-items: center;
    -webkit-box-pack: center;
    -webkit-justify-content: center;
    justify-content: center;
    padding: 0 0.10667rem;
    color: #000;
}

.van-ellipsis {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.van-picker__mask {
    background-size: 100% 110px;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    background-image: -webkit-linear-gradient(top, hsla(0, 0%, 100%, .9), hsla(0, 0%, 100%, .4)), -webkit-linear-gradient(bottom, hsla(0, 0%, 100%, .9), hsla(0, 0%, 100%, .4));
    background-image: linear-gradient(180deg, hsla(0, 0%, 100%, .9), hsla(0, 0%, 100%, .4)), linear-gradient(0deg, hsla(0, 0%, 100%, .9), hsla(0, 0%, 100%, .4));
    background-repeat: no-repeat;
    background-position: top, bottom;
    -webkit-transform: translateZ(0);
    transform: translateZ(0);
    pointer-events: none;
}

.van-picker__frame {
    position: absolute;
    top: 50%;
    right: 0.42667rem;
    left: 0.42667rem;
    height: 44rem/$r;
    z-index: 2;
    -webkit-transform: translateY(-50%);
    transform: translateY(-50%);
    pointer-events: none;
}

[class*=van-hairline]::after {
    position: absolute;
    box-sizing: border-box;
    content: ' ';
    pointer-events: none;
    top: -50%;
    right: -50%;
    bottom: -50%;
    left: -50%;
    border: 0 solid #ebedf0;
    -webkit-transform: scale(.5);
    transform: scale(.5);
    border-width: 0.02667rem 0;
}
</style>

用法如下:

  <input type="text" " placeholder="选择省 / 市 / 区"   v-model="from.location" @focus="ssx">

定义的参数和js代码

 data() {
        return {
            from: {
                location: '',
            },
            value: '',
           
        }
    },
methods:{
 ssx() {
            this.$refs.uio.style.zIndex = 2000
            this.$refs.iop.style.bottom = 0
            this.$refs.iop.style.zIndex = 2100
        },
        lopss(val) {
            // 接收
            console.log(val);
            this.value = val
        },
        que() {
            this.$refs.uio.style.zIndex = -2
            this.$refs.iop.style.bottom = -300
            this.$refs.iop.style.zIndex = -3
            this.from.location = this.value.join('/')

        }
}

这边是一个级联选择器的模态框:

 <div class="jilian" @touchmove.prevent ref="uio">
            <div class="jilian2" ref="iop">
                <div class="tous">
                    <div class="qx">取消</div>
                    <div class="qd" @click="que">确定</div>
                </div>

                <jilian @val=lopss></jilian>

            </div>
        </div>

还有级联选择器的模态框css:

<style scoped lang="scss">
$r: 37.5;


.jilian {
    position: fixed;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: -1000;

    .jilian2 {
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 300rem / $r;
        background-color: white;
        border-radius: 20rem/$r 20rem/$r 0 0;
        bottom: -300rem / $r;
        transition: all 0.5s;
        z-index: -2000;

        .tous {
            width: 100%;
            height: 30rem / $r;
            // background-color: red;
            margin-top: 10rem/$r;

            .qx {
                float: left;
                font-size: 14rem/$r;
                text-indent: 14rem/$r;
                // margin-top: 14rem/$r;
                line-height: 30rem/$r;
            }

            .qd {
                float: right;
                font-size: 14rem/$r;
                margin-right: 14rem/$r;
                // margin-top: 14rem/$r;
                color: #5780C1;
                line-height: 30rem/$r;
            }
        }

    }

}
</style>

还有rem文件里面的index像素配置 需要在全局引入一下:

(function flexible(window, document) {
    var docEl = document.documentElement
    var dpr = window.devicePixelRatio || 1

    // adjust body font size
    function setBodyFontSize() {
        if (document.body) {
            document.body.style.fontSize = (12 * dpr) + 'px'
        }
        else {
            document.addEventListener('DOMContentLoaded', setBodyFontSize)
        }
    }
    setBodyFontSize();

    // set 1rem = viewWidth / 10
    function setRemUnit() {
        var rem = docEl.clientWidth / 10
        docEl.style.fontSize = rem + 'px'
    }

    setRemUnit()

    // reset rem unit on page resize
    window.addEventListener('resize', setRemUnit)
    window.addEventListener('pageshow', function (e) {
        if (e.persisted) {
            setRemUnit()
        }
    })

    // detect 0.5px supports
    if (dpr >= 2) {
        var fakeBody = document.createElement('body')
        var testElement = document.createElement('div')
        testElement.style.border = '.5px solid transparent'
        fakeBody.appendChild(testElement)
        docEl.appendChild(fakeBody)
        if (testElement.offsetHeight === 1) {
            docEl.classList.add('hairlines')
        }
        docEl.removeChild(fakeBody)
    }
}(window, document))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值