添加换行符后防止 chrome 中的 textarea 滚动行为

8 html javascript google-chrome

最近我的 chrome 版本越来越频繁地做一些奇怪的事情(ubuntu 18.04 上的 74.0.3729.131)。我有一个小的编辑器脚本,它有一个显示代码的 textarea。textarea 具有固定大小和垂直滚动条。除此之外没有什么花哨的。

通常,当我插入换行符(textarea 的正常行为)时,滚动条不会移动。现在出于某种原因,大约 80% 的时间它会向下滚动 textarea 直到插入符号的位置位于 textarea 的顶部。奇怪的是,如果我删除并在同一位置输入换行符,它通常不会滚动。

我不确定这是否是 Chrome 中的一些新问题。我以前使用相同编辑器的版本没有这个问题。

这是一个演示问题的代码笔,滚动到某行,按回车键,textarea 应该向下滚动。尝试几次以查看不可预测的行为(添加代码只是为了能够添加链接,因为您可以看到它只是一个文本区域)。

https://codepen.io/anon/pen/rgKqMb

<textarea style="width:90%;height:300px"></textarea>
Run Code Online (Sandbox Code Playgroud)

我想到的唯一解决方案是停止输入键的正常行为并将换行符添加到文本中。非常欢迎任何其他想法/见解。

All*_*sso 6

快2020年年底了,Chrome版本86了,这个问题还存在吗?更重要的是,我很惊讶我没有找到关于此事的更多信息(投诉)(这篇文章是我发现的唯一专门讨论这个问题的文章。)我观察到这种行为不仅发生在打字中,而且还发生在打字时。粘贴任何包含换行符的文本。我还观察到,如果我在发生这种情况后执行撤消操作,则会发生另一个随机滚动,使我在页面上走得更远,而离插入符所在的位置很远。

我对这种行为进行了长时间的实验和检查,但无法找到任何可重复的情况,可以为如何预测这种情况何时发生提供线索。它确实看起来“随机”。尽管如此,我必须为我正在创建的 NWJS 编辑器应用程序解决这个问题(NWJS 使用 Chrome 作为 UI。)

这似乎对我有用:

首先我先简单的介绍一下原理。我们将“输入”侦听器和“滚动”侦听器附加到文本区域。这是有效的,因为无论如何,根据我的观察,“input”[1] 侦听器在随机滚动操作发生之前被触发。

滚动侦听器记录每个滚动操作并将其保存在全局prevScrollPos. 它还检查全局标志scrollCorrection

scrollCorrection每次将文本输入到文本区域时,“输入”侦听器都会设置该标志。请记住,这是在随机滚动发生之前发生的。

因此,下一次发生的滚动(这可能是令人讨厌的随机操作)滚动侦听器将清除scrollCorrection,然后将文本区域滚动到上一个滚动位置,即将其滚动回“随机”滚动之前的位置。但问题是不可预测的,如果没有随机滚动并且下一次发生的滚动是故意的怎么办?这没什么大不了的。这只是意味着,如果用户手动滚动,则第一个滚动事件基本上无效,但之后(清除scrollCorrection)所有内容都会正常滚动。由于在正常滚动期间,事件的吐出速度非常快,因此不太可能产生任何明显的效果。

这是代码:

let textarea;
let prevScrollPos = 0;
let scrollCorrection = false;

function onScroll(evt) {
  if (scrollCorrection) {
    // Reset this right off so it doesn't get retriggered by the corrction.
    scrollCorrection = false;
    textarea.scrollTop = prevScrollPos;
  }
  prevScrollPos = textarea.scrollTop;
}

function onInput(evt) {
  scrollCorrection = true;
}

window.addEventListener("load", () => {
  textarea = document.getElementById("example_textarea");
  textarea.addEventListener("scroll", onScroll);
  textarea.addEventListener("input", onInput);
})
Run Code Online (Sandbox Code Playgroud)

现在我们来扩展一下:

还有另一个考虑因素。如果键入或粘贴操作将键入或粘贴文本的末尾(以及插入符号)置于文本区域视口的视图之外,该怎么办?当正常滚动时,大多数浏览器将滚动页面[2],因此插入符号将保留在视图中。然而,现在我们已经接管了滚动操作,我们需要自己实现它。

在下面的伪代码中,在输入到文本区域时,除了设置scrollCorrection之外,我们还调用一个函数,该函数将:

  1. 确定插入符相对于文本区域视口的 xy 位置
  2. 确定它是否滚动到视图之外
  3. 如果是这样:
  4. 确定滚动量以使其可见
  5. 通过测试scrollCorrection的状态来确定随机滚动是否已经发生
  6. 如果没有,设置包含滚动量的标志scrollCorrection2
  7. 如果有,则明确执行额外的滚动以将其带回到视图中

在文本区域中查找插入符的 xy 位置并不是一件小事,并且超出了本答案的范围,但是在搜索网络时可以找到很多方法。大多数涉及在非表单元素(例如 div 块)中复制文本区域内容,具有类似的字体、字体大小、文本换行等,然后getBoundingClientRect在生成的包含块等上使用。在我的情况下,我已经为我的编辑做了大部分工作,所以这并不是什么额外的费用。但我已经包含了一些伪代码来展示如何在滚动校正机制中实现这一点。 setCaretCorrection基本上执行上面的步骤 1 - 7。

let textarea;
let prevScrollPos = 0;
let scrollCorrection = false;
let caretCorrection = 0;

function onScroll(evt) {
  if (scrollCorrection) {
    // Reset this right off so it doesn't get retriggered by the correction.
    scrollCorrection = false;
    textarea.scrollTop = prevScrollPos + caretCorrection;
    caretCorrection = 0;
  }

  prevScrollPos = textarea.scrollTop;
}

function onTextareaInput() {
  scrollCorrection = true;
  setCaretCorrection();
}

function setCaretCorrection(evt) {
  let caretPos = textarea.selectionStart;
  let scrollingNeeded;
  let amountToScroll;

  /* ... Some code to determine xy position of caret relative to
         textarea viewport, if it is scrolled out of view, and if
         so, how much to scroll to bring it in view. ... */

  if (scrollingNeeded) {
    if (scrollCorrection) {
      // scrollCorrection is true meaning random scroll has not occurred yet,
      // so flag the scroll listener to add additional correction. This method
      // won't cause a flicker which could happen if we scrollBy() explicitly.
      caretCorrection = amountToScroll;
    } else {
      // Random scroll has already occurred and been corrected, so we are
      // forced to do the additional "out of viewport" correction explicitly.
      // Note, in my situation I never saw this condition happen.
      textarea.scrollBy(0, amountToScroll);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

人们可以更进一步,使用实验事件“beforeinput”[3],对此进行一点优化,从而减少不必要的调用setCaretCorrectionevent.data如果从“beforeinput”事件进行检查,在某些情况下它会报告要输入的数据。如果没有,则输出null不幸的是,当输入换行符时,event.datanull但是,如果粘贴换行符,它将报告换行符。因此至少可以看到 event.data 是否包含字符串,如果该字符串不包含换行符,则跳过整个纠正操作。(另请参阅下面的 [1]。)

[1] 我也看不出有什么理由不能在“beforeinput”[3] 侦听器中执行我们在“input”侦听器中执行的操作。scrollCorrection这也可能为我们在随机滚动发生之前设置的提供更多保障。尽管请注意“输入之前”是实验性的。

[2] 我怀疑此功能的实现被破坏导致了此问题。

[3] https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event(根据此链接,可在 Chrome 和除 Firefox 之外的所有主要浏览器上使用。)


Mau*_*nas 1

您可以尝试使用 css 和 js 避免文本区域上的事件,然后强制滚动到当前位置:

CSS:

textarea { 
    overflow:auto; 
    resize:none; 
    width:90%; 
    height:300px; 
} 
Run Code Online (Sandbox Code Playgroud)

Node.js:您需要在A处插入此问题的第一个答案

function preventMoving(e) {
    var key = (e.keyCode ? e.keyCode : e.which);
    if(key == 13) {
        e.preventDefault();
        // A
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在你的 HTML 上:

<textarea onkeyup="preventMoving(event);"></textarea>
Run Code Online (Sandbox Code Playgroud)

  • (1) 这与页面滚动有关,而不是我指出的问题是文本区域滚动 (2) 在 keydown 事件而不是 keyup 事件中发生不需要的滚动行为。(3) 该 bug 看起来不像正常行为,因为它是不可预测的。 (2认同)