【问题标题】:element.focus() doesn't work on rerendered contentEditable div elementelement.focus() 不适用于重新渲染的 contentEditable div 元素
【发布时间】:2022-01-15 13:30:42
【问题描述】:

import React, { useEffect, useState } from "react";

import "./styles.css";

const RichText = () => {
  const [value, setValue] = useState("");

  useEffect(() => {
    const div = document.getElementById("textarea");

    if (div) {
      setTimeout(() => {
        div.focus();
      }, 0);
    }
  });

  return (
    <div
      className="rich-text"
      onInput={(e) => {
        setValue(e.target.innerText);
      }}
      contentEditable
      id="textarea"
      dangerouslySetInnerHTML={{
        __html: value
      }}
    />
  );
};

export default RichText;

我想实现富文本组件,想法是你可以在某个字段内输入文本,你可以设置文本的样式(使其变为粗体、斜体、下划线等)。我想在 value 状态变量上使用相同的文本值,然后以某种方式将其包装在 html 标签 &lt;p&gt;Hello &lt;b&gt; Andrew&lt;/b&gt;&lt;/p&gt; 内,并在同一个字段中实时显示,样式化。为了在 divcontentEditable 字段内显示 html 标签,我需要使用 dangerouslySetInnerHTML ,这是主要问题。每次按下按钮时,我都会更新值,然后重新渲染组件,焦点位于字段的开头,但我希望它在您输入新文本时结束。我试图通过ref => ref.current.focus() 来实现它,它不起作用,在上面的代码中你可以看到我也尝试通过 vanilla js 使用 timeout 来实现它,它也不起作用, autoFocus - 只能在 input, textarea, etc 上使用,div 可以与此属性一起使用。我已将其保存到 ref ,但是我无法在 div 内显示包装好的 html。尝试了很多案例,但似乎都是。有什么制作方法吗?

【问题讨论】:

标签: javascript reactjs richtext


【解决方案1】:

问题是当使用 useState 钩子与 contentEditabledangerouslySetInnerHTML 同步值时。当您在 div 中键入内容时,它会再次重新呈现并将光标带回到开头。

您可以使用函数组件中的实例变量(useRef 更新值)来解决此问题。

你应该使用innerHTML而不是innerText来保存HTML字符串

像下面这样试试

import React, { useRef } from "react";

import "./styles.css";

const RichText = () => {
  const editableRef = useRef(null);
  const { current: value } = useRef(
    '<div style="color:green">my initial content</div>'
  );

  const setValue = (e) => {
    value.current = e.target.innerHTML;
  };

  const keepFocus = () => {
    const { current } = editableRef;
    if (current) {
      current.focus();
    }
  };

  return (
    <div
      className="rich-text"
      onInput={setValue}
      contentEditable
      id="textarea"
      ref={editableRef}
      onBlur={keepFocus}
      dangerouslySetInnerHTML={{
        __html: value
      }}
    />
  );
};

export default RichText;

Code Sandbox

【讨论】:

  • @Andrey Radkevich,看看这个!!
  • value.current = e.target.innerHTML - 在这里我想将它包装在一些标签中,并在每种类型的 div 中显示它 => value.current = &lt;b&gt;${e.target.innerHtml}&lt;/b&gt; ,它显示为粗体在 div 中。我的组件应该像这样工作,你输入一些文本,并且从这个文本中的一些工作你想要加粗,你选择这个文本,按粗体按钮,它显示为粗体,这就是为什么我需要在你的 setValue 函数中处理它,获取选定的文本并将其包装在 中并重新渲染 div ,并将其显示为粗体,检查富文本的工作原理,您就会明白,您的解决方案还可以,但它不能解决我的情况跨度>
  • 这就是为什么我需要一些功能来将插入符号移到最后,在 div 的每个重新渲染上,我都需要重新渲染
  • 我得到了你的用例。您可能需要自己处理重新渲染中断的光标位置。这段代码试图做到这一点 => github.com/lovasoa/react-contenteditable/blob/master/src/…
  • 好像找到了窍门,请检查答案
【解决方案2】:
import React from "react";

import "./styles.css";

class TextInput extends React.Component {
  constructor(props) {
    super();
    this.state = { value: "" };
  }

  shouldComponentUpdate() {
    return true;
  }

  componentDidUpdate() {
    const el = document.getElementById("textarea");

    if (el) {
      console.log(el.selectionStart, el.selectionEnd);

      var range, selection;
      if (document.createRange) {
        //Firefox, Chrome, Opera, Safari, IE 9+
        range = document.createRange(); //Create a range (a range is a like the selection but invisible)
        range.selectNodeContents(el); //Select the entire contents of the element with the range
        range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
        selection = window.getSelection(); //get the selection object (allows you to change selection)
        selection.removeAllRanges(); //remove any selections already made
        selection.addRange(range); //make the range you have just created the visible selection
      } else if (document.selection) {
        //IE 8 and lower
        range = document.body.createTextRange(); //Create a range (a range is a like the selection but invisible)
        range.moveToElementText(el); //Select the entire contents of the element with the range
        range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
        range.select(); //Select the range (make it the visible selection
      }
    }
  }

  update(value) {
    this.setState({ value });
  }

  render() {
    console.log(this.state.value);
    return (
      <div
        className="rich-text"
        onInput={(e) => {
          this.update(e.target.innerText);
        }}
        contentEditable
        id="textarea"
        dangerouslySetInnerHTML={{
          __html: `<b>${this.state.value}</b>`
        }}
      />
    );
  }
}

export default TextInput;

【讨论】:

  • 需要指出的一点是,在您输入 textarea div 后,那里已经有一个光标。你可以运行window.getSelection,你会看到它是一个“插入符号”类型,范围为selection.getRangeAt(0)。从范围您可以调用range.setEnd 调整终点,然后selection.collapseToEnd 将焦点设置到终点。这仅适用于具有选择 API 的浏览器。但是,您可以通过这种方式为这些浏览器减少手动创建范围。
  • 您能编辑我的答案吗? @GenericUser,我在打字时看到一些闪烁,也应该解决这个问题)
  • 如果在字符串的开头/中间键入将不起作用:光标跳到结尾。
  • @AlonShmiel,哦,没想到,明白了
猜你喜欢
  • 1970-01-01
  • 2015-11-05
  • 2018-08-05
  • 1970-01-01
  • 2012-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-08
相关资源
最近更新 更多