react contentEditable 可輸入div 高亮關(guān)鍵字

/*
 * 開發(fā)調(diào)試
 */
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import styles from './style.module.less';

const keyword = ['const'];
const replaceBlank = (html) => html.replace(/ /ig, ' ');

// 獲取光標(biāo)位置
function getCaretCharacterOffsetWithin(element) {
  let caretOffset = 0;
  const doc = element.ownerDocument || element.document;
  const win = doc.defaultView || doc.parentWindow;
  let sel;
  if (typeof win.getSelection !== 'undefined') {
    sel = win.getSelection();
    if (sel.rangeCount > 0) {
      const range = win.getSelection().getRangeAt(0);
      const preCaretRange = range.cloneRange();
      preCaretRange.selectNodeContents(element);
      preCaretRange.setEnd(range.endContainer, range.endOffset);
      caretOffset = preCaretRange.toString().length;
    }
  } else if ((sel = doc.selection) && sel.type != 'Control') {
    const textRange = sel.createRange();
    const preCaretTextRange = doc.body.createTextRange();
    preCaretTextRange.moveToElementText(element);
    preCaretTextRange.setEndPoint('EndToEnd', textRange);
    caretOffset = preCaretTextRange.text.length;
  }
  return caretOffset;
}

// 設(shè)置光標(biāo)位置
function setCaretPosition(element, offset) {
  const range = document.createRange();
  const sel = window.getSelection();

  // select appropriate node
  let currentNode = null;
  let previousNode = null;

  for (let i = 0; i < element.childNodes.length; i++) {
    // save previous node
    previousNode = currentNode;

    // get current node
    currentNode = element.childNodes[i];
    // if we get span or something else then we should get child node
    while (currentNode.childNodes.length > 0) {
      [currentNode] = currentNode.childNodes;
    }

    // calc offset in current node
    if (previousNode != null) {
      offset -= previousNode.length;
    }
    // check whether current node has enough length
    if (offset <= currentNode.length) {
      break;
    }
  }
  // move caret to specified offset
  if (currentNode != null) {
    range.setStart(currentNode, offset);
    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
  }
}

// 排序方法
function compareWordLength(a, b) {
  if (a.length > b.length) {
    return -1;
  } if (a.length < b.length) {
    return 1;
  }
  return 0;
}

// 高亮關(guān)鍵字
function addKeyWordHighline(oText, keyWords) {
  let returnVal = oText;
  let i = 0;
  let wordReg;
  keyWords.sort(compareWordLength);

  for (i = 0; i < keyWords.length; i++) {
    if (keyWords[i] !== '') {
      wordReg = new RegExp(`(?!<span+>.[^<]*)${keyWords[i]}(?!.[^<]*<\/span>)`, 'g');
      returnVal = returnVal.replace(wordReg, `<span>${keyWords[i]}</span>`);
    }
  }
  return returnVal;
}

const CodeInput = ({
  value,
  disable,
  children,
  onChange,
}) => {
  const ref = useRef();
  // 是否鎖定輸入
  const isLock = useRef(false);

  const getRef = () => ref && ref.current;

  const onCompositionstart = (e) => {
    isLock.current = true;
  };
  const onCompositionend = (e) => {
    isLock.current = false;
  };

  // 解決中文輸入的時(shí)候腐晾,直接輸出英文字母的問題(中文輸入期間存谎,不允許輸入字符)
  useEffect(() => {
    // 監(jiān)聽中文輸入
    const el = getRef();
    el.addEventListener('compositionstart', onCompositionstart, false);
    el.addEventListener('compositionend', onCompositionend, false);
    return () => {
      el.removeEventListener('compositionstart', onCompositionstart, false);
      el.removeEventListener('compositionend', onCompositionend, false);
    };
  }, []);

  const onInput = () => {
    const el = getRef();
    // dom是否為空 || 是否為鎖定模式
    if (!el || isLock.current) return;
    // 獲取內(nèi)容
    let text = el.innerHTML;
    // 是否修改了
    if (value !== text) {
      // 獲取光標(biāo)
      const position = getCaretCharacterOffsetWithin(el);
      // 替換空格
      text = replaceBlank(text);
      // 替換關(guān)鍵字
      text = addKeyWordHighline(text, keyword);
      el.innerHTML = text;
      // 更新父組件
      onChange(text);
      // 恢復(fù)位置
      setCaretPosition(el, position);
    }
  };

  return (
    <pre
      ref={ref}
      className={styles['formula-input']}
      contentEditable={!disable}
      dangerouslySetInnerHTML={{ __html: value }}
      onInput={onInput}
    >

      {children}
    </pre>
  );
};

CodeInput.propTypes = {
  value: PropTypes.array,
  disable: PropTypes.number,
  onChange: PropTypes.func,
  children: PropTypes.node,
};

CodeInput.defaultProps = {
  value: '默認(rèn)',   // value
  disable: false,   // 是否可用
  children: undefined, // 子元素
  onChange: () => {},
};

export default CodeInput;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末膝擂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子引润,更是在濱河造成了極大的恐慌惧盹,老刑警劉巖酝润,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肿孵,死亡現(xiàn)場離奇詭異碴犬,居然都是意外死亡践磅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門含末,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猜拾,“玉大人,你說我怎么就攤上這事答渔」卮” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宋雏。 經(jīng)常有香客問我芜飘,道長,這世上最難降的妖魔是什么磨总? 我笑而不...
    開封第一講書人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任嗦明,我火速辦了婚禮,結(jié)果婚禮上蚪燕,老公的妹妹穿的比我還像新娘娶牌。我一直安慰自己,他們只是感情好馆纳,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開白布诗良。 她就那樣靜靜地躺著,像睡著了一般鲁驶。 火紅的嫁衣襯著肌膚如雪鉴裹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評(píng)論 1 290
  • 那天钥弯,我揣著相機(jī)與錄音径荔,去河邊找鬼。 笑死脆霎,一個(gè)胖子當(dāng)著我的面吹牛总处,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播睛蛛,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼鹦马,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了玖院?” 一聲冷哼從身側(cè)響起菠红,我...
    開封第一講書人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤第岖,失蹤者是張志新(化名)和其女友劉穎难菌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蔑滓,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡郊酒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了键袱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片燎窘。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蹄咖,靈堂內(nèi)的尸體忽然破棺而出褐健,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布蚜迅,位于F島的核電站舵匾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谁不。R本人自食惡果不足惜坐梯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望刹帕。 院中可真熱鬧吵血,春花似錦、人聲如沸偷溺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挫掏。三九已至晕翠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間砍濒,已是汗流浹背淋肾。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留爸邢,地道東北人樊卓。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像杠河,于是被迫代替她去往敵國和親碌尔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容