需求 :在textArea输入框插入一段带样式的文本
之前遇到一个场景,想了下这不就是简易版的富文本吗,用富文本库可以实现但是太大材小用了,还是用css 实现可编辑div + JS Range 对象定位光标实现即可
关于css -webkit-user-modify
这是个css属性,通过对应属性的设置也可以达到同样的效果。
read-only: 默认值,元素只读,不可编辑;
read-write: 可以编辑,支持富文本;
read-write-plaintext-only: 可以编辑,不支持富文本;
write-only: 使元素仅用于编辑(几乎没有浏览器支持)
由于还需根据光标的位置插入标签,所以我们还需要这么一个对象Range。
可以用 Document 对象的 Document.createRange 方法创建 Range,也可以用 Selection 对象的 getRangeAt 方法获取 Range。另外,还可以通过 Document 对象的构造函数 Range() 来得到 Range。我在实现的时候通过的是selection对象(表示用户选择的文本范围或插入符号的当前位置)的方法创建的,通过监听selectionchange事件来响应式的更新我的range,这样我就可以定位到光标的位置,那么对于标签插在哪的问题就解决了。
效果图
代码实现如下(方案一)
<template>
<div
@mouseenter="mouseenter"
@mouseleave="mouseleave"
class="myTextArea"
id="edit"
ref="innerRuleRef"
cols="30"
rows="10"
></div>
<div>
<Button @click="addfactor"> 插入元素 {{ isEnter }}</Button></div
>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Button } from '/@@/Button';
const Range = ref<any>(null);
const selecthandler = () => {
if (isEnter.value) {
const sel = window.getSelection();
Range.value = sel ? (sel.rangeCount > 0 ? sel?.getRangeAt(0) : null) : null;
}
};
const isEnter = ref(false);
const addTag = (text: string) => {
const node = document.createElement('wise');
node.innerText = text;
// const cancelNode = document.createElement('span');
// cancelNode.innerText = '✕';
// cancelNode.style.color = 'black';
// node.append(cancelNode);
// document.execCommand('insertHTML', false, '<div style="color:red"> test </div>');
Range.value?.insertNode(node);
// insertNode(node);
};
const mouseleave = () => {
isEnter.value = false;
};
const mouseenter = () => {
isEnter.value = true;
};
document.addEventListener('selectionchange', selecthandler);
// 添加因子
const addfactor = () => {
addTag('test');
};
</script>
<style>
.myTextArea {
-webkit-user-modify: read-write-plaintext-only !important;
border: 1px solid #ccc;
overflow: hidden;
box-sizing: border-box;
word-break: break-word;
height: 200px;
width: 200px;
}
wise {
background-color: #f0f6fe;
background-color: #5387f7;
padding: 0 1px;
border-radius: 2px;
/* white-space: nowrap; */
cursor: default;
-webkit-user-modify: read-only !important;
margin-left: 8px;
}
</style>
以上我们已经可以简易实现我们需求了 ,我们通过isEnter 来判断鼠标是否进入 可编辑输入框区域,如果不加这层判断,那么鼠标点到按钮插入元素时,其实光标也定位到按钮这里了 就会在按钮处插入标签,这不是我们想要的,但是用@mouseenter="mouseenter" @mouseleave="mouseleave" 来判断不太优雅,我们开源用Range 自带的属性来判断光标是否进入可编辑区域
代码实现如下(方案二)
/** 获取光标,插入html */
<template>
<div
@mouseenter="mouseenter"
@mouseleave="mouseleave"
class="myTextArea"
id="edit"
ref="innerRuleRef"
cols="30"
rows="10"
></div>
<div>
<Button @click="addfactor"> 插入元素 {{ isEnter }}</Button></div
>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Button } from '/@@/Button';
const addTag = (text: string) => {
const node = document.createElement('wise');
node.innerText = text;
Range.value?.insertNode(node);
pasteHtmlAtCaret(node)
};
const addfactor = () => {
addTag('test');
};
pasteHtmlAtCaret = (html: any) => {
const sel = window.getSelection();
if (!sel) return;
let range: any;
if (sel && sel.rangeCount) {
range = sel.getRangeAt(0);
}
// debugger;
if (['', null, undefined].includes(range)) {
// 如果div没有光标,则在div内容末尾插入
range = keepCursorEnd(true)?.getRangeAt(0);
} else {
const targetNode = document.getElementById('edit');
const contentRange = document.createRange();
contentRange.selectNode(targetNode as HTMLElement);
// 对比range,检查光标是否在输入范围内
const compareStart = range.compareBoundaryPoints(Range.START_TO_START, contentRange);
const compareEnd = range.compareBoundaryPoints(Range.END_TO_END, contentRange);
const compare = compareStart !== -1 && compareEnd !== 1;
if (!compare) range = keepCursorEnd(true)?.getRangeAt(0);
}
console.log(range);
// debugger;
// 末尾添加空格
// html += ' ';
const input = range.createContextualFragment(html);
// 记录插入input之后的最后节点位置
const lastNode = input.lastChild;
range.insertNode(input);
// 如果有最后的节点
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
};
/** 将光标重新定位到内容最后 */
keepCursorEnd = (isReturn: any) => {
const targetNode = document.getElementById('edit');
targetNode?.focus();
// 创建range
const sel = window.getSelection() as Selection;
// range 选择obj下所有子内容
sel.selectAllChildren(targetNode as HTMLElement);
// 光标移至最后
sel.collapseToEnd();
if (isReturn) return sel;
};
</script>