实现一个双击编辑文字组件

2,459 阅读1分钟

双击编辑文字,对于一些简单的需求就很简单,双击之后把div换成input,鼠标失去焦点之后隐藏input换回div就好了。

but本文要讲的是复杂一点的需求,主要用于一些编辑系统,或者低代码平台的编辑文字组件,属于一种基础组件吧。 类似于这样:

image.png

总体思路其实很简单

  • 两个区域,一个展示,一个编辑
  • 双击(dbclik)展示的dom,切换成编辑区域,并选中文字(使用rangeApi)
  • 失去焦点(blur),变回展示区域

花了个图: image.png

有一些要点需要说明下:

  • contenteditable 该属性让div可以编辑
  • 选中文字使用了浏览器的range api
  • 监听paste事件,添加了对粘贴文字的处理,主要是去除多余的格式
  • css样式也要注意一下,white-space: pre-line;让/n在div中可以换成 word-break: break-all; 让文字长度超出div之后断行

有问题看代码吧:

<template>
    <section class="element-box" :style="boxStyle">
        <!--  展示部分-->
        <div
            class="show-text"
            :style="textStyle"
            v-if="!isEdit"
            @dblclick="editText"
            v-html="text"
        ></div>
        <!--  编辑部分-->
        <div ref="text" class="textbox" v-if="isEdit">
            <div
                class="textbox-container"
                contenteditable="true"
                @blur="updateText()"
                id="textbox"
                :style="textStyle"
                v-html="text"
            ></div>
        </div>
    </section>
</template>

<script>
export default {
    name: 'edit-text-test',
    data() {
        return {
            isEdit: false,
            text: '双击编辑文字',
            boxInfo: {
                width: 200,
                height: 36
            }
        }
    },
    computed: {
        //文字的样式
        textStyle() {
            return {
                fontSize: '24px',
                textAlign: 'center'
            }
        },
        //容器的宽高
        boxStyle() {
            return {
                width: this.boxInfo.width + 'px',
                height: this.boxInfo.height + 'px'
            }
        }
    },
    methods: {
        editText() {
            this.isEdit = true
            setTimeout(() => {
                //选中文字
                if (window.getSelection) {
                    let selection = window.getSelection()
                    let range = document.createRange()
                    range.selectNodeContents(document.getElementById('textbox'))
                    selection.removeAllRanges()
                    selection.addRange(range)
                }
                //粘贴时,去除多余格式
                document
                    .getElementById('textbox')
                    .addEventListener('paste', e => {
                        e.preventDefault()
                        e.stopPropagation()
                        let text
                        let clp = (e.originalEvent || e).clipboardData
                        if (clp === undefined || clp === null) {
                            text = window.clipboardData.getData('text') || ''
                            if (text !== '') {
                                if (window.getSelection) {
                                    let newNode = document.createElement('span')
                                    newNode.innerHTML = text
                                    window
                                        .getSelection()
                                        .getRangeAt(0)
                                        .insertNode(newNode)
                                } else {
                                    document.selection
                                        .createRange()
                                        .pasteHTML(text)
                                }
                            }
                        } else {
                            text = clp.getData('text/plain') || ''
                            if (text !== '') {
                                document.execCommand('insertText', false, text)
                            }
                        }
                    })
            }, 100)
        },
        updateText() {
            let modifiedText = this.$refs.text.childNodes[0].innerHTML
                .replace(/<div><br></div>/g, '\n')
                .replace(/<div.*?>/g, '\n')
                .replace(/<br.*?>/g, '')
                .replace(/</div>|&nbsp;|</?span.*?>/g, '')
            this.isEdit = false
            // 文字内容没修改不update
            if (this.text !== modifiedText) {
                if (modifiedText === '') modifiedText = '双击编辑文字'
                this.text = modifiedText
                this.updateBoxSize()
            }
        },
        updateBoxSize() {
            //更新下父容器的高度
            this.boxInfo.height = document.getElementById(
                'textbox'
            ).offsetHeight
        }
    }
}
</script>

<style lang="less" scoped>
.element-box {
    position: relative;
}

.show-text {
    word-break: break-all;
    white-space: pre-line;
}

.textbox {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;

    .textbox-container {
        width: 100%;
        position: absolute;
        border: 1px dashed #000;
        height: auto;
        word-break: break-word;
        outline: none;
        white-space: pre-wrap;
        box-sizing: content-box;
        -webkit-user-select: text !important;
        user-select: text !important;
        -webkit-background-clip: text;
        caret-color: black;
    }
}
</style>

双击编辑文字的逻辑大概都是这样的,我是用vue写的,其实可以很方便的改成react或者其他框架的语法。

希望本文可以解决一些人的需求。

写文本大约耗费了两局王者荣耀的时间,求一下点赞。

算了,不求赞了,这篇文章受众太小了,估计都没什么人看、就酱紫,写完收工。