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

Posted

技术标签:

【中文标题】添加换行符后防止 chrome 中的 textarea 滚动行为【英文标题】:Preventing textarea scroll behaviour in chrome after newline added 【发布时间】:2019-10-13 05:44:55 【问题描述】:

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

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

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

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

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

<textarea style="width:90%;height:300px"></textarea>

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

【问题讨论】:

你有那个 textarea 脚本吗?我想看看 @MauricioCárdenas 查看密码笔 那里没有脚本。这只是标准的文本区域。 “我有一个小编辑器脚本,它有一个显示代码的 textarea。textarea 有一个固定的大小和一个垂直滚动条。除此之外没有什么花哨的”。我想看看那个脚本大声笑 @MauricioCárdenas 该错误不涉及任何脚本,此代码笔中存在有问题的行为 我也有同样的问题。 @user10275798 你找到解决方案了吗? 【参考方案1】:

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

我对这种行为进行了很长时间的试验和检查,但未能找到任何可重复的情况,这些情况可能会为如何预测何时发生这种情况提供线索。它真的只是看起来“随机”。尽管如此,我不得不为我正在创建的 NWJS 编辑器应用程序解决这个问题(NWJS 使用 Chrome for UI。)

这似乎对我有用:

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

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

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

所以下一次滚动发生,这可能是讨厌的随机动作,滚动监听器将清除scrollCorrection,然后将textarea滚动到上一个滚动位置,即滚动回“随机”之前的位置“滚动。但是问题是不可预知的,如果没有随机滚动而下一次滚动是故意的怎么办?这没什么大不了的。这只是意味着如果用户手动滚动,第一个滚动事件基本上无效,但之后(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);
)

现在让我们对其进行扩展:

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

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

    确定插入符号相对于 textarea 视口的 xy 位置 确定它是否被滚动出视图 如果是这样: 确定滚动量以使其显示在视图中 通过测试scrollCorrection的状态判断随机滚动是否已经发生 如果没有,设置标志scrollCorrection2包含滚动量 如果有,请显式进行额外的滚动以使其重新显示

在 textarea 中查找插入符号的 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);
    
  

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

[1] 我也看不出你不能在“beforeinput”[3] 监听器中做的任何原因,我们在“输入”监听器中所做的事情。这也可能为我们在随机滚动发生之前设置的scrollCorrection 提供更多保障。尽管请注意“输入前”是实验性的。

[2] 我怀疑这是导致此问题的此功能的错误实现。

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

【讨论】:

【参考方案2】:

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

css:

textarea  
    overflow:auto; 
    resize:none; 
    width:90%; 
    height:300px; 
 

js: 您需要在 A

处插入来自this question 的第一个答案
function preventMoving(e) 
    var key = (e.keyCode ? e.keyCode : e.which);
    if(key == 13) 
        e.preventDefault();
        // A
    

然后在您的 HTML 上:

<textarea onkeyup="preventMoving(event);"></textarea>

【讨论】:

(1) 这与页面滚动有关,而不是与 textarea 滚动有关,因为我注意到问题是 (2) 不需要的滚动行为发生在 keydown 事件而不是 keyup 中。 (3) 该错误看起来不像正常行为,因为它是不可预测的。 给我几分钟,我会更新答案,我现在有点忙,但感谢您解决这个问题。 PS:这不是不可预测的,这是由于焦点引起的正常行为(显然)。我能够在您发布的代码笔中不断重现该问题。无论如何,我会在几分钟后深入研究它。 我不知道您是否看到与此处相同的问题,它并非一直发生,它会在不改变对 textarea 元素的焦点的情况下发生开/关。 可能我看到了不同的东西,这就是为什么我理解的东西不同。我能看到的是:每当你在 textarea 中向下滚动(以及整个页面),然后你写一些东西并按下回车键,textarea 就会自动滚动,所以你刚刚写的任何内容都会出现在 textarea 的顶部.

以上是关于添加换行符后防止 chrome 中的 textarea 滚动行为的主要内容,如果未能解决你的问题,请参考以下文章

调用 reloadData 后防止标头更新?

点击链接后防止 UIWebView 滚动

在 Laravel 中无效后防止重定向到主页

双击后防止文本选择

HTML 双击后防止多个页面请求

表单提交后防止页面重新加载