反应内容可编辑光标跳转到开头

问题描述 投票:0回答:4

我正在使用模块 react-simple-contenteditable 来编辑空白工作表中的填充内容。我必须使用内容可编辑元素而不是输入元素的原因是因为我希望问题的文本换行。例如,如果问题有一个空白,它会将文本分为三部分:空白之前的部分、空白和之后的部分。如果我将外部两个表示为单独的

div
(或输入字段),那么文本将不会像段落一样换行。相反,我必须有一个可内容编辑的
div
,其中包含两侧空白和自由文本的输入字段。

文本按照我想要的方式换行,但是当我在内容可编辑字段中键入文本时,光标跳到开头。我不明白为什么,因为我尝试了 module 的 github 站点上的示例,它工作得很好,虽然我的实现有点复杂,但它的工作原理基本相同。

这是我的渲染函数,使用

<ContentEditable />
:

render() {
    const textPieces =
      <div className='new-form-text-pieces'>
        {
          this.props.problem.textPieces.map( (textPiece, idx) => {
            if (textPiece.blank) {
              return (
                  <div data-blank={true} className='blank' key={ textPiece.id } style={{display: 'inline'}}>
                    <input
                      placeholder="Answer blank"
                      className='new-form-answer-input'
                      value={ this.props.problem.textPieces[idx].text }
                      onChange={ (event) => this.props.handleTextPiecesInput(this.props.problemIdx, idx, event.target.value) }
                    />
                    <button className='modify-blank remove-blank' onClick={ (event) => this.props.removeBlank(this.props.problemIdx, idx) }>-</button>

                  </div>
              );
            } else {
              let text = this.props.problem.textPieces[idx].text;
              const placeholder = idx === 0 ? 'Problem text' : '...continue text';
              // text = text === '' ? placeholder : text;
              if (text === '') {
                text = <span style={{color:'gray'}}>{placeholder}</span>;
              } else {

              }
              return (
                this.props.isTextSplit ?
                  <TextPiece
                    key={ textPiece.id }
                    problemIdx={this.props.problemIdx}
                    textPieceIdx={idx}
                    dropBlank={this.props.dropBlank}
                    moveBlank={this.props.moveBlank}
                  >
                    <div style={{display: 'inline-block', }}>{text}</div>
                  </TextPiece>
                : text

              );
            }
          })
        }
      </div>;



    return (
       this.props.isTextSplit ? textPieces :
        <ContentEditable
          html={ReactDOMServer.renderToStaticMarkup(textPieces)}
          className="my-class"
          tagName="div"
          onChange={ (event, value) => this.props.handleProblemChange(event, this.props.problemIdx, value) }
          contentEditable='plaintext-only'
        />
    );

  }

这是

onChange
函数:

handleProblemChange(event, problemIdx) {
    const problems = cloneDeep(this.state.problems);
    event.target.children[0].childNodes.forEach( (textPieceNode, idx) => {
      if (textPieceNode.constructor === Text) {
        problems[problemIdx].textPieces[idx].text = textPieceNode.wholeText;
      } else {
        problems[problemIdx].textPieces[idx].text = textPieceNode.childNodes[0].value;
      }
    });
    this.setState({ problems });
  }

这是它所指的状态,只是为了弄清楚:

this.state = {
  problems: [
    {
      id: shortid.generate(),
      textPieces: [
        {
          text : "Three days was simply not a(n)",
          blank : false,
          id: shortid.generate(),
        },
        {
          text : "acceptable",
          blank : true,
          id: shortid.generate(),
        },
        {
          text : "amount of time to complete such a lot of work.",
          blank : false,
          id: shortid.generate(),
        }
      ]
    }

非常感谢

reactjs contenteditable
4个回答
3
投票

长话短说,没有简单的方法可以做到这一点。我自己也尝试过,并花了几天时间尝试。基本上,您必须保存光标位置并在更新后自行重新定位。所有这些都可以通过 window.getSelection() 来实现

https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection

但是,根据内容的更改程度,这可能会变得非常棘手。

我最终使用了 DraftJS。这是 facebook 自己对 contenteditable div 的抽象。

https://draftjs.org/docs/overview.html#content

拿起的时间有点长,但你将能够做更多事情


1
投票

我使用 VueJS 也遇到了类似的问题。

这是包含 contenteditable div 的组件:

<Text @update-content="updateContent" :current-item-content="item.html_content"/>

这是 Text.vue 组件中的 prop 定义:

const props = defineProps({
    currentItemContent: {
        type: String,
        default: ''
    }
})

这是 Text.vue 组件中的 contenteditable div :

<div
    id="text-editor"
    ref="text_editor"
    class="mt-3 h-full w-full break-words"
    contenteditable="true"
    @input="updateContent"
    v-html="currentItemContent"
>

这是@update-content 事件触发的方法

const item = computed(() => { ... })
(...)
function updateContent(content) {
    item.value.html_content = content
}

这里的问题是注入

item.html_content
值作为 props 触发 contenteditable div 的重新渲染。 因为它在 updateContent 方法中改变了它的值,并且随着计算的 (
item
) 的更新,道具值也会更新,
v-html
会检测到更新的值并触发重新渲染。

为了避免这种情况,我删除了 v-html 绑定:

<div
    id="text-editor"
    ref="text_editor"
    class="mt-3 h-full w-full break-words"
    contenteditable="true"
    @input="updateContent"
>

并在 onMounted 挂钩中初始化了 contenteditable div 的值:

const text_editor = ref(null)
(...)
onMounted(() => {
    if (props.currentItemContent !== '') {
        text_editor.value.innerHTML = props.currentItemContent 
    }
})

我不知道是否有更好的解决方案,但它对我来说效果很好。希望这对某人有帮助


0
投票

就我而言,我只是添加了一个

event.preventDefault();

在“onInput”道具中,我认为原因是反应刷新


0
投票

我知道它们的工作方式不同,但就我而言,只需使用

onBlur
代替
onInput
就可以了。我希望它有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.