携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天
因为业务的驱使,需要做一个可编辑的文字组件,即点击就可以编辑,由于之前的同事做的点击之后切换成input
的效果看起来不太满意,所以这边我用div
的contenteditable
属性对组件进行重构
无焦点状态
焦点状态
重构过程
到div在contenteditable
模式下,内容是html文档而不是纯文字数据,所以进行回显的时候需要使用v-html
对保存的内容进行插入
<div
ref="editorRef"
contenteditable
v-html="title"
/>
只利用div的blur
进行数据的修改,因为是实时对数据进行修改的话会导致组件的重复渲染,出现光标错位的问题
<div
ref="editorRef"
contenteditable
v-html="title"
@blur="handleTitleChange"
/>
<script setup>
const emit = defineEmits(['update:title'])
const handleTitleChange = (e) => {
context.emit('update:title', e.target.innerHTML)
}
</script>
就这样子很简单,其实就完成了一个div的编辑框封装,外部使用方式就是引进组件,然后通过v-model:title
来绑定要显示的数据
遇到的棘手问题
输入框的paste
事件问题,及复制事件,因为contenteditable
模式是html文档内容,复制其他内容过来的时候也会把样式一起复制过来,就会出现内容样式不一致的问题
window.addEventListener('paste', pasteFn)
const pasteFn = (e) => {
// 先阻止默认的复制事件
e.preventDefault()
// 获取复制板的内容
const paste = (e.clipboardData || window.clipboardData).getData('text/plain')
// 新建元素标签
var newNode = document.createElement('span')
newNode.innerHTML = paste
// 获取当前光标位置,插入元素
window.getSelection().getRangeAt(0).insertNode(newNode)
}
通过监听paste
的事件对事件进行重写,只复制内容,不复制样式,就可以避免复制出现的问题
最后附送组件内代码---vue3版本
<template>
<div :class="['title-warp', `${model}-question`]" :style="{width: `${width}px`}">
<div class="quill">
<div class="q-container">
<div
:id="keyName"
ref="editorRef"
:tabindex="0"
class="q-editor"
:style="{fontSize: `${fontSize}px !important`}"
:contenteditable="model === 'edit'"
@focus="handleFocus"
@blur="handleTitleChange"
v-html="title"
/>
</div>
</div>
</div>
</template>
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
props: {
title: {
type: String,
default: ''
},
fontSize: {
type: [String, Number],
default: 14
},
width: {
type: [String, Number],
default: ''
},
keyName: {
type: String,
default: ''
},
model: {
type: String,
default: 'edit'
}
},
setup(props, context) {
const editorRef = ref()
const handleTitleChange = (e) => {
context.emit('update:title', e.target.innerHTML)
window.removeEventListener('paste', pasteFn)
}
const handleFocus = () => {
window.addEventListener('paste', pasteFn)
}
const pasteFn = (e) => {
e.preventDefault()
const paste = (e.clipboardData || window.clipboardData).getData('text/plain')
var newNode = document.createElement('span')
newNode.innerHTML = paste
window.getSelection().getRangeAt(0).insertNode(newNode)
}
return { editorRef, handleTitleChange, handleFocus }
}
})
</script>
<style lang="scss" scoped>
.q-editor {
box-sizing: border-box;
line-height: 1.42;
height: 100%;
outline: none;
overflow-y: auto;
tab-size: 4;
-moz-tab-size: 4;
padding: 5px 8px;
text-align: left;
white-space: pre-wrap;
word-wrap: break-word;
border: 1px solid transparent;
span {
font-size: 0 !important;
}
}
.edit-question {
.q-editor {
&:focus {
outline: 0px;
border: 1px solid rgb(24, 144, 255) !important;
}
&:hover {
border: 1px dashed #aaaaaa;
transition: all .5s;
}
}
}
.title-warp {
box-sizing: border-box;
margin: 0px;
font-variant: tabular-nums;
list-style: none;
font-feature-settings: "tnum";
width: 100%;
color: rgba(0, 0, 0, 0.65);
border: 1px solid transparent;
transition: all 0.3s ease 0s;
.quill {
text-align: center;
.q-container {
box-sizing: border-box;
font-family: Helvetica,Arial,sans-serif;
font-size: 13px;
height: 100%;
margin: 0;
position: relative;
}
}
}
</style>
关于作者
一个工作三年,摆烂躺平的前端攻城狮~~~🦁