有如下业务需求
在输入框中,允许插入其他标签元素。这在普通的 input 元素上是没法实现的,这里我们可以利用元素的 contenteditable
属性来实现此类效果。
基础样式代码:
<style>
body {
padding: 100px;
}
#input {
border: 1px solid #ccc;
}
#input:focus {
outline: 0;
border-color: #409eff;
}
#input:empty:before {
content: attr(placeholder);
color: #cccccc;
}
#tip {
color: #409eff;
/* user-select: none; */
}
#btn {
margin-top: 30px;
user-select: none;
display: inline-block;
}
</style>
html 和 js:
<body>
<div id="input" contenteditable="true" placeholder="请输入">
哇哈哈哈哈哈<span id="tip" contenteditable="false">#标签#</span>
</div>
<input id="btn" type="button" value="添加标签" contenteditable="false" />
<script>
let inputId = '';
// 不使用click的原因是:点击按钮后会导致光标丢失。事件执行顺序: mousedown -> blur(输入框的事件) -> mouseup -> click
document.getElementById('btn').addEventListener('mousedown', mousedown);
document.getElementById('btn').addEventListener('mouseup', () => {
console.log('mouseup');
});
document.getElementById('btn').addEventListener('click', () => {
console.log('click');
});
function mousedown(e) {
e.preventDefault(); // 防止光标消失, 阻止执行输入框的 blur 事件
if (inputId !== 'input') {
// 只有聚焦的元素是input时才执行
return;
}
const selection = window.getSelection();
console.log('mousedown', selection);
// 没有光标或选区时 getRangeAt(0) 会报错
let range;
try {
range = selection.getRangeAt(0);
} catch (e) {}
if (range) {
const htmlStr = `<span id="tip" contenteditable="false">#标签#</span>`;
const node = range.createContextualFragment(htmlStr); // 将 HTML 字符串转换为文档片段
range.deleteContents(); // 删除选中的内容
range.insertNode(node); // 插入文本
range.collapse(false); // 焦点移动到插入文本后
range.detach(); // 释放range
}
}
document.getElementById('input').addEventListener('blur', blur);
// 自动匹配文本中的内容并转换为span标签
function blur(e) {
inputId = '';
const text = e.srcElement.innerText || '';
console.log('blur', text);
const ruleString = `(?<!<span id=\"tip\" contenteditable=\"false\">)(#标签#)(?!<\/span>)`;
const pattern = new RegExp(ruleString, 'g');
const newString = text.replace(pattern, '<span id="tip" contenteditable="false">$1</span>');
e.srcElement.innerHTML = newString;
}
document.getElementById('input').addEventListener('click', (e) => {
inputId = e.target.id;
});
</script>
</body>
其中需要特别注意的几点:
- 用
inputId
记录当前聚焦的元素,判断是否是需要插入标签的元素 - 允许用户手动输入
#标签#
,失去焦点后自动回显
涉及到的一些 api 可以去 MDN 上了解: