美文网首页web前端
自定义radio组件

自定义radio组件

作者: 姜治宇 | 来源:发表于2020-09-06 17:37 被阅读0次

原生html的radio标签太丑了,实际开发中都需要自定义radio。如何实现呢?
思路很简单,我们需要另外做一套好看的样式,然后把原来的radio标签隐藏掉即可。

静态结构

components/myradio.vue:

<template>
    <div class="my-radio">

        <span class="my-radio__input">
            <span class="my-radio__inner"></span>
            <!--真实的radio,被隐藏,去掉.my-radio__original可以看到-->
            <input class="my-radio__original" type="radio" />
        </span>
        <span class="my-radio__label">
          说明文字
        </span>
    </div>
</template>

<script>
    export default {
        name: "MyRadio"
    }
</script>

<style lang="scss">
    .my-radio {
        color: #606266;
        font-weight: 500;
        line-height: 1;
        position: relative;
        cursor: pointer;
        display: inline-block;
        white-space: nowrap;
        outline: none;
        font-size: 14px;
        margin-right: 30px;
        -moz-user-select: none;
        -webkit-user-select: none;
        -ms-user-select: none;

        .my-radio__input {
            white-space: nowrap;
            cursor: pointer;
            outline: none;
            display: inline-block;
            line-height: 1;
            position: relative;
            vertical-align: middle;

            .my-radio__inner {
                border: 1px solid #dcdfe6;
                border-radius: 100%;
                width: 14px;
                height: 14px;
                background-color: #fff;
                position: relative;
                cursor: pointer;
                display: inline-block;
                box-sizing: border-box;
            }

            .my-radio__original {
                opacity: 0;
                outline: none;
                position: absolute;
                z-index: -1;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                margin: 0;
            }
        }

        .my-radio__label {
            font-size: 14px;
            padding-left: 10px;
        }
    }
</style>

main.js:

import Vue from 'vue'
import App from './App.vue'
import MyRadio from './components/myradio'
Vue.config.productionTip = false

Vue.component(MyRadio.name,MyRadio)
new Vue({
  render: h => h(App),
}).$mount('#app')

App.vue:

<template>
  <div id="app">
    <my-radio></my-radio>
  </div>
</template>

功能实现

<template>
  <div id="app">
    <my-radio label="0" v-model="sex">男</my-radio>
    <my-radio label="1" v-model="sex">女</my-radio>
  </div>
</template>
<script>
  export default {
    data(){
      return {
        sex: '0'
      }
    }
  }

</script>

需要实现的功能:

1、接收props

input一般都有name,所以一块加上。

<template>
    <div class="my-radio">

        <span class="my-radio__input">
            <span class="my-radio__inner"></span>
            <!--真实的radio,被隐藏,去掉.my-radio__original可以看到-->
            <input :name="name" :value="label" type="radio" class="my-radio__original" />
        </span>
        <span class="my-radio__label">
           说明文字
        </span>
    </div>
</template>

<script>
    export default {
        name: "MyRadio",
        props:{
            label:{ //接收label
                type:[String,Number,Boolean],
                default:''
            },
            value:null, // 接收v-model
            name:{ // 有可能传name
                type:String,
                default:''
            }


        }
    }
</script>

<style lang="scss">
    .my-radio {
        color: #606266;
        font-weight: 500;
        line-height: 1;
        position: relative;
        cursor: pointer;
        display: inline-block;
        white-space: nowrap;
        outline: none;
        font-size: 14px;
        margin-right: 30px;
        -moz-user-select: none;
        -webkit-user-select: none;
        -ms-user-select: none;

        .my-radio__input {
            white-space: nowrap;
            cursor: pointer;
            outline: none;
            display: inline-block;
            line-height: 1;
            position: relative;
            vertical-align: middle;

            .my-radio__inner {
                border: 1px solid #dcdfe6;
                border-radius: 100%;
                width: 14px;
                height: 14px;
                background-color: #fff;
                position: relative;
                cursor: pointer;
                display: inline-block;
                box-sizing: border-box;
            }

            .my-radio__original {
                opacity: 0;
                outline: none;
                position: absolute;
                z-index: -1;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                margin: 0;
            }
        }

        .my-radio__label {
            font-size: 14px;
            padding-left: 10px;
        }
    }
</style>

2、获取父组件的label文字

这里用slot插槽就行,不过考虑到这个label文字可能不传,直接用label的值,所以需要做个判断。

        <span class="my-radio__label">
           <slot></slot>
            <!--如果没传内容,就用label的值-->
            <template v-if="!$slots.default">{{label}}</template>
        </span>
3、v-model的处理

这里需要注意的是v-model,根据单向数据流动的原则,父组件传给子组件的value,子组件不能直接修改。但单选框肯定是要改value的,怎么办呢?
解决方案是:
子组件另外定义一个属性,不过不能在data里面直接定义,因为这个属性是跟着value走的,我们可以定义一个计算属性,并定义它的get和set,其中set里面用$emit触发input事件即可:

<template>
    <div class="my-radio">

        <span class="my-radio__input">
            <span class="my-radio__inner"></span>
            <!--真实的radio,被隐藏,去掉.my-radio__original可以看到-->
            <input v-model="model" :name="name" :value="label" class="my-radio__original" type="radio" />
        </span>
        <span class="my-radio__label">
           <slot></slot>
            <!--如果没传内容,就用label的值-->
            <template v-if="!$slots.default">{{label}}</template>
        </span>
    </div>
</template>

<script>
    export default {
        name: "MyRadio",
        computed:{
            model:{
                get(){
                    return this.value
                },
                set(val){
                    console.log(val)
                    this.$emit('input', val)
                }
            },
        },
        props:{
            label:{ //接收label
                type:[String,Number,Boolean],
                default:''
            },
            value:null, // 接收v-model
            name:{ // 有可能传name
                type:String,
                default:''
            }


        }
    }
</script>

<style lang="scss">
   ...
</style>

这里有个坑。这段代码测试是无效的,当点击radio时,set函数根本无法触发。为啥呢?
因为radio渲染出来应该是一个行内标签,而div是块级标签肯定不行了。我们将最外层标签换成label即可。

4、选中效果
    .is-checked {
        .my-radio__input{
            .my-radio__inner{
                border-color:#409eff;
                background: #409eff;
                &:after{
                    transform: translate(-50%,-50%) scale(1);
                }
            }
        }
        .my-radio__label{
            color:#409eff;
        }
    }

判断如果label和value的值是相等的,那就是选中状态。

<label class="my-radio" :class="{'is-checked': label === value}">
   ...
</label>
5、最终代码
<template>
    <label class="my-radio" :class="{'is-checked': label === value}">

        <span class="my-radio__input">
            <span class="my-radio__inner"></span>
            <!--真实的radio,被隐藏,去掉.my-radio__original可以看到-->
            <input v-model="model" :name="name" :value="label" class="my-radio__original" type="radio" />
        </span>
        <span class="my-radio__label">
           <slot></slot>
            <!--如果没传内容,就用label的值-->
            <template v-if="!$slots.default">{{label}}</template>
        </span>
    </label>
</template>

<script>
    export default {
        name: "MyRadio",
        computed:{
            model:{
                get(){
                    return this.value
                },
                set(val){
                    console.log(val)
                    this.$emit('input', val)
                }
            },
        },
        props:{
            label:{ //接收label
                type:[String,Number,Boolean],
                default:''
            },
            value:null, // 接收v-model
            name:{ // 有可能传name
                type:String,
                default:''
            }


        }
    }
</script>

<style lang="scss">
    .my-radio {
        color: #606266;
        font-weight: 500;
        line-height: 1;
        position: relative;
        cursor: pointer;
        display: inline-block;
        white-space: nowrap;
        outline: none;
        font-size: 14px;
        margin-right: 30px;
        -moz-user-select: none;
        -webkit-user-select: none;
        -ms-user-select: none;

        .my-radio__input {
            white-space: nowrap;
            cursor: pointer;
            outline: none;
            display: inline-block;
            line-height: 1;
            position: relative;
            vertical-align: middle;

            .my-radio__inner {
                border: 1px solid #dcdfe6;
                border-radius: 100%;
                width: 14px;
                height: 14px;
                background-color: #fff;
                position: relative;
                cursor: pointer;
                display: inline-block;
                box-sizing: border-box;
            }

            .my-radio__original {
                opacity: 0;
                outline: none;
                position: absolute;
                z-index: -1;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                margin: 0;
            }
        }

        .my-radio__label {
            font-size: 14px;
            padding-left: 10px;
        }
    }

    /*增加选中的样式*/
    .is-checked {
        .my-radio__input{
            .my-radio__inner{
                border-color:#409eff;
                background: #409eff;
                &:after{
                    transform: translate(-50%,-50%) scale(1);
                }
            }
        }
        .my-radio__label{
            color:#409eff;
        }
    }
</style>

相关文章

网友评论

    本文标题:自定义radio组件

    本文链接:https://www.haomeiwen.com/subject/yvqcektx.html