富文本编辑器或者text是没有办法插入一个自定义的html节点,所以最后决定用div的contenteditable属性,使其变成可编辑的状态,控制光标的位置来插入。
一、html
<div
class="ant-input"
id="edit"
ref="url"
contenteditable="true"
placeholder="请输入链接"
@blur="changeUrl"
@paste="HandlePaste"
v-html="url"
></div>
<button @click="onAddItem">添加标签</button>
二、显示placeholder
因为div本身不具备placeholder的属性,所以我们要通过css来显示
.ant-input {
height: 140px; // 设置高度
overflow-y: auto; // 超多的部分出现滚动条
&:empty::before { // placeholder设置
content: attr(placeholder);
font-size: 14px;
color: #CCC;
line-height: 21px;
padding-top: 20px;
}
}
三、逻辑
- 注册全局的光标变化事件,主要记录用户最后选中的光标位置,在该位置插入自定的标签
@Prop() value;
private url: string = '';
private savedRange = null; // 保留当前选择的范围
$refs: {
url: HTMLElement
}
mounted() {
// 注册光标移动事件
document.addEventListener('selectionchange', this.HandleSelectionChange, false);
// 因为是组件,接收数据显示
this.url = this.value;
}
- 保留用户最后光标停留处
// 保留当前选择的范围
HandleSelectionChange() {
let sel = window.getSelection && window.getSelection();
if (sel && sel.rangeCount) {
this.savedRange = sel.getRangeAt(0);
}
}
- 点击按钮添加自定义标签,input标签type是button类型的,这样删除可以整个删掉
// 添加参数到链接中
onAddItem() {
let data = '数据';
let tag = `<input
class="ant-tag ant-tag-blue"
type="button"
value=${data}
/>`;
this.pasteHtmlAtCaret(tag);
}
// 获取光标,插入html
pasteHtmlAtCaret(html) {
let sel, range;
// IE9 and non-IE
if (window.getSelection) {
sel = window.getSelection();
if (sel && sel.rangeCount === 0 && this.savedRange !== null) sel.addRange(this.savedRange); // 保留光标在文字中间插入的最后位置
if (sel && sel.rangeCount) range = sel.getRangeAt(0);
if (['', null, undefined].includes(range)) {
// 如果div没有光标,则在div内容末尾插入
range = this.keepCursorEnd(true).getRangeAt(0);
} else {
const contentRange = document.createRange()
contentRange.selectNode(this.$refs.url)
// 对比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 = this.keepCursorEnd(true).getRangeAt(0);
}
let input = range.createContextualFragment(html);
let lastNode = input.lastChild; // 记录插入input之后的最后节点位置
range.insertNode(input)
if (lastNode) { // 如果有最后的节点
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
} else if (document['selection'] && document['selection'].type !== 'Control') {
// IE < 9
document['selection'].createRange().pasteHTML(html);
}
}
/**
* 将光标重新定位到内容最后
* isReturn 是否要将range实例返回
* */
keepCursorEnd(isReturn) {
// const div = document.getElementById('edit');
if (window.getSelection) {
// ie11 10 9 firefox safari
this.$refs.url.focus();
let sel = window.getSelection(); // 创建range
sel.selectAllChildren(this.$refs.url); // range 选择obj下所有子内容
sel.collapseToEnd(); // 光标移至最后
if (isReturn) return sel;
} else if (document['selection']) {
// ie9以下
let sel = document['selection'].createRange(); // 创建选择对象
sel.moveToElementText(this.$refs.url); // range定位到编辑器
sel.collapse(false);// 光标移至最后
sel.select();
if (isReturn) return sel;
}
}
- 获取数据,发送到父组件保存
changeUrl(e) {
// 去除用户在输入时,主动输入的换行符
const url = e.target.innerHTML.toString().replace(/<br>/g, '')
this.$emit('input', url);
}
- 由于编辑框是可以粘贴从其他地方复制来的数据,但是会带着数据之前的样式,所以我们需要去除粘贴时的格式
// 处理粘贴时消除样式
HandlePaste(e) {
e.stopPropagation();
e.preventDefault();
var text = ''; var event = (e.originalEvent || e);
if (event.clipboardData && event.clipboardData.getData) {
text = event.clipboardData.getData('text/plain');
} else if (window['clipboardData'] && window['clipboardData'].getData) {
text = window['clipboardData'].getData('Text');
}
if (document.queryCommandSupported('insertText')) {
document.execCommand('insertText', false, text);
} else {
document.execCommand('paste', false, text);
}
}
效果图: