Vue 实现contenteditable属性的自定义带样式的textarea输入框

参考: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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值