修改文件


瀏覽器剖析完 HTML 後,建立的 DOM 元素會組成樹狀結構,瀏覽器上呈現的畫面,就是根據 DOM 樹繪製出來,只要改變 DOM 樹,瀏覽器就會根據改變後的 DOM 樹重繪畫面,而這就構成動態修改文件的基本原理。

底下這個範例示範如何動態新增與刪除圖片:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body>

    <input id="src" type="text"><button id="add">新增圖片</button>
    <div id="images"></div>

<script type="text/javascript">
    document.getElementById('add').onclick = function() {
      let img = document.createElement('img');

      img.src = document.getElementById('src').value;
      img.onclick = function() {
          document.getElementById('images').removeChild(this);
      };

      document.getElementById('images').appendChild(img);
    };

</script>
</body>
</html>

按我看執行結果

在原本的 HTML 中,並沒有任何的 <img> 元素,當在文字方塊中輸入圖片的網址並按下按鈕時,會使用 documentcreateElement 來動態建立元素,此時這個元素並沒有繫結至 DOM 樹,所以還不會出現在畫面上。

接著你設定建立的圖片元素 src 為輸入的網址,並註冊按下圖片時,使用 removeChild 將圖片本身(this)從 idimages<div> 中移除。

最後,將這個動態建立的圖片元素使用 appendChild 附加至 idimages<div> 元素成為其子元素,此時瀏覽器根據 DOM 樹結構重繪畫面。

當使用 JavaScript 動態改變 DOM 樹時,在瀏覽器的檢視網頁原始碼中,是看不到動態調整後的 HTML(那是一開始載入的靜態 HTML),你要使用瀏覽器中的開發者工具,才能看到動態的 DOM 畫面。例如 Chrome 的「開發人員工具」:

修改文件

每個節點都只能有一個父節點,如果直接取得 DOM 樹中既有的節點,並使用 appendChild 將之附加至另一個節點,則表示節點會從原有的父節點脫離,再附加至另一節點。例如:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
</head>
<body>  
    容器一:
    <div id="container1">
        <img id="image" src="https://openhome.cc/Gossip/images/caterpillar_small.jpg"/>
    </div><br>
    容器二:
    <div id="container2"></div>  

<script type="text/javascript">
    document.getElementById('image').onclick = function() {
        let container1 = document.getElementById('container1');
        let container2 = document.getElementById('container2');
        if(this.parentNode === container1) {
            container2.appendChild(this);
        }
        else {
            container1.appendChild(this);
        }
    };
</script>  
</body>
</html>

按我看執行結果

在這個例子中,點選圖片,會將圖片來回附加於兩個 <div> 之間,由於一個節點只能有一個父節點,所以 appendChild 的動作,會使被附加的節點從原父節點脫離。

createElement 是用來建立標籤對應的元素,如果要建立文字節點,必須使用 createTextNode,如果要動態建立屬性,則使用 createAttribute(少用)。

例如,若有個 <div id="console"></div>,想要在其中附加文字,可以如下:

let text = document.createTextNode('your text ....');
document.getElementById('console').appendChild(text);

也可以使用 insertBeforereplaceChild 等方法來調整 DOM 樹上的節點,各種方法的說明可以參考〈JavaScript and HTML DOM Reference〉。

要注意的是,只要你將節點附加至 DOM,瀏覽器就會重繪畫面,若有大量的節點要建立,每次建立就附加至 DOM 樹,則會有效能的問題。建議在背景準備好節點樹片段,等樹片段準備好,再將樹片段的根節點繫結至 DOM 樹,如此會有比較好的效能。

除了自行建立片段之外,也可以使用 createDocumentFragment 來建立 DocumentFragment,利用它在背景作樹片段組織,再一次將 DocumentFragment 附加至 DOM 樹。

DOM 元素有個非標準的 innerHTML 特性,你可以用之取得標籤中內含的 HTML,也可以指定字串給 innerHTML,瀏覽器會剖析這個字串,並建立對應的 DOM 元素安插至元素中,過去它不是標準特性,但幾乎每個瀏覽器都支援,而 HTML 5 已將 innerHTML 納入標準。

例如,要在上面提及的 <div> 中建立 <b>哈囉</b>,可以如下:

document.getElementById('console').innerHTML = '<b>哈囉</b>';