前端 - 用div仿输入框,解决鼠标点击位置错乱的问题

由于项目需要在输入框的中插入各种自定义标签,特别的,需要将自定义标签插入指定的位置,可能是已有的字符串中间,而现有的组件无法记录失去焦点前的鼠标位置,所以采用div仿造一个div来实现需求,该方案可于移动端和pc端

直接上代码

import React, { useEffect, useState, useRef } from "react";
import styles from "./index.scss";

function DiscussInput({ tagName, value, wrapStyle = {} }, ref) {
  const [caretOffset, setCaretOffset] = useState(0);
  const [focusNode, setFocusNode] = useState(null);

  const inputElement = useRef(null);

  // in order to form.resetFields()
  useEffect(() => {
    if (!value) {
      inputElement.current.innerHTML = "";
    }
  }, [value]);

  useEffect(() => {
    if (tagName) {
      inputElement.current.focus();
      setCaretPosition();

      insertContent(tagName);
    }
  }, [tagName]);

  function setCaretPosition() {
    if (document.createRange && focusNode) {
      const range = document.createRange();
      range.setStart(focusNode, caretOffset);
      range.setEnd(focusNode, caretOffset);
      const selection = window.getSelection();
      selection.removeAllRanges();
      selection.addRange(range);
    }
  }

  function getCursortPosition(element) {
    const selection = window.getSelection();
    if (selection.rangeCount > 0) {
      const range = selection.getRangeAt(0);
      setCaretOffset(range.endOffset);
    }
  }

  function insertContent(content) {
    if (!content) {
      return;
    }
    inputElement.current.focus();
    let sel = null;
    sel = window.getSelection();

    if (sel.rangeCount > 0) {
      let range = sel.getRangeAt(0); //获取选择范围
      range.deleteContents(); //删除选中的内容
      let frag = document.createDocumentFragment(); //创建一个空白的文档片段,便于之后插入dom树
      let lastNode = frag.appendChild(content);
      range.insertNode(frag); //设置选择范围的内容为插入的内容
      let contentRange = range.cloneRange(); //克隆选区
      contentRange.setStartAfter(lastNode); //设置光标位置为插入内容的末尾
      contentRange.collapse(true); //移动光标位置到末尾
      sel.removeAllRanges(); //移出所有选区
      sel.addRange(contentRange); //添加修改后的选区
    }
  }

  let inputFlag = true;

  return (
    
{ inputElement.current.focus(); }} >
{ const range = window.getSelection(); range.selectAllChildren(e.target); range.collapseToEnd(); }} onBlur={e => { const node = window.getSelection().focusNode; getCursortPosition(node); setFocusNode(node); }} // onInput={e => { // const target = e.currentTarget; // setTimeout(() => { // if (inputFlag) { // onChange(target.innerHTML); // } // }, 0); // }} onCompositionStart={e => { inputFlag = false; }} onCompositionEnd={e => { inputFlag = true; }} />
); } export default DiscussInput;
一、获取组件内的输入值
  1. 如果外层有包裹form,那么可以直接调用form的内置方法,在各个值会改变的地方调用 onChange,外层form就可以直接拿到value。
  2. 如果外层没有包裹form,那么可以用 useImperativeHandle 和 forwardRef 向上传递value,父组件可以通过ref拿到子组件的值。
二、ContentEditable
  1. contentEditable: “true”,元素可编辑,可输入html标签
  2. contentEditable: “plaintext-only”,编辑区域只能键入纯文本

最明显的差异就是,属性设置true,回车会显示
,如果属性设置 plaintext-only,回车会显示换行符

三、onCompositionStart/onCompositionEnd

如果需要实时监听输入的数据,那么输入中文的时候,就会发现会将中文拼音也会记录,这时候就需要 onCompositionStart 、onCompositionEnd 再加上变量判断,等到中文输入完成之后,再处理输入的中文,中间输入的中文拼音忽略不处理。

你可能感兴趣的:(前端,前端)