JavaScript 处理 HTML DOM

Android 开发 中,许多时候不可避免需要展示 富文本 内容,通常我们用到 WebView 加载网页的形式来实现,比如新闻类应用的新闻详情页、休闲软件的阅读界面等等,几乎你看到的图文混杂的富文本页面都是由网页实现的。

但不知道读者有没有发现,这些网页和 Native 交互起来很自然,完全不像网页。比如点击网页的图片跳转到图片浏览界面、标题预加载图片懒加载,包括我司阅读软件的阅读界面的点击查词。本文将讲述如何通过 JavaScript 处理 HTML 的 DOM 结构来实现与 Native 更好地交互。

1. 概览

1.1 DOM 是什么

DOM(Document Object Model)是浏览器加载网页时构造的对象树结构,如图:

DOM

HTML 本身也是 XML 格式,熟悉 XML 解析的读者对这个可能并不陌生,每一个标签都被抽象为一个 Element,它具备它包含的文本和它具有的属性。

1.2 JavaScript 能对其做什么

JavaScript 获得了足够的能力来创建动态的 HTML:

  • JavaScript 能够改变页面中的所有 HTML 元素
  • JavaScript 能够改变页面中的所有 HTML 属性
  • JavaScript 能够改变页面中的所有 CSS 样式
  • JavaScript 能够对页面中的所有事件做出反应

1.3 三种取得 HTML 元素的方法

(1)通过 id 查找

1
var x=document.getElementById("intro");

(2)通过标签名查找

1
var x=document.getElementsByTagName("p");

(3)通过 class 查找

1
var x=document.getElementsByClassName('cc')

1.4 操作 HTML 元素

(1)改变 HTML 输出流

在 JavaScript 中,document.write() 可用于直接向 HTML 输出流写内容,注意会覆盖网页现有内容

(2)改变 HTML 内容

修改 HTML 内容的最简单的方法时使用 innerHTML 属性,例如:

1
2
3
4
5
6
7
8
9
10
11
<html>
<body>

<p id="p1">Hello World!</p>

<script>
document.getElementById("p1").innerHTML="New text!";
</script>


</body>
</html>

(3)改变 HTML 属性

Element#attributeName = value 语法用来改变 HTML 元素的属性,例如:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<body>

<img id="image" src="smiley.gif">

<script>
document.getElementById("image").src="landscape.jpg";
</script>


</body>
</html>

1.5 操作 CSS 元素

Element#style.propertyName = newStyle 语法用来改变样式,例如:

1
2
3
4
5
<h1 id="id1">My Heading 1</h1>

<button type="button" onclick="document.getElementById('id1').style.color='red'">
点击这里
</button>

1.6 DOM 事件

HTML DOM 使 JavaScript 有能力对 HTML 事件做出反应,如:

  • 当用户点击鼠标时
  • 当网页已加载时
  • 当图像已加载时
  • 当鼠标移动到元素上时
  • 当输入字段被改变时
  • 当提交 HTML 表单时
  • 当用户触发按键时

此处讲述点击事件,其他事件类似,可前往 W3School 参考

(1)HTML 声明事件

1
2
3
<h1 onclick="this.innerHTML='谢谢!'">请点击该文本</h1>
or
<h1 onclick="changetext(this)">请点击该文本</h1>

(2)JS 添加事件

1
2
3
document.getElementById("myBtn").onclick=function(){
displayDate(this) // this 指的是 Element 元素
};

1.7 添加和删除

(1)创建新的 HTML 元素

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="div1">
<p id="p1">这是一个段落</p>
<p id="p2">这是另一个段落</p>
</div>

<script>
var para=document.createElement("p");
var node=document.createTextNode("这是新段落。");
para.appendChild(node);

var element=document.getElementById("div1");
element.appendChild(para);
</script>

(2)删除已有的 HTML 元素

1
2
3
4
5
6
7
8
9
10
<div id="div1">
<p id="p1">这是一个段落。</p>
<p id="p2">这是另一个段落。</p>
</div>

<script>
var parent=document.getElementById("div1");
var child=document.getElementById("p1");
parent.removeChild(child);
</script>

父节点可通过 parentNode 属性寻找到,如上述代码可以简单实现:

1
2
var child=document.getElementById("p1");
child.parentNode.removeChild(child);

2. 应用

在 Android 中,应用思路主要是通过执行 JavaScript 代码为网页 DOM 添加事件,回调我们的 Native 接口,此处以为网页添加单词点击事件为例。

百词斩爱阅读

2.1 第一步,分析网页

后端接口返回的 JSON 数据中,其中需要我们展示的便是这段 HTML 代码,我们的目的是为每个单词添加点击事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="paragraph" id="para_2">
<span cid="424590" class="span" rwt_id="62989" word="in" trans="n.知情者;adj.在里面的,时髦的" pnc="/ɪn/" audio_src="https://ws.bczcdn.com/r/in.mp3" lv="3" >
in</span>
<span cid="424592" class="span" rwt_id="82049" word="my" trans="adj. 我的 (I的所有格形式)" pnc="/maɪ/" audio_src="http://7n.bczcdn.com/dict/audio/n_us/bXk=.mp3" lv="3" >
my</span>
<span cid="424594" class="span" rwt_id="19648" word="case" trans="n. 例子;情况;案例" pnc="/keɪs/" audio_src="http://7n.bczcdn.com/dict/audio/n_us/Y2FzZQ==.mp3" lv="3" >
case</span>
<span cid="424595" class="tm" >
,</span>
<span cid="424597" class="span" rwt_id="127815" word="too" trans="adv.太,也,而且,很" pnc="/tuː/" audio_src="https://ws.bczcdn.com/r/too.mp3" lv="3" >
too</span>
<span cid="424599" class="span" rwt_id="39267" word="early" trans="adj.早的,早期的;adv.早,先" pnc="/ˈɜːrli/" audio_src="https://7n.bczcdn.com/r/early.mp3" lv="3" >
early</span>
...
</div>

简单分析很容易发现,每一个单词都是一个 span 标签,或者 class 都为 “span”,而且我们需要展示的内容全部都在标签属性中体现了,所以我们选择 span 标签下手。

2.2 第二步,准备添加点击的 JS 代码

JS 代码很简单,找出所有的 span 标签元素,为每个标签元素添加 onclick 事件属性即可:

1
2
3
4
5
6
var tags = document.getElementsByTagName('span')
for (var i = 0; i < tags.length; i++) {
tags[i].onclick = function() {
window.read.onWordClick(this.word) // 回调 Native
}
}

接下来,在 Native 端我们要在网页加载完毕后执行这段代码:

1
2
3
4
5
6
7
private class DetailWebClient extends WebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
view.loadUrl("javascript:(function(){" + CODE + "})()");
}
}

2.3 第三步,声明 Native 接口

通过 WebView 的 addJavascriptInterface() 方法可指定接口,代码如下:

1
2
3
4
5
6
7
webview.addJavascriptInterface(this, "detail");

@JavascriptInterface
public void onWordClick(String word) {
Log.d(TAG, "onclick: " + word);
// do some thing
}

总结

本文主要讲解了 WebView 是如何与网页更好交互的,通常的手段是执行 JavaScript 代码操作 DOM,为 HTML 元素添加点击事件,而在事件中回调 Native 实现的。