输入框特殊单词变色

879 阅读5分钟

输入框内特定单词变色处理。

场景: 将输入框看成是一个命令输入器,该输入器可以对两个特殊命令单词进行变色,如rename与stats,我们将rename与stats称为命令。命令后面可以跟其参数,如rename的参数为as,stats的参数为by;命令必须以 | 字符开头,即 | rename 才会变色,单独输入rename不做改变。一个 | 字符只能匹配一个命令,输入框可以输入多个命令,如| rename ..... | stats ....。

大致效果如下:

思路:

使用两个div输入框,将两个输入框重叠,一个用于输入,一个用于显示。为什么使用两个输入框,一是有利于数据交互,二是可以解决输入框在变色处理后光标错位问题。

输入框hide显示在表层,输入框show显示在里层,输入框hide使用css属性进行隐藏,只在其中进行输入,hide输入框的内容与show输入框进行动态绑定,让输入信息在show输入框显示。

代码如下:

<div class="show-input" contentEditable="plaintext-only" ref={show} tabIndex="-1" />
<div className="hideinput"
     contentEditable="plaintext-only"
     onScroll={onscroll}
     onInput={onInput}
     ref={hide}
/>

css:

.hide-input {
    width: 100%;
    padding: 12px 12px 0 12px;
    height: auto;
    min-height: 39px;
    max-height: 70px;
    position: absolute;
    overflow: auto;
    overflow-x: hidden;
    color: transparent;
    background-color: transparent;
    caret-color: black; // 可能部分浏览器不生效
    box-shadow: 0 0 2px @search-shadow;
  }

  .show-input {
    width: 100%;
    padding: 12px 12px 0 12px;
    height: auto;
    min-height: 39px;
    max-height: 70px;
    position: absolute;
    overflow: auto;
    overflow-x: hidden;
    background-color: white;
    box-shadow: 0 0 2px @search-shadow;
  }

这里再对命令进行说明,比如以字符 | 表示一个需要变色命令的开始,如| rename的rename会变色,但是单独输入rename不变色,as单词在rename变色的情况下才会变色。相当于rename作为命令,as作为参数。

一个 | 字符只能匹配一个命令,如果现在有两个可变色的命令如rename和stats,那么 | rename stats,rename变色后,它之后的stats不在变色,除非使用 | 开启一个新的命令输入,如 | rename | stats。

大致的变色逻辑是这样。

实现过程:

先将输入字符串中的蓝色命令进行正则替换,给其添加变色标签,然后再将字符串按字符 | 分割成一个数组,对数组中每一项进行正则处理,实现相应命令的参数的变色。

  //命令蓝色
  const repCommand = ['rename','stats'];
  
  //命令参数橙色
  const repParamOrange = ['as','by'];
  
  // 多级参数命令正则匹配
  const multilParams = [/rename<\/span>/, /stats<\/span>/];
  
  /**
   * @description 对传入字符串进行正则处理,输出变颜色的字体
   * @param {string} html 需要正则处理的字符串
   */
  const regHandle = html => {
    let temp = html;

    // 对蓝色命令进行正则匹配
    repCommand.forEach(item => {
      temp = temp.replace(
        RegExp(`(\\|)([\\s&nbsp;]*)\\b${item}\\b`, 'g'),
        `$1$2<span class="color-blue">${item}</span>`
      );
    });

    // 把字符串以|隔开放入一个数组中,对数组中每一项进行正则处理,实现相应命令参数变色
    temp = temp.split('|');

    // 对数组每一项进行正则匹配替换颜色
    for (let i = 0; i < temp.length; i++) {
      // 对橙色as命令进行正则匹配
      if (temp[i].match(multilParams[0])) {
        temp[i] = temp[i].replace(
          RegExp(`\\b${repParamOrange[0]}\\b`, 'g'),
          `<span class="color-orange">${repParamOrange[0]}</span>`
        );
      }
      // 对黄色by命令进行正则匹配
      if (temp[i].match(multilParams[1])) {
        temp[i] = temp[i].replace(
          RegExp(`\\b${repParamOrange[1]}\\b`, 'g'),
          `<span class="color-orange">${repParamOrange[1]}</span>`
        );
      }
    }

    // 把数组转换成字符串输出
    return temp.join('|');
  };

该函数已经将需要变色的特殊单词做上了标记,将其以span标签包裹。

现在只需要在输入信息的时候进行动态绑定即可:

const oninput = () => {
    // 对输入字符串进行颜色转换
    show.current.innerHTML = regHandle(hide.current.innerHTML);
}

为什么让两个输入框在滚动和滚动条出现的时候信息同步,需要将两个输入框的scroll同步,大致效果如下:

 /**
  * @description 保持滑轮同步
  */
  const onscroll = () => {
    show.current.scrollTop = hide.current.scrollTop;
  };

功能开发完成,完整代码如下:

import React, { useRef } from 'react';
import './index.less';

 const InputChangeColor = () => {
 //命令蓝色
  const repCommand = ['rename','stats'];
  
  //命令参数橙色
  const repParamOrange = ['as','by'];
  
  // 多级参数命令正则匹配
  const multilParams = [/rename<\/span>/, /stats<\/span>/];

  // 绑定节点
  const hide = useRef(); // 隐藏的输入框的节点
  const show = useRef(); // 显示的输入框的节点

  /**
   * @description 对传入字符串进行正则处理,输出变颜色的字体
   * @param {string} html 需要正则处理的字符串
   */
  const regHandle = html => {
    let temp = html;

    // 对蓝色命令进行正则匹配
    repCommand.forEach(item => {
      temp = temp.replace(
        RegExp(`(\\|)([\\s&nbsp;]*)\\b${item}\\b`, 'g'),
        `$1$2<span class="color-blue">${item}</span>`
      );
    });

    // 把字符串以|隔开放入一个数组中,对数组中每一项进行正则处理,实现相应命令参数变色
    temp = temp.split('|');

    // 对数组每一项进行正则匹配替换颜色
    for (let i = 0; i < temp.length; i++) {
      // 对橙色as命令进行正则匹配
      if (temp[i].match(multilParams[0])) {
        temp[i] = temp[i].replace(
          RegExp(`\\b${repParamOrange[0]}\\b`, 'g'),
          `<span class="color-orange">${repParamOrange[0]}</span>`
        );
      }
      // 对黄色by命令进行正则匹配
      if (temp[i].match(multilParams[1])) {
        temp[i] = temp[i].replace(
          RegExp(`\\b${repParamOrange[1]}\\b`, 'g'),
          `<span class="color-orange">${repParamOrange[1]}</span>`
        );
      }
    }

    // 把数组转换成字符串输出
    return temp.join('|');
  };

  /**
   * @description 保持滑轮同步
   */
  const onscroll = () => {
    show.current.scrollTop = hide.current.scrollTop;
  };

  /**
   * @description 监听输入框动态绑定
   */
  const oninput = () => {
    // 对输入字符串进行颜色转换
    show.current.innerHTML = regHandle(hide.current.innerHTML);
}

  return (
    <div className="search">
      <div className="show-input" contentEditable="plaintext-only" ref={show} tabIndex="-1" />

      <div
        className="hide-input"
        contentEditable="plaintext-only"
        onScroll={onscroll}
        onInput={oninput}
        ref={hide}
      />
  );
};

export default InputChangeColor;

css:

.search {
  position: relative;
  width: 100%;
  height: 40px;

  .hide-input {
    width: 100%;
    padding: 12px 12px 0 12px;
    height: auto;
    min-height: 39px;
    max-height: 70px;
    position: absolute;
    overflow: auto;
    overflow-x: hidden;
    color: transparent;
    background-color: transparent;
    caret-color: black;
    box-shadow: 0 0 2px @search-shadow;
  }

  .show-input {
    width: 100%;
    padding: 12px 12px 0 12px;
    height: auto;
    min-height: 39px;
    max-height: 70px;
    position: absolute;
    overflow: auto;
    overflow-x: hidden;
    background-color:white;
    box-shadow: 0 0 2px @search-shadow;
  }

  .color-blue {
    color: blue;
  }

  .color-orange {
    color: orange;
  }
}