实现输入框内的部分文字变色

今天公司有个需求,要求多行文本输入框内的文字,#号开头,空格结尾的部分,颜色要变成蓝色,其他部分的文字颜色不变

实现下图的效果


image.png

最终代码-vue3可直接使用

<!-- 调用的地方 -->
<template>
  <div>
    <color-input v-model="str" />
    <div>{{ str }}</div>
  </div>
</template>

<script setup>
import colorInput from "@/components/colorInput.vue";
import { ref } from "vue";
const str = ref("111 #112 333");
</script>
<!-- 组件 -->
<template>
  <div
    class="colorInput"
    ref="htmlDom"
    @compositionstart="isComposing = true"
    @compositionend="handleCompositionEnd"
    @input="textChange"
    v-html="htmlStr"
    contenteditable="true"
    :style="{ width, height }"
  ></div>
</template>

<script setup>
import { ref, nextTick, defineEmits, defineProps } from "vue";

const htmlStr = ref("");
const htmlDom = ref(null);
const isComposing = ref(false);
const props = defineProps({
  modelValue: {
    type: String,
    default: "",
  },
  width: {
    type: String,
    default: "100%",
  },
  height: {
    type: String,
    default: "32px",
  },
});
const emit = defineEmits(["update:modelValue"]);
console.log(props);

if (props.modelValue) setText(props.modelValue);

function textChange(e) {
  if (!isComposing.value) {
    setText(e.target.innerText);
  }
}

function setText(str) {
  htmlStr.value = setTxtColor(str);
  nextTick(() => {
    emit("update:modelValue", str);
    placeCaretAtEnd(htmlDom.value);
  });
}

// 正则匹配需要变色的文案
function setTxtColor(text) {
  return text.replace(
    /#[^\s#]+/g,
    (match) => `<span style="color:#5290ff">${match}</span>`
  );
}

// 这里是输入中文的时候会触发两次input,在这里进行限制
function handleCompositionEnd(event) {
  isComposing.value = false;
  // 手动触发一次处理,因为compositionend后可能不会触发input
  textChange(event);
}

// 辅助光标到输入框的最后一位
function placeCaretAtEnd(el) {
  el.focus();
  if (
    typeof window.getSelection !== "undefined" &&
    typeof document.createRange !== "undefined"
  ) {
    const range = document.createRange();
    range.selectNodeContents(el);
    range.collapse(false);
    const sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
  }
}
</script>

<style lang="less" scoped>
.colorInput {
  white-space: pre-wrap;
  word-wrap: break-word;
  box-sizing: border-box;
  margin: 0;
  font-variant: tabular-nums;
  list-style: none;
  font-feature-settings: "tnum";
  position: relative;
  display: inline-block;
  padding: 4px 11px;
  font-size: 14px;
  line-height: 1.5;
  background-color: #fff;
  background-image: none;
  border: 1px solid #d9d9d9;
  border-radius: 4px;
  transition: all 0.3s;
}
</style>

具体实现思路

  1. 因为textarea内部是纯文本,没有办法变色,所以最初的想法是给div添加contenteditable="true",这样可以让div变成可以输入的盒子,模拟一个textarea,然后我想着在上面放上v-model
<div contenteditable="true" v-model='str'></div>
<!-- 但是这样失败了,因为v-model只能绑定到input这些表单的dom上 -->
  1. 然后我想着把v-model拆开,分成一个input事件,和一个v-html(因为是div不是input。所以不能:value)
  <div
    class="colorInput"
    @input="textChange"
    v-html="htmlStr"
    contenteditable="true"
  ></div>
const htmlStr = ref("");
function textChange(e) {
    setText(e.target.innerText);
}

function setText(str) {
  htmlStr.value = setTxtColor(str);
}

function setTxtColor(text) {
  return text.replace(
    /#[^\s#]+/g,
    (match) => `<span style="color:#5290ff">${match}</span>`
  );
}

在触发input的时候,对文字进行处理,用setTxtColor这个正则匹配,然后,修改文字颜色,再通过v-html展示到页面上

  1. 又遇到问题,每次修改之后会通过v-html更新到div里面,这时候鼠标会自动聚焦到输入内容的句首,而不是句尾
function placeCaretAtEnd(el) {
  el.focus();
  if (
    typeof window.getSelection !== "undefined" &&
    typeof document.createRange !== "undefined"
  ) {
    const range = document.createRange();
    range.selectNodeContents(el);
    range.collapse(false);
    const sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
  }
}

所以就有这个函数,来将光标定位到元素末尾的函数

  1. 但是后来又发现问题了,输入中文部分符号(,。《》?¥)的时候,会触发两次input事件

这个和输入法有关,不同的输入法,对于标点符号输入事件序列也不太一样

// 这里是输入中文的时候会触发两次input,在这里进行限制
function handleCompositionEnd(event) {
  isComposing.value = false;
  // 手动触发一次处理,因为compositionend后可能不会触发input
  textChange(event);
}

这里通过一个变量来处理,只触发一次修改

然后对代码封装一下,就结束了,功能完成

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容