使用div生成一个自定义样式的input

680 阅读2分钟

因为有个需求,在input里面输入的文字可以自定义加粗改色等等,让人能一目了然的看见重点内容,比如hello,world!

1. div contenteditable=true

  1. input[text]标签是无法实现输入的内容部分文字自定义样式的
  2. 如果要实现上述的样式,就需要一个可编辑的div,然后手动更改div的innerHtml即可。
  3. 把一个div做成可以编辑的输入框,contenteditable是一个先决条件,设置为true,然后该div就有一定的编辑能力。

2. v-model的自定义组件内部实现

  1. 因为已经更改了文本内容,所以为了可编辑div产生减少负面影响(bug),不能直接使用div,而是封装成一个组件。
  2. 然后使用v-model实现双向绑定value。(props:{value}this.$emit('input',inputValue))
  3. 既在组件内部更改文字样式呈现出来,又在组件外部输出不带样式标签的实际真实内容,力求不影响页面的前端逻辑。

3. 组件实现

  • customInputDiv.vue

<template>
    <div class="custom-input-div-wrap" @click="divClick">
        <div class="custom-input-div" ref="cusInputRef" :contenteditable="true" @input="fieldInput" @focus="inputFocus" @blur="inputBlur" :placeholder="placeholder" @keydown="keydownClick">
        </div>
    </div>
</template>
<script>
import { isEmpty } from 'lodash';
export default {
    name: "CustomInputDiv",
    props: {
       placeholder: {
           type: String,
           default: ''
       },
       value: { // 接受v-model中传过来的数据
           type: String,
           default: 'name: george,gender: male,class: 3,remark: i have adream'
       },
    },
    watch: {
        value: {
            handler(newval) {
                if(!newval) { // 监听传过来的数据如果为空,则清空本地div中的innerHTML
                    this.$refs.cusInputDivRef && 
                        (this.$refs.cusInputDivRef.innerHTML = "");
                }
            },
            deep: true,
            immediate: true
        }
    },
    methods: {
        keydownClick(e) { // 去除可编辑div的回车事件,以免光标换行,样式错乱
            const event = e || window.event;
            if(event.keyCode === 13) {
                event.preventDefault
                    ? event.preventDefault()
                    : (event.returnValue = false);
            }
        },
        inputFocus () { //向父级组件触发focus 回调函数
            this.$emit('cus-focus');
        },
        fieldInput () {
            let text = this.$refs.cusInputDivRef.innerHTML;
            let text = text
                        .replaceAll('<span style="color:red">', '')
                        .replaceAll('</span>', '')
                        .replaceAll('<span style="color:green">', '')
                        .replaceAll('</span>', '')
                        .replaceAll('<span style="color:blue">', '')
                        .replaceAll('</span>', '')
                        .replaceAll('<b>', '')
                        .replaceAll('</b>', '') // 在剪切/复制 再粘贴的过程中,可能会带有一个这种标签,需要被处理。
                        .replaceAll('&nbsp;', ' ');
            this.$emit('input', text); // 组件内部更改部分样式呈现出来,组件外部数据依然是正确数据,相互不受影响
            this.$emit('cus-input', text);
        },
        inputBlur() {
            let colorList = ['red', 'green', 'blue'];
            this.$refs.cusInputDivRef.innerHTML = this.value.split(',')
                        .map((v,i) => {
                            if(i < 3) {
                                const str = v.split(':')[0];
                                return v.replaceAll(str, (match) => `<span style="color:${colorList[i]}">${match}</span>`)
                            }
                            return v;
                        })
                        .join(',');//失去焦点后,更改文本样式,赋值给innerHTML
            this.$refs.cusInputDivRef.scrollLeft = 0; // 文本靠左显示
            this.$emit('cus-blur'); // 失焦回调。
        },
        clickClear() { // 给父级组件提供一个清空事件,方便清空,并回调。
            this.$refs.cusInputDivRef.innerHTML = "";
            this.$emit('click-clear');
        },
        divClick() { //点击div获取焦点
            this.$refs.cusInputDivRef.focus();
        }
    }
}
</script>
<style lang="scss" scoped>
.custom-input-div-wrap {
    width: 100%;
    min-width: 100px;
    padding: 10px 20px
    border: 1px solid #000;
    border-radius: 3px;
    .custom-input-div {
        background-color: transparent;
        text-align:left;
        height:40px;
        line-height: 40px;
        font-size: 14px;
        color: #666;
        outline:none;
        box-sizing: border-box;
        white-space: nowrap;
        overflow-x:auto;
        overflow-y:hidden;
        &::-webkit-scrollbar {
            display: none;
        }
        &:empty:before {
            content: attr(placeholder);
            color: #bdbdbd;
            opacity: 1;
        }
        &:not(:empty):before {
            content: none;
        }
        // &:focus:before {
        //     content: none;
        // }
    }
}
</style>
  • 组件总结:
  1. 组件实现 v-model双向绑定,主要是 设置 value的props 名[父级向子组件传输数据] 和 this.$emit('input',inputValue)[子组件向父组件回传处理完的数据],形成一个双向绑定
  2. 因为可编辑div,按回车会换行,所以如果不模拟textarea, 则废除回车的默认事件(preventDefault)
  3. 在模拟输入框的时候,可以使用css实现placeholder
    • &:empty:before,使用div的伪元素,实现placeholder :表示当输入框为empty时,显示placeholder
    • &:not(:empty):before: 表示当输入框不为empty时,不显示placeholder
    • &:focus:before: 表示当输入框聚焦时,不显示placeholder
    • 具体使用啥,根据自己的需求而定。
  • view.vue

<template>
    <div>
        ...
        <customInputDiv ref="fieldDivRef" v-model="inputText" @cus-focus="inputFocus" @cus-blur="inputBlur" @click-clear="clickClear" @cus-input="fieldInput" :placeholder="placeholder">
        </customInputDiv>
        <button @click="clearText">清空内容</button>
    </div>
    ...
</template>
<script>
import customInputDiv from './customInputDiv.vue';
export default {
    ...
    data() {
        return {
            inputText: '',
            placeholder: 'enter please'
        }
    },
    components: {
        customInputDiv:customInputDiv
    },
    ...
    methods: {
        inputFocus() {}, // 聚焦回调
        inputBlur() {}, // 失焦回调
        clickClear() {}, // 清空回调
        fieldInput() {}, // input 回调
        clearText() {
            this.$refs.fieldDivRef.clickClear(); // 清空输入框的文字
        }
    }
}
</script>
...