文本输入框高亮特定内容

959 阅读2分钟

本章主要详细介绍实现特定文本内容高亮和滚动同步的功能。

废物不多说,先直接上效果

高亮特定文本.gif

实现思路

1. 布局和样式

  • 使用 Ant Design Vue 的 <a-row><a-col> 组件来创建响应式布局。
  • <div class="container"> 是主容器,设置了固定的宽度和高度。
  • <div class="text-div"> 用于显示处理后的文本(高亮显示的文本)。
  • <div class="text-wrap"> 包含一个 <a-textarea>,用于用户输入文本。

2. 文本处理

  • 用户在 <a-textarea> 中输入文本,这部分文本通过 Vue 的 v-model 双向绑定到 inputText 变量。

  • 当用户输入文本时,触发 handleChange 方法。这个方法将 inputText 中的文本按行分割,并检查每一行是否包含数字“8”。

    • 如果一行包含“8”,则该行被包裹在一个带有 vin-matching-error 类的 <span> 标签中,这个类定义了高亮样式(红色字体)。
    • 如果一行不包含“8”,则保持原样。
    • 空行被替换为包含空格的 <span>,以保持格式。
  • 处理后的文本(可能包含高亮的行)被赋值给 inputText2,并通过 v-html<div class="text-div"> 中显示。

3. 滚动同步

  • 当用户在 <a-textarea> 中滚动时,触发 handleScroll 方法。
  • handleScroll 方法获取 <a-textarea> 的滚动位置,并将这个滚动位置应用到 <div id="myText"> 上,从而实现两个区域的滚动同步。

4. Vue 生命周期和 DOM 引用

  • 使用 onMounted 钩子函数来获取对 myText(显示处理后文本的 <div>)的引用。这是通过 document.getElementById 实现的。
  • 这个引用在 handleScroll 方法中用于同步滚动位置。

5. CSS 样式

  • .container 和其他类用于设置布局和基本样式。
  • :deep(.vin-matching-error) 用于定义高亮样式。:deep() 是一个 Vue 特有的选择器,用于穿透 scoped 样式的边界,使得定义在组件内部的样式可以应用到动态生成的 HTML 内容上。

Let's show you the code!

<template>
  <div style="padding: 50px">
    <a-row>
      <a-col :span="8">
        <div class="container">
          <div id="myText" class="text-div" v-html="inputText2"></div>
          <div class="text-wrap">
            <a-textarea
              v-model:value="inputText"
              placeholder="请输入批量车架号"
              class="textarea"
              @scroll="handleScroll"
              @input="handleChange"
              @change="handleChange"
            />
          </div>
        </div>
      </a-col>
    </a-row>
  </div>
</template>

<script setup>
import { ref, onMounted } from "vue";

const inputText = ref("");
const inputText2 = ref("");
const myText = ref(null);

const handleChange = () => {
  inputText2.value = inputText.value
    .split("\n")
    .map((item) =>
      item.includes("8")
        ? `<span class="vin-matching-error">${item}</span>`
        : item || "<span>&nbsp;</span>"
    )
    .join("\n");
};

onMounted(() => {
  myText.value = document.getElementById("myText");
});

const handleScroll = (event) => {
  if (myText.value) {
    myText.value.scrollTop = event.target.scrollTop;
  }
};
</script>
<style scoped>
.container {
  width: 400px;
  height: 500px;
  position: relative;
  :deep(.vin-matching-error) {
    color: #ff4d4f;
    position: relative;
    z-index: 10000;
    word-break: break-all;
    font-size: 14px;
    line-height: 1.5715;
  }
}
.text-div {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 4px 11px;
  white-space: pre-wrap;
  color: rgba(0, 0, 0, 0.85);
  font-size: 14px;
  line-height: 1.5715;
  vertical-align: bottom;
  transition: all 0.3s, height 0s;
  border: 1px solid #fff;
  border-radius: 2px;
  overflow: auto;
  text-align: left;
}
.text-wrap {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  position: absolute;
  z-index: 1;
  top: 0;
  left: 0;
}
.textarea {
  height: 500px;
  resize: none;
}
.error {
  color: #ff4d4f;
  position: relative;
  z-index: 10000;
}
</style>