无法在 scala-js 中按 id 定位元素-dom

Can't locate element by id in scala-js-dom

我正在尝试将文本写入 DOM 中的元素:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>The Scala.js Tutorial</title>
    <!-- Include Scala.js compiled code -->
    <script type="text/javascript" src="./target/scala-2.13/hello-world-fastopt/main.js"></script>
  </head>
  <body>
    <div id="output"></div>
  </body>
</html>

然而,该元素为空:

package hello

import org.scalajs.dom

object TutorialApp {
  def main(args: Array[String]): Unit = {
    println(dom.document.getElementById("output")) // null
    dom.document.onload = (e) => {
      println(dom.document.getElementById("output")) // null
    }
  }
}

需要做什么才能获得 ID 为“output”的元素?

编辑:问题答案:

def main(args: Array[String]): Unit = {
    // Uncaught TypeError: Cannot read property 'innerHTML' of null
    println("innerHTML: " + dom.document.body.innerHTML)
    dom.document.onload = (e) => {
      println("onload") // Doesn't get called
    }
}
  1. 在本地,用 sbt fastOptJS 编译。最终结果行是:

[success] Total time: 2 s, completed Dec 13, 2020 4:30:36 PM

  1. HTML 与 HTML 页面 (hello-world-template/index-dev.html) 中呈现的一样(通过查看源代码),逐字逐句。它在 <DIV>.

    中打印“初始文本”
  2. 在 main() 中打印正文的内部 HTML 会导致 TypeError(请参阅代码注释)。代码中的 onload() 似乎根本没有被调用,因为它内部的 println() 调用没有打印。

  3. 是的,它在浏览器控制台打印了null

在您的代码中:

  def main(args: Array[String]): Unit = {
    println(dom.document.getElementById("output")) // null
    dom.document.onload = (e) => {
      println(dom.document.getElementById("output")) // null
    }
  }

第一个 println 打印 null 因为此时浏览器尚未完成解析 HTML / 实例化 DOM (稍微简化一下...)。浏览器从上到下读取 HTML 并构建 DOM 树,当它看到您的脚本标签时,它会“阻止”,即先下载并执行脚本,然后再读取其余脚本的 DOM。那是你的 main 运行的时间,也是为什么 output div 在这个阶段找不到的原因——浏览器还没有找到它。

要解决此问题,您可以将您的脚本标签移动到 HTML 中的 <body> 标签下方,以便下载脚本并在 文档全部解析并且 DOM 全部初始化之后执行

但更好的解决方案是延迟 DOM 脚本中的访问,直到浏览器触发一个事件表明这样做是安全的。这就是你试图通过分配 dom.document.onload 来实现的,但这在 JS 世界中有点“老式”,并不是所有浏览器都普遍支持(特别是 dom.document.onload 我的意思是,我认为分配 dom.window.onload 可能工作得很好)。

最终最好为此使用现代语法:

dom.window.addEventListener("load", ev => {
  println(dom.document.getElementById("output"))
})

您也可以使用“DOMContentLoaded”事件代替“load”,它们的目的相同,但在等待图像等资源方面的时间略有不同。不过在你的情况下并不重要。如果好奇,请检查 MDN 以获取详细信息。