Div 模拟 Textarea
使用 contenteditable 属性实现可编辑的文本输入框
问题
使用 div 模拟 textarea 实现,支持 placeholder、自适应高度等功能。
解答
基础实现
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Div 模拟 Textarea</title>
<style>
.fake-textarea {
min-height: 100px;
max-height: 300px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
overflow-y: auto;
outline: none;
line-height: 1.5;
word-break: break-all;
}
.fake-textarea:focus {
border-color: #409eff;
}
/* placeholder 样式 */
.fake-textarea:empty::before {
content: attr(data-placeholder);
color: #999;
}
/* 聚焦时隐藏 placeholder */
.fake-textarea:focus::before {
content: none;
}
</style>
</head>
<body>
<div
class="fake-textarea"
contenteditable="true"
data-placeholder="请输入内容..."
></div>
<script>
const editor = document.querySelector('.fake-textarea');
// 获取纯文本内容
function getText() {
return editor.innerText;
}
// 设置内容
function setText(value) {
editor.innerText = value;
}
// 监听输入
editor.addEventListener('input', () => {
console.log('内容:', getText());
});
// 阻止粘贴富文本,只保留纯文本
editor.addEventListener('paste', (e) => {
e.preventDefault();
const text = e.clipboardData.getData('text/plain');
document.execCommand('insertText', false, text);
});
</script>
</body>
</html>
封装成组件
class FakeTextarea {
constructor(container, options = {}) {
this.options = {
placeholder: '请输入...',
maxLength: Infinity,
onChange: null,
...options
};
this.el = document.createElement('div');
this.el.className = 'fake-textarea';
this.el.contentEditable = true;
this.el.dataset.placeholder = this.options.placeholder;
container.appendChild(this.el);
this.bindEvents();
}
bindEvents() {
// 输入事件
this.el.addEventListener('input', () => {
// 限制最大长度
if (this.getText().length > this.options.maxLength) {
this.setText(this.getText().slice(0, this.options.maxLength));
this.moveCursorToEnd();
}
this.options.onChange?.(this.getText());
});
// 粘贴纯文本
this.el.addEventListener('paste', (e) => {
e.preventDefault();
const text = e.clipboardData.getData('text/plain');
document.execCommand('insertText', false, text);
});
}
// 获取内容
getText() {
return this.el.innerText;
}
// 设置内容
setText(value) {
this.el.innerText = value;
}
// 清空内容
clear() {
this.el.innerHTML = '';
}
// 聚焦
focus() {
this.el.focus();
this.moveCursorToEnd();
}
// 光标移到末尾
moveCursorToEnd() {
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(this.el);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
}
}
// 使用
const textarea = new FakeTextarea(document.body, {
placeholder: '请输入内容...',
maxLength: 200,
onChange: (text) => console.log(text)
});
处理换行问题
// 不同浏览器 contenteditable 换行行为不一致
// 统一使用 <br> 换行
editor.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
document.execCommand('insertLineBreak');
}
});
// 获取内容时处理换行
function getText() {
// 将 <br> 和 <div> 转换为换行符
return editor.innerHTML
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<\/div><div>/gi, '\n')
.replace(/<\/?div>/gi, '')
.replace(/ /g, ' ')
.trim();
}
关键点
contenteditable="true"使元素可编辑- 使用
:empty::before伪元素实现 placeholder - 粘贴时用
e.clipboardData.getData('text/plain')获取纯文本 innerText获取纯文本,innerHTML获取带标签内容- 不同浏览器换行行为不同,需要统一处理
目录