innerHTML 不会在每个 keydown 事件中改变

innerHTML won't change in each keydown event

以下代码旨在将一个元素(myNote)的innerHTML更改为另一个元素(myInput)的值,每次 Return 键被按下:

// ==UserScript==
// @name        Duolingo
// @include     *://duolingo.com/*
// @include     *://www.duolingo.com/*
// ==/UserScript==
setTimeout(()=>{
  let myInput = document.querySelector('._7q434._1qCW5._2fPEB._3_NyK._1Juqt._3WbPm');
  let myNote = document.createElement('div');
  document.body.appendChild(myNote);
  myNote.setAttribute("style", "position: fixed; bottom: 0; right: 0; display: block; width: 400px; height: 400px; background: orange; color: #000");

  myInput.addEventListener('keydown', (k)=>{
    if ( k.keyCode === 13 ) {
      myNote.innerHTML += myInput.value + '<br>';
    }
  });
}, 2000);

目的

我 运行 duolingo.com(一个学习 mind2mind 语言如法语的网站)上的 Greasemonkey 代码,在 "translate this sentence".

这样的文本问题会话中

该代码的目的是创建一个橙色背景的小框,其中包含我已经在 Duolingo 问题中尝试过的输入,前提是 Duolingo 不会保存这些内容。

有了脚本,我可以保存它们,并在我重考语言问题时使用它们,因为它节省了我重新输入大部分句子的时间。

问题

代码失败,因为 innerHTML 只改变了一次。如果值再次改变,我按下 Return,什么也不会发生。

重现

在 duolingo.com.

上使用 Greasemonkey(或类似程序)中的代码进行复制

问题

为什么 innerHTML 只更改一次?鉴于 addEventListener 一直在监听,为什么它只工作一次?

使用 return 进行更改或添加 return false 没有帮助。

也许需要另一种添加包含每次按下的值的新元素的方法。

Mobius 更新:

Mobius,这是我使用的代码,但没有用:

setTimeout(()=>{
    window.myCss =`.note {position: fixed; bottom: 0; right: 0; width: 400px; height: 400px; background: orange}`;
    style = document.createElement("style");
    style.type = "text/css";
    style.styleSheet ? style.styleSheet.cssText = myCss : style.appendChild(document.createTextNode(myCss) );
    head = document.head || document.getElementsByTagName("head")[0];
    head.appendChild(style);

    let note = document.createElement('div');
    note.classList.add('note');
    document.querySelector('body').appendChild(note);

  let savedValue;
    document.addEventListener('keydown', (e)=>{
    let target = e.target;
    if (target.nodeName === 'textarea') {
        savedValue = e.target.value;
        }
    });

    if (k.keyCode === 13) {
        setTimeout(()=>{
            document.querySelector('textarea').value = savedValue;
        }, 100);
    }
}, 2500);

我在控制台没有任何错误。

我试着阅读这篇文章,我明白在最初的例子中我是胡说八道而不是捕获(我应该通过从 html 元素到我的 textarea 元素来捕获任何处理程序每次重新捕获它的处理程序,但我看不出如何改进代码。也许我现在心情不好,所以没读好...

据我所知,duolingo 中的测验是用 React 编写的,因此很可能您添加事件监听器的元素不再存在。有几种方法可以解决这个问题,但最好的方法可能是使用事件冒泡(可能在捕获阶段)来监听不会被替换的内容,例如 body 元素并等待来自元素的事件符合您的需求。

您的代码将如下所示:

let savedValue = '';
document.addEventListener('keydown', (e) => {
    let target = e.target;
    if (target.nodeName == 'TEXTAREA') {
        // save off the text value;
        savedValue = e.target.value;
    }

    if (e.keyCode == 13){
        setTimeout(() => { 
            document.querySelector('textarea').value = savedValue;
        }, 10);
    }
}, true)

// ==UserScript==
// @name        Duolingo
// @namespace   nms
// @include     *://duolingo.com/*
// @include     *://www.duolingo.com/*
// @version     1
// @grant       none
// ==/UserScript==

setTimeout(()=>{
    // Style:

        /*let myCss =`
            .note {position: fixed; bottom: 0; right: 0; width: 400px; height: 400px; background: orange}
        `;*/
      let myCss =`
            .note {position: fixed; top: 100px; left:100px; bottom: 100px; right: 100px; width: 200px; height: 200px; background: orange}
        `;  

        style = document.createElement("style");
        style.type = "text/css";
        style.styleSheet ? style.styleSheet.cssText = myCss : style.appendChild(document.createTextNode(myCss) );

        head = document.head || document.getElementsByTagName("head")[0];
        head.appendChild(style);

    // Behavior:

        let myNote = document.createElement('div');
        myNote.classList.add('note');
        document.querySelector('body').appendChild(myNote);
        let myInputField = document.querySelector('textarea');

        myInputField.addEventListener('keydown', (k)=>{
            if ( k.keyCode === 13 ) {
                myNote.innerHTML = myInputField.value;
            }
        });
}, 2000);
<textarea></textarea>
<br>
<div id="myNote">

</div>

如果您在 Whosebug 上 运行 完整页面视图中的原始代码,代码 运行 没问题。我更改了橙色矩形的 css 以在非完整页面视图中更好地查看结果并且它也有效。注意:我使用的是 Google Chrome 版本 49.0.2623.112 m。

代码失败(在删除拼写错误后),因为它将事件侦听器添加到文本区域,网页会被每个新问题覆盖。当文本区域被覆盖时,它会破坏您添加的先前事件侦听器。它还在该代码中孤立了 myInput 的值。

在持久容器或整个主体上使用更智能的事件侦听器。并且,根据需要刷新 myInput

还有其他问题:

  1. ._7q434._1qCW5._2fPEB._3_NyK._1Juqt._3WbPm 是一个特别脆弱的选择器,下次网站代码更改时会崩溃。它在我看到的某些页面上也已经不起作用。

    textarea[data-test] 可能会起作用。考虑到其余代码,甚至 textarea 可能就足够了。

  2. setTimeout 是一种等待元素的脆弱方式,是 already causing you grief。使用 waitForKeyElementsMutationObserver 或类似的代替。但在您的情况下,由于您正在等待用户交互,您可能不需要任何其他延迟机制。

将所有这些放在一起,您的用户脚本将类似于:

// ==UserScript==
// @name        Duolingo, answer text recycler
// @match       *://*.duolingo.com/*
// @run-at      document-idle
// ==/UserScript==
let myNote = document.createElement ('div');
document.body.appendChild (myNote);
myNote.setAttribute (
    "style",
    "position: fixed; bottom: 0; right: 0; width: 400px; height: 400px; background: orange; overflow: auto;"
);

document.addEventListener ('keydown', (zEvent) => {
    if (zEvent.keyCode === 13) {
        if (zEvent.target.nodeName === 'TEXTAREA') {
            let myInput = document.querySelector ('textarea[data-test]');
            if (myInput) {
                myNote.innerHTML += myInput.value + '<br>';
            }
        }
    }
} );