参考:vue实现带样式的textarea输入框,contenteditable属性应用
感谢参考文章的作者,本文是在以上基础上进行完善的!
具体功能:自定义的textarea输入框,使用contenteditable属性和v-html结合,实现输入框内文字可带样式,如内部带tags的输入框,高亮带颜色背景的标签文字等。
template部分:
<template>
<div class="textarea_box">
<div
id="textarea"
class="textarea"
contenteditable="true"
:placeholder="tipStr"
@focus="focusTextarea"
@input="inputTxt"
@blur="blurTxt"
@keyup.delete.native="deleteTxt"
@keydown="handleKeyDown"
@paste="handlePaste"
v-html="txtHtml"
></div>
</div>
</template>
contenteditable="true"即是实现textarea的关键,结合v-html,即可实现自定义内容样式的textarea。
但要实现回车发送问题和shift+回车换行等还需绑定键盘事件进行处理。
额外一提,若只想实现textarea仅输入文字功能,可使用contenteditable=“plaintext-only”,但兼容性不支持火狐IE等浏览器,会出现点击无法显示光标并输入的问题。
兼容性请看下图:
js部分:
<script>
export default {
name: "TextareaComponent",
computed: {},
props: {
// 外部传入的需带样式的文本
data: {
type: String,
default: () => (""),
},
},
data() {
return {
tipStr: "请输入",
txtData: [],
txtHtml: "",
txtStr: "",
txtTitle: "",
};
},
// 监听外部传入数据的变动,有则转换html样式内容
watch: {
data: {
handler(newVal) {
this.txtHtml = "";
this.txtStr = "";
this.txtTitle = "";
this.txtData = [];
if (newVal) {
this.createInput(newVal);
} else {
this.txtHtml = "";
this.txtStr = "";
this.txtTitle = "";
this.txtData = [];
let divElement = document.getElementById("textarea");
try {
if (divElement.innerText) {
divElement.innerText = "";
}
} catch (err) {
console.log(err);
}
}
},
immediate: true,
deep: true,
},
},
created() {},
methods: {
// 处理粘贴事件
handlePaste(e) {
e.stopPropagation(); // 停止浏览器自带的粘贴
e.preventDefault();
let text = "",
event = e.originalEvent || e;
// 获取粘贴板的内容,仅获取文本内容,去掉其他图片等html
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);
}
},
handleKeyDown(e) {
// 当shift+Enter时增加换行
if ((e.keyCode === 13 || e.key == "Enter") && e.shiftKey) {
if (this.txtStr) {
this.txtStr += "\n";
}
} else if (e.keyCode === 13 || e.key === "Enter") {
// 当Enter时更新并且发送输入内容
e.target.innerText = e.target.innerText;
this.handleEnter(e);
e.preventDefault();
}
},
//textarea输入
inputTxt(e) {
let str = e.target.innerText ? e.target.innerText.trim() : "";
if (str.length >= 1) {
this.txtStr = e.target.innerText;
this.createInputHtml();
}
},
// enter发送问题
handleEnter(e) {
this.$emit("send", this.txtStr);
// 清除输入框内容
e.target.innerText = "";
this.txtStr = "";
this.txtHtml = "";
},
//textarea删除
deleteTxt(e) {
// 清空输入框有时会留一个\n,因此要去掉才会恢复placeholder
if (e.target.innerText == "\n") {
e.target.innerText = "";
}
},
//主动使textarea获取焦点
focusTextarea() {
let node = document.getElementById("textarea");
if (window.getSelection) {
let range = window.getSelection();
range.selectAllChildren(node);
range.collapseToEnd();
} else {
let range = document.selection.createRange();
range.moveToElementText(node);
range.collapse(false);
range.select();
}
},
//失去焦点
blurTxt(e) {
// 同上,仅剩\n时清空输入框
if (e.target.innerText == "\n") {
e.target.innerText = "";
}
},
// 外部带样式文字输入时,创建输入样式
createInput(newVal, colorType, splitStr) {
//修改输入内容,带样式内容用{}包裹,用正则匹配替换带样式的span标签
this.txtStr = newVal.content.replace(/[{]/g, "").replace(/[}]/g, "");
this.txtTitle = newVal.title;
let newData = [];
newData.push(newVal.title, newVal.content);
// 变量添加样式
const newContent = newVal.content
.replace(/[{]/g, `<span class="tag">{`)
.replace(/[}]/g, `}</span>`);
this.$nextTick(() => {
this.txtHtml = newContent;
});
//触发父组件input改变事件
this.$emit("changeInput", this.txtStr);
},
// 键盘输入时,创建输入内容
createInputHtml(str, colorType, splitStr) {
if (this.txtStr == "") {
this.$emit("clearInput"); // 清空输入
} else {
const newVal = this.txtStr.replace(/[{]/g, "").replace(/[}]/g, "");
//触发父组件input改变事件
this.$emit("changeInput", newVal);
}
},
//删除字符串中的空格
deleteSpace(str) {
let data = [];
if (str) {
data = str.split(" ");
} else {
data = this.txtStr.split(" ");
}
let newStr = "";
for (let i = 0; i < data.length; i++) {
if (data[i] != "") {
newStr += data[i];
}
}
return newStr;
},
},
};
</script>
<style lang="less" scoped>
.textarea_box {
display: flex;
width: 100%;
height: 100%;
overflow: auto;
padding: 16px 20px;
}
.textarea {
width: 100%;
height: 100%;
white-space: pre-wrap;
overflow-y: auto;
outline: none;
color: #000;
font-size: 14px;
cursor: text;
text-overflow: ellipsis;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}
// 为空时展示placeholder
.textarea[contenteditable]:empty:before {
color: var#808080;
font-size: 14px;
content: attr(placeholder);
}
// 获取焦点后移除placeholder
.textarea[contenteditable]:focus:before {
content: none;
}
:deep(.content) {
max-width: calc(100% - 100px);
color: #000;
font-size: 14px;
}
:deep(.tag) {
color: pink;
}
.textarea::-webkit-scrollbar-thumb {
display: block;
cursor: pointer;
}
</style>