You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/3-terrarium/3-intro-to-DOM-and-closures/translations
Gturpin e7f22a8dd1
Add french translation for Terrarium - intro to DOM and closures (#454)
3 years ago
..
README.es.md changing quiz app link 3 years ago
README.fr.md Add french translation for Terrarium - intro to DOM and closures (#454) 3 years ago
README.hi.md changing quiz app link 3 years ago
README.it.md changing quiz app link 3 years ago
README.ja.md changing quiz app link 3 years ago
README.ko.md changing quiz app link 3 years ago
README.ms.md changing quiz app link 3 years ago
README.zh-tw.md Update README.zh-tw.md 3 years ago
assignment.es.md Update assignment.es.md 3 years ago
assignment.fr.md Update assignment.fr.md 3 years ago
assignment.hi.md Update assignment.hi.md 3 years ago
assignment.it.md Italian translation of chapter 3 4 years ago
assignment.ja.md Update assignment.ja.md 3 years ago
assignment.ko.md Update assignment.ko.md 3 years ago
assignment.ms.md Update assignment.ms.md 3 years ago
assignment.nl.md Update assignment.nl.md 3 years ago
assignment.zh-cn.md [zh-cn] 3/3/assignment.zh-cn.md 3 years ago
assignment.zh-tw.md Merge branch 'main' into patch-2 3 years ago

README.zh-tw.md

盆栽盒專案 Part 3 - DOM 元素控制與閉包

DOM 元素與閉包

Tomomi Imura 繪製

課前測驗

課前測驗

大綱

操作 DOM (Document Object Model) 是網頁開發的一項關鍵。根據 MDN 文件 「Document Object Model (DOM) 元素能根據網頁文件的結構與內容來呈現物件」。藉由使用 JavaScript 框架而非原始的 JavaScript 程式碼來管理 DOM在網頁上操作 DOM 的挑戰已經不比以前困難了,但這裡我們要自己來管理它們!

此外,這堂課也會介紹有關JavaScript 閉包(Closure)的概念,你可以想像成一個函式被包在另一個函式中,以訪問外面函式範圍中的變數。

JavaScript 閉包是個廣闊且複雜的主題。本堂課只觸及建立盆栽盒需要的最基礎概念。你能得知一個閉包為:內部函式和外部函式建立一項關係,允許內部函式存取外部函式的變數等作用域。要得知更多關於閉包的原理,請造訪觀看額外的文件

我們會使用閉包來操控 DOM。

想像 DOM 就像一棵樹,表現出所有操作網頁的方式。多樣的 APIs (Application Program Interfaces) 提供程式開發者,依照自己使用的程式語言,以存取、編輯、編排等方式管理 DOM 元素。

DOM 樹的表達

HTML 語法會參考 DOM 的呈現方式。出自 Olfa Nasraoui

在這堂課中,我們會完成我們的盆栽盒專案,建立 JavaScript 來對網頁中的植物進行互動式操作。

開始之前

確保盆栽盒的 HTML 與 CSS 已經編輯完成。這堂課會新增拖曳植物進出盆栽罐的功能。

課題

在專案資料夾中,新增檔案 script.js。 匯入該檔案在 HTML 檔 <head> 的部分:

	<script src="./script.js" defer></script>

筆記:匯入外部 JavaScript 檔案到 HTML 檔案須使用 defer,讓 JavaScript 檔案只有在 HTML 被完全載入時才被執行。你也可以使用 async 的屬性,允許 JavaScript 在解析 HTML 檔時就被執行。這項專案中,我們必須確保 HTML 的元件被完整建立後才允許使用拖曳功能。


DOM 元素

我們要做的第一件事是建立 DOM 下,要被操控的物件的連結。在專案例子中,我們有罐子外的十四株植物等著被拖曳。

課題

dragElement(document.getElementById('plant1'));
dragElement(document.getElementById('plant2'));
dragElement(document.getElementById('plant3'));
dragElement(document.getElementById('plant4'));
dragElement(document.getElementById('plant5'));
dragElement(document.getElementById('plant6'));
dragElement(document.getElementById('plant7'));
dragElement(document.getElementById('plant8'));
dragElement(document.getElementById('plant9'));
dragElement(document.getElementById('plant10'));
dragElement(document.getElementById('plant11'));
dragElement(document.getElementById('plant12'));
dragElement(document.getElementById('plant13'));
dragElement(document.getElementById('plant14'));

發生了什麼事?你正以 DOM 搜尋網頁檔內的物件,藉由 Id 作為依據來搜尋。回想第一堂 HTML 課中,我們可每一株植物一個專屬的 Id (id="plant1"),現在你就可以使用它。在辨別完每一株植物物件後,傳遞給待編輯的函式 dragElement,讓 HTML 物件可以被拖曳。

為什麼我們要以 Id 作為物件的參考?為什麼不以 CSS 的 class 作為參考?請參考以前的 CSS 課程回答此問題。


閉包(Closure)

現在,你已經準備好要建立 dragElement 閉包,建立包在外部函式內的內部函式組,在我們的例子中,會用上三個函式。

閉包在一或多個以上函式要存取外部函式時非常好用。看看下面的例子:

function displayCandy(){
	let candy = ['jellybeans'];
	function addCandy(candyType) {
		candy.push(candyType)
	}
	addCandy('gumdrops');
}
displayCandy();
console.log(candy)

這項例子中,函式 displayCandy 包住另一個函式 addCandy新增新的糖果樣式到已存在的矩陣當中。當執行這段程式時矩陣 candy 會被認作是未定義,因為它是函式的本地變數。

你能讓矩陣 candy 被存取嗎?試著將它移到閉包外面。這時,矩陣會變成全域變數,取消閉包內的存取限制。

課題

在檔案 script.js 的元素宣告下方,新增函式:

function dragElement(terrariumElement) {
	//set 4 positions for positioning on the screen
	let pos1 = 0,
		pos2 = 0,
		pos3 = 0,
		pos4 = 0;
	terrariumElement.onpointerdown = pointerDrag;
}

dragElement 藉由程式定義的參數取得 terrariumElement 物件。之後,設定一些位置 0 的變數給函式內的物件使用。它們是本地變數,給每一個進到拖曳函式內的物件操控。盆栽盒會被這些拖曳物件填充,我們的網頁應用必須要持續追蹤這些物件的位置。

此外,進到函式的 terrariumElement 也被新增了 pointerdown 事件,它是管理 DOM 的其中一項網頁 APIs。當按鈕按下時,或是在我們案例中,一個拖曳物件被點擊時,onpointerdown 事件就會被觸發。這個事件處理器(event handler)皆運作在網頁與行動瀏覽器上,只有少部分的例外。

事件處理器 onclick支援更多的瀏覽器。為什麼我們不在這邊使用它? 想想看我們在這此建立的視窗互動類型。


函式 pointerDrag

terrariumElement 已經準備好被拖曳了。當觸發 onpointerdown 事件時,函式 pointerDrag 會參與其中。新增這項函式在程式碼 terrariumElement.onpointerdown = pointerDrag; 下方:

課題

function pointerDrag(e) {
	e.preventDefault();
	console.log(e);
	pos3 = e.clientX;
	pos4 = e.clientY;
}

許多事情會發生。首先,你使用 e.preventDefault(); 取消掉 pointerdown 原先的預設事件。這樣你可以操作更多的介面行為。

回到你建立的程式碼中,試著刪除 e.preventDefault() 並執行看看,發生了什麼事?

第二,用瀏覽器打開 index.html 並調查我們的介面。當你點擊植物時,你可以發現 'e' 事件被觸發了。專研一下,一個 pointerdown 事件會產生多少資訊!

接下來,紀錄本地變數 pos3pos4 被設定為 e.clientX 和 e.clientY。你可以在觀察面板中會發現 e 的數值。這項數值取得按下植物瞬間的 x 與 y 座標資訊。為了全面的控制植物行為,在拖曳植物時,我們會持續更新座標資訊。

將整個網頁應用建立在一個大閉包下,會讓程式碼變得比較清楚嗎?如果沒有,你有其他方法管理這十四株可拖曳的植物嗎?

增加初始化函式,在程式碼 pos4 = e.clientY 下方加上下列兩行事件處理:

document.onpointermove = elementDrag;
document.onpointerup = stopElementDrag;

現在,在游標拖曳時,你的植物能跟著你的游標走,而在你取消點擊時停下來。onpointermoveonpointerup 也是 onpointerdown 類型相同的 API。然而現在介面會出現錯誤訊息因為我們還沒建立函式 elementDragstopElementDrag

函式 elementDrag 與 stopElementDrag

新增兩條內部函式在閉包中,它們會處理拖曳植物與停止拖曳的事件。你希望你可以拖曳任何一株植物且放在螢幕上的任一地方。介面並沒有強制你盆栽盒的配置格式,你可以自由地增加、移除與移動盆栽罐內的植物。

課題

新增函式 elementDrag 在函式閉包 pointerDrag 宣告列的正下方:

function elementDrag(e) {
	pos1 = pos3 - e.clientX;
	pos2 = pos4 - e.clientY;
	pos3 = e.clientX;
	pos4 = e.clientY;
	console.log(pos1, pos2, pos3, pos4);
	terrariumElement.style.top = terrariumElement.offsetTop - pos2 + 'px';
	terrariumElement.style.left = terrariumElement.offsetLeft - pos1 + 'px';
}

在這條函式之前,你編輯了四個本地變數位置的初始值在外部函式中。這邊又做了什麼事?

當你拖曳物件時,你更新數值 pos1pos3 減去現在的 e.clientX,而 pos3 在之前被初始化為為 e.clientX。同樣的行為套用在 pos2上。之後,你更新 pos3pos4 到新的 XY 座標點位置。你能在 console 下看到數值在拖曳下更新的情況。我們也更新植物的 CSS 造型中的定位點為 pos1pos2,比較植物左上方座標點與新座標點的關係。

offsetTopoffsetLeft 是 CSS 的屬性,決定物件與它父關係物件的定位關係。父關係物件可以是任何元素,只要它的定位屬性不為 static

這些座標點的計算式讓你成功校整了植物與盆栽盒之間的行為。

課題

最後的課題是在介面上新增 stopElementDrag 函式,我們將它加在函式閉包 elementDrag 的正下方:

function stopElementDrag() {
	document.onpointerup = null;
	document.onpointermove = null;
}

這條小函式重制 onpointeruponpointermove 事件,這樣你可以重新開始該植物的拖曳事件,或是拖曳新的植物。

如果不將這些事件設為空值時,會發生什麼事?

我們終於完成了這項專案!

🥇 恭喜你!你建立了一個漂亮的盆栽盒。盆栽盒成果圖


🚀 挑戰

新增新的事件處理器到你的閉包中,讓你能對植物做更多的事情。舉例來說,雙擊植物讓它排列到最上層。發揮你的創意吧!

課後測驗

課後測驗

複習與自學

在螢幕上拖曳物件看似簡單,但依照不同的目的與實現方法會遭遇到不同的問題。事實上,這邊有一份關於你可以嘗試的拖曳 API。我們沒在專案中使用是為了建立不一樣的實現方法,試著使用這些 API 到專案中,看看你能完成什麼。

W3C 文件MDN 網頁文件上取得更多關於 pointer 的事件。

記得習慣性用 CanIUse.com 檢查網頁的瀏覽器兼容性。

作業

用 DOM 做更多事