第 09 章 - 事件處理
上次更新日期 2020/12/01
我們在前面的章節中,其實使用了不少的『事件』處理功能了,包括使用於滑鼠的 onClick 事件,以及鍵盤的 keydown 事件。 這些事件的觸發,可以帶動程式在網頁裡面進行某些任務,這就是 javascript 的事件囉。還有哪些事件可用? 就讓我們來瞧一瞧。
學習目標:
- 理解如何進行事件的監聽,除了 onclick 之外,學習 addEventListener 的用法
- 了解滑鼠事件的應用
- 使用 HTML5 的 drag and drop 事件處理拖曳行為
- 使用鍵盤事件來處理射擊遊戲等任務
9.1: JavaScript 的 event 相關說明
當我們在作業系統或者是瀏覽器上面進行某些舉動時,就會發生某些特定的影響效應,這是所謂的事件驅動 (event driven)。
- 事件驅動 event driven
在 windows 作業系統當中,任何視窗都有一個唯一的代碼,這就好像程序一般,都有個 PID 的概念。而系統會有個程序持續不斷的監視這些視窗的活動, 當這些視窗有任何的事件發生時,該監控程序就會提供相對應的反應來處理這些活動。舉例來說,使用者進行視窗的拖曳、按下鍵盤或滑鼠、 開啟新的視窗等,監控程式就會根據該次事件給予相對的活動。這種根據用戶操作來的反應行為模式,稱作事件驅動模式 (event driven)。
所以,說穿了,就是你進行什麼動作,就需要有個相對的程式來提供反應,這樣讓你可以順利的操作該系統,那就是事件驅動 (event driven)。 簡單的說,我們前面的章節,當你點擊 (onclick) 某個元件時,該元件會提供某些程式碼,這樣的反應,就稱事件驅動。 所以,同學們已經做過了!^_^
瀏覽器其實都有一直在監控使用者的動作,JavasScript 則可以根據這個監控的行為,來進行額外的某些指令碼,一項一項分別持續處理這樣, 這就是事件驅動的模式。而我們也是透過這樣的事件來撰寫 javascript 的程式碼,讓這些程式可以完成我們的需求。而根據事件的觸發與事件後續的動作, 大致上又將事件的影響分為傳送者與接收者:
- 事件發送者 (event sender, event generator, event source):觸發事件的物件
- 事件接收者 (event receiver, event consumer):接收事件後的物件
這些都只是簡單的定義而已,大家只要知道這些事件的專有名詞而已,事實上,之前已經有碰過啊,這裡只是彙整文件說明。 接下來就得要了解,那麼有哪些常見的事件在瀏覽器與 javascript 裡面會發生呢?
- 傳統的 HTML 瀏覽器事件
底下為網頁、瀏覽器上面常見的事件,裡面有幾項大家應該都已經撰寫過相關的程式了!先來談談與視窗 (window), 亦即跟瀏覽器本身及網頁整體比較有關的事件:
- load (載入):瀏覽器完成載入網頁時,會觸發的事件
- unload (卸載):瀏覽器移除網頁或是窗時,會觸發的事件
- focus (聚焦):例如透過鍵盤 [tab] 按鈕,讓某個元件聚焦時,會觸發的事件
- blur (模糊):跟 focus 相反,當物件由聚焦變成沒有聚焦時,會觸發的事件
- error (錯誤):當發生錯誤的時候,會觸發的事件
- scroll (滾動捲軸):當瀏覽器捲動畫面時,會觸發的事件
- resize (變更大小):當瀏覽器的視窗大小改變時,會觸發的事件
再來看看跟鍵盤的動作比較有關的事件:
- keydown (按下按鍵):某個元件被按下鍵盤按鍵時,會觸發的事件
- keyup (放開按鍵):某個元件放開按鍵時,會觸發的事件
- keypress (按下放開):某個元件被按下鍵盤按鈕然後放開時,會觸發的事件
再來就是跟滑鼠有關係的事件,用最多的就是我們經常使用的 click 事件了。
- click (按下滑鼠):在某個元件上面按下滑鼠按鍵 (通常是左鍵) 時,會觸發的事件
- dbclick (雙擊滑鼠):同 click,只是為雙擊滑鼠按鍵,會觸發的事件
- mousedown (按下滑鼠):在某元件上按下滑鼠任意鍵,就會立刻觸發的事件 (不用放開)
- mouseup (鬆開滑鼠按鍵):與 mousedown 相反,放開滑鼠任意鍵,就會立刻觸發的事件
- mouseover (滑鼠游標):滑鼠游標移動到這個物件上面時,就會觸發的事件
- mousemove (滑鼠游標):滑鼠游標在這個物件上面移動時,就會觸發的事件
- mouseout (滑鼠游標):與 mouseover 相反,移開這個物件時,就會觸發的事件
- mousewheel (滾輪):滾動滑鼠滾輪時,會觸發的事件
滑鼠事件裡面比較有趣的是 mousedown / mouseup / click 這三個事件,到底有什麼分別啊?一般來說,我們使用的 click 事件, 大部分指的是按下左鍵之後鬆開,才會去進行的事件任務,而 mousedown 則是按下按鍵的那一刻,就開始運作的事件,無論是左鍵、右鍵還是中鍵。 至於 mouseup 則是鬆開按鍵後要去進行的事件。
另外,還有跟表單相關性比較高的事件!這些事件最好放置到 <form> 的元件裡面,比較適當!畢竟因為是表單啊!
- submit (送出):送出表單的時候,會觸發的事件
- reset (重置):清除表單時候,會觸發的事件
- select (選擇):大致上與 select 元件有關,選擇某些 option 或文字欄位時,會觸發的事件
- change (改變):當表單上面的選擇有改變時,會觸發的事件
- focus / blur :跟視窗事件一樣,就是某個表單上的元件在聚焦與模糊的情境下,會觸發的事件
通常我們使用的事件就是這一些,當然還有許多有趣的事件,包括 HTML5 事件、DOM 事件與觸控事件等,那就未來慢慢用到再來處理。 這些事件從瀏覽器上面被觸發,我們可以撰寫事件處理程式來進行對應的工作 (event handler),我們也能撰寫監聽事件的腳本程式, 來持續監聽事件並回應該事件 (event listener)。
另外,除了在 HTML 原始碼裡面使用類似 onclick="myfunction()" 之類的方式來呼叫函數之外,也可以透過 javascript 裡面直接這樣撰寫:
# 方法一: function myfunction() { process... ... } <tag onclick="myfunction()" id="myobj" /> # 方法二: myobj = document.getElementById('myobj'); myobj.onclick = myfunction; function myfunction() { process... ... }
- 將 unit08-3-4.php 照片淡出淡入另存為 unit09-1-1.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將 HTML 原始碼裡面,在 body 的部份,將 onload 函數取消,改以進入 javascript 的程式碼內,
加上底下這行來處理網頁載入後立刻進行的函數 (透過視窗事件):
window.onload = mymain;
- 增加一個名為 rendergo() 的函數,內容為進行 timer,直接將 mymain() 裡面的 setIntervla 複製過來即可。
- 增加一個名為 renderdown() 的函數,內容為 clearInterval(timer) 即可。
- 修改 mymain() 函數的內容:
- 讓 myfig 元件增加 onmouseover 時,執行 renderdown 的函數,亦即滑鼠移動到圖片上,圖片就不會淡出了!
- 讓 myfig 元件增加 onmouseout 時,執行 rendergo 的函數,亦即滑鼠移出圖片後,圖片開始淡出淡入變化
- 取消 timer,但是增加執行 rendergo() 函數的運作 (將相同的指令程式碼集中管理)
上面的範例中,我們可以學會不用在 HTML 的原始標籤裡面放置 onclick, onmouseover 等指令,取而代之的是直接在取得元件控制權後, 以事件物件的方式來進行設計。此外,上面的範例也能了解,某些網頁的變動時,我們可以使用類似的功能將圖片暫時固定不動的原因與方式。
- addEventListener 的事件處理模式
除了上述的事件處理方式之外,還有另一個方式,那就是透過 addEventListener 函數來處理。這個函數的處理方式如下:
myobj = document.getElementById('myobj'); myobj.addEventListener('click', myfunction, false|true); function myfunction() { process... ... }
在 addEventListener 當中,那個事件名稱就沒有含有 on 這個關鍵字,因此就得要使用 click, mouseover, keydown 等事件名稱, 而不是使用 onclick 之類的事件名稱。至於第二個欄位,那就是函數名稱,一樣不能加上括號喔!否則會失敗。最後一個是事件的執行範圍, 表示當該事件發生時,外層元件是否需要同步處理之意。預設為 false,不寫也就是 false 的意思。
- 將 unit09-1-1.php 另存為 unit09-1-2.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 只要修改 mymain() 函數內的資料即可,亦即將 onmouseover 與 onmouseout 那兩行修改成為使用 addEventListener 的方法即可。
- 最後整體呈現的方法會與原本的暫停方式相同。
- removeEventListener 的事件處理模式
想像一個案例,如同上面的照片淡出淡入功能,如果想要讓該照片在經過大約 3 次的 mouseover, mouseout 之後, 就放棄事件處理,而讓照片自己一直輪換下去,不再暫停,那該如何是好?意思就是說,我想要『移除』事件監聽程式。 這時,可以透過 removeEventListener 來處理啊!該函數內容的設定則與 addEventListener 完全一樣!
- 將 unit09-1-2.php 另存為 unit09-1-3.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 在全域變數的地方,設定名為 stopnu 的變數,這個變數給予初始值為 0,代表滑鼠尚未經過圖片。
- 在 renderdown 函數內,加上入 stopnu 的變數 +1 的語句
- 在 rendergo 的函數內,加入判斷式,當發生 stopnu >= 3 時,就停止 mouseover 與 mouseout 的事件監聽。
- 測試一下,在經過三次的暫停後,滑鼠指向應該就會失敗了!測試看看。
9.2: 滑鼠事件與資料拖曳行為
滑鼠事件就如同前一小節談到的,有 mouseover, mouseout, mousemove 等。我們在寫一些遊戲時,很可能需要知道滑鼠的游標所在。 那如何知道滑鼠的游標所在呢?其實就與 JavaScript 對『座標』的定義有關。事實上,根據定義,我們會需要知道幾種位置, 包括 pageX/Y, clientX/Y, screenX/Y 。基本上, screenX, screenY 指的是與『螢幕』有關的定位,這與網頁本身比較無關, 所以,撰寫遊戲時,不太會用到 screenX, screenY。但是如果是用在開啟新視窗,或許就有可能會用到這個定位。若以新開視窗為例, 底下是常見的可能會用到的物件參數:
- window.screen.width:螢幕的寬度
- window.screen.height:螢幕的高度
- document.body.clientWidth:瀏覽器視窗的寬度
- document.body.clientHeight:瀏覽器視窗的高度
- 新增 unit09-2-1.php 的檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將 這個檔案內容 加入 unit09-2-1.php 當中,並先查看瀏覽器顯示的狀態。
- 因為可能會有不同的函數使用到螢幕的寬度與高度,因此宣告 mysx, mysy 變數,分別代表螢幕的寬與高。
- 使用 window.onload = mymain; 設計視窗載入之後主動執行 mymain 函數。
- 在 mymain() 函數內設計:
- 取得 mysx 為螢幕的寬度,注意,需要是整數的數值
- 取得 mysy 為螢幕的高度,注意,需要是整數的數值
- 宣告 mycx 為視窗的寬度,注意,需要是整數的數值
- 宣告 mycy 為視窗的高度,注意,需要是整數的數值
- 取代 res 這個 id 元素的內部 HTML 資料,顯示出螢幕的 (寬, 高) 以及視窗的 (寬, 高) 數值。
- 使用 addEventListener 的方式,讓 opennew 在滑鼠 click 時,可以啟用 opennew 函數。
- 在 opennew() 函數內設計:
- 設計 features 變數,變數內容為類似『 left=800, top=30, width=800, height=300 』之類的資料, 其中 left, top 為新開視窗的左上角定位點,而 width, height 則是視窗的長與寬。
- 以上數的方式,啟動一個名為 mywin 的新視窗,且不需要給予 url 的數值。關於 window.open 的方法, 可以參考 7.1 小節的介紹。
- 透過 document.write 的方式,將 features 的內容寫入到新視窗上。
因為螢幕的寬、高指的是跟螢幕有關,實際上,我們設計網頁內容,還是需要與瀏覽器視窗、網頁內容有關才對! 所以,比較經常會用到的『座標』,大概就是整體網頁的位置: pageX, pageY,以及相對於瀏覽器可視範圍內的 clientX, clientY 兩個座標才對。
如果想要讓你的瀏覽器內的網頁 (document) 能夠進行滑鼠移動事件的偵測,可以寫這樣:
document.onmousemove = mydetect; function mydetect(mye) { var myevent = mye || window.mye; var x = myevent.clientX; .... }
在 mydetect 函數中,傳來的變數 mye 則是當時的滑鼠特性,透過 myevent 抓取成為視窗參數, 新的瀏覽器可以直接將上述的 mye 當作是滑鼠事件傳輸者,但是如果是舊版的 I.E. 瀏覽器,就得要透過 window.mye 來取得參數。 之後再透過 clientX, clientY 等,即可取得座標了。
- 新增 unit09-2-2.php 的檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將 這個檔案內容 加入 unit09-2-2.php 當中,並先查看瀏覽器顯示的狀態。
- 在 style 樣式內,增加幾個特殊的定義:
- 針對 body:新增高度為 3000 像素
- 針對 res:位置為固定 (fixed)、定位點左側 0、定位點底端 0、給予 1 像素的框線
- 增加整份文件 (document) 的滑鼠移動事件,指定處理 mymain 函數。
- 在 mymain() 函數內:
- 定義一個名為 e 的傳輸變數名,亦即 function mymain(e) 的語法
- 定義名為 myevent 的變數,取得 e 的參數
- 設定 x, y 分別取得 myevent 的 clientX 與 clientY
- 設定 xx, yy 分別取得 pageX 與 pageY
- 讓 res 的 innerHTML 呈現兩種座標。
- 拖曳行為 (drag and drop)
將資料在網頁上面拖曳,而不是單純的 keyin 資料,會讓使用者的界面看起來直覺很多!Javascript 搭配 HTML5 的 API, 有相當多的拖曳資料可供參考,不過,在這個小節裡面,我們先針對原生的 JavaScript code 來看看如何讓一個圖片或方塊, 可以在整個網頁上面進行拖曳的行為。
所謂的『拖曳 (drga and drop)』行為,其實就是『(1)當滑鼠點擊下去時,若點擊的就在圖片上,(2)然後沒有放開滑鼠按鍵並進行移動時, (3)就將圖片的座標根據與滑鼠的座標相對位置進行移動』,大致上就是這樣而已。而要達成這樣可以在網頁上面胡亂移動的規則,就得要 CSS 的 position 為 absolute (絕對位置) 才能提供這樣的功能。
因此,在實做全部都是 JavaScript code 的拖曳時,得要針對整個網頁 (document) 去監聽滑鼠事件,而不是針對圖片或方塊物件去監聽滑鼠事件, 這個行為得要注意到!那怎麼知道滑鼠點擊的時候是剛好在圖片上呢?其實可以透過底下的位置去討論:
所以,滑鼠座標在 (fx, fy) 以及 ((fx+fw),(fy+fh)) 之間時,就是按住該元件了!那滑鼠座標可以使用 .clientX 與 .clientY 來取得, 這樣就能夠了解座標位置系統,來得到我們所需要的環境!接下來,就是 mousemove 的事件,讓元件的 left 與 top 樣式進行數據的變更, 基本上,應該就能夠進行物件的拖曳行為了!來玩一下:
- 新增 unit09-2-3.php 的檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將 這個檔案內容 加入 unit09-2-3.php 當中,並先查看瀏覽器顯示的狀態。
- 設計讓整個網頁載入後,就立刻執行 mymain 函數 (不要在 body 裡面新增的用法)
- 宣告全域變數,包括 myfig, myfig2, res, mx, my, fx, fy, diffx, diffy 等
- 宣告全域變數,包括 mcoor, fcoor 兩個未來會寫座標的變數
- 宣告 figgo = 0 這個全域變數,用來設計『是否有按到目標圖案』的設計,預設值 0 為沒有按到。
- 增加 document 的 addEventListener ,當發生 mousemove 事件時,呼叫 mmove 函數 (移動控制)
- 增加 document 的 addEventListener ,當發生 mousedown 事件時,呼叫 mdown 函數 (判定是否按到圖案)
- 增加 document 的 addEventListener ,當發生 mouseup 事件時,呼叫 mup 函數 (判定不要再移動)
- 設計第一個函數, mymain() 函數,內容大致上有:
- 取得 myfig 元素的控制權,指定為 myfig 這個全域變數
- 取得 myfig2 元素的控制權,指定為 myfig2 這個全域變數
- 取得 res 元素的控制權,指定為 res 這個全域變數
- 額外指定 myfig.style.left 為 0 ,且 myfig.style.top 為 100px 的模樣
- 增加 myfig 的 mouseover 事件監聽,會主動去呼叫 fover 的函數
- 處理 fover() 函數的設計,這個函數的目的在處理 (1)取得目前圖片的座標 (2)替換 res 元素的 innerHTML 內容,所以:
- 因為圖片的座標會一直被呼叫,所以額外執行 figxy() 這個函數,此函數等等設計
- 變更 res 的 innerHTML,內容為 fcoor 與 mcoor 這兩個座標資料 (等等加入)
- 設計 figxy() 函數,重點在取得圖片的座標位置:
- 取得 myfig.style.left 的『整數』型態,然後將數值帶給 fx 變數
- 取得 myfig.style.top 的『整數』型態,然後將數值帶給 fy 變數
- 設計 fcoor 內容為: "(" + fx + ", " + fy + ")"
- 開始處理 mmove(e) 這個函數,這個函數的目的 (1)取得滑鼠座標 (2)若有按下圖片時,變更圖片位置。這個函數需要與 mdown 搭配才能執行。
- 設計 mye 這個變數為 e 的內容
- 透過 mye.clientX 來設計 mx 變數內容,同理設計出 my 變數內容
- 設計 mcoor 為 "
(" + mx + ", " + my + ")" - 判斷,當 figgo 為 1 時,進行如下行為:
- 執行 fover 變更座標值
- 修改 myfig 的 left,成為 (mx+diffx) + "px" 的位置,其中 diffx 為上面談到的滑鼠與圖片座標之間的相對位置
- 修改 myfig 的 top,成為 (my+diffy) + "px" 的位置
- 開始處理 mdown() 函數:
- fw 為 myfig2 的寬度取整數
- fh 為 myfig2 的高度取整數
- 依據本題目上方的說明,設計出 mx 與 my (滑鼠座標) 在圖片上面的定義,並指定:
- diffx 為 fx - mx
- diffy 為 fy - my
- figgo 為 1
- 最後才處理 mup() 函數
- figgo 設定為 0
經過測試之後,總是覺得怪怪的!為什麼會這樣呢?這是因為 HTML5 以後,瀏覽器廣泛的加入了很多的 API 支援,因此瀏覽器本身也具有拖曳的支援! 因此,當我們使用純 JavaScript code 進行拖曳時,瀏覽器就會同時進行兩個拖曳的行為 (Javascript code 與瀏覽器自己支援的拖曳), 所以就產生上面的困擾!明明是可以拖曳,但是就是怪怪的。此時,只要在 mymain 函數內,加上這個段落取消瀏覽器自己的拖曳功能,就能成功了!
myfig.ondragstart = function() { return false;};
意思就是說,取消 dragstart 這個事件的監聽之意!單純使用 JavaScript 自己的拖曳程式碼!現在再次拖曳看看,應該就可以發現順暢的移動了!
9.3: HTML5 的拖曳事件處理
在上述的滑鼠拖曳事件中,可以簡單的進行拖曳的行為。但是,如果想要將某個元件直接丟進某個元件當中,舉例來說, 剪刀石頭布用拖曳的方法來處理,而不是用點擊的方式,這樣要如何處理?如果還是使用這些 mouse 事件,恐怕得要撰寫更多的程式碼才行! 真是傷腦筋!
事實上,HTML5 是有提供滑鼠拖曳功能的 API 的,這個稱為『 drag and drop 』事件,跟上頭一樣,這是瀏覽器的標準事件了! 所以,你可以透過『 ondragstart = function() {}; 』之類的方式來啟用事件的監聽與處理喔!
整個拖曳的行為,基本的項目其實是:拖曳來源物件、拖曳目標物件這兩個,然後你必需要設計:
- 拖曳來源物件:拖曳來源物件需要可以被拖曳,因此,需要在 HTML 裡面增加『 draggable="true" 』這個屬性才行。
- 拖曳目標物件:一般瀏覽器預設不准資料被拖曳到任何元件上,因此,你就必需要指定 dragover 事件,將該事件取消不准拖曳的預設參數, 亦即在物件上加上 .preventDefault() 這個方法即可。
上面兩個資料是必要條件!另外,與滑鼠拖曳事件不同,這個 HTML5 的 drag 事件其實是『將某個元件丟到某個元件去』的意思, 並不是讓你在瀏覽器上面亂丟東西!這個很重要喔!完全是不同的狀態!也因為這個東西是一個蘿蔔一個坑,因此很適合製作一些簡易的遊戲, 讓使用者直接拖曳資料或答案到正確的方塊裡面去!這是相當好用的工具!
- 拖曳行為
拖曳行為的事件,如果針對拖曳來源物件與拖曳目標物件來說,會影響到的事件分別是:
事件 | 拖曳來源 | 拖曳目標 |
dragstart(開始拖曳事件) | V | |
drag(拖曳中) | V | |
dragenter(進入目標) | V | |
dragover(經過目標) | V | |
dragleave(離開目標) | V | |
drop(滑鼠按鍵放開) | V | |
dragend(結束拖曳) | V |
一定要設計的拖曳事件,必須要有 dragstart, dragover, drop 這三個事件,那麼如何取得被拖曳的物件呢?其實可以透過底下的方式來設計:
document.addEventListener('dragstart', mstart); function mstart(mye) { mye.dataTransfer.setData("text", mye.target.id); return false; }
當產生 dragstart 事件時,就呼叫 mstart 函數,在 mstart 函數內規範 mye 事件物件,這個物件的目標屬性 (target) 裡面的 id, 就可以將該 id 名稱寫入到該事件傳輸時的資料當中 (setData),使用的函數為 dataTransfer ,方法用 setData!注意大小寫。 事實上,幾乎所有的拖曳事件,通通是透過 dataTransfer 函數來處理的!而這個函數可以運用的方法主要有:
方法 | 說明 |
setData(format,data) | 基本的資料格式為文件 (text),也可以是網址 (url),後面的 data 指的就是放入的資訊,
一般而言,大部分都用事件的 target.id 或 target.tagName 等等,分別取得 id 或 tagName(標籤名),也分別取得單一物件或陣列物件。
一般使用上,大多數還是使用 id 比較多!比較好整理。 .setData("text",event.target.id); |
getData(format) | 與 setData 相呼應,主要用在 drop 事件當中。就是將 setData 傳來的資訊抓下來,
以上面的範例來說,當使用 getData("text") 時,就會回傳該物件的 id 名稱,之後我們可以透過 document.getElementById 來取得這個元素的控制權,
最終可以將該元件直接以 event.target.appendChild(這裡) 來新增物件,這樣就有拖曳的效果了! .getData("text") |
不過我們還是得要注意,一般一開始,瀏覽器除了圖片 (img, image) 之外,基本上不允許其他元件在瀏覽器的其他元件上面進行拖曳的! 而同時,瀏覽器可能也會進行一些額外的重導向 (redirect) 的行為,這些行為可能都會導致整個拖曳的事件變得怪怪的。 因此,底下兩個函數,很多時候也需要注意一下,用在拖曳的事件裡面才能讓拖曳順利進行下去:
- event.preventDefault():取消瀏覽器預設不能進行『拖曳放置』的行為,這才能讓原本不被支援的元件,也能進行拖曳!
- event.stopPropagation():取消瀏覽器的 redirect 行為,可以避免瀏覽器過多的協助,反而造成拖曳事件的問題。
接下來,讓我們先來測試一下簡單的拖曳,亦即單一容器放置與單一可拖曳元件:
- 新增 unit09-3-1.php 的檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將 這個檔案內容 加入 unit09-3-1.php 當中,並先查看瀏覽器顯示的狀態。
- 設計讓整個網頁載入後,就立刻執行 mymain 函數 (不要在 body 裡面新增的用法)
- 設計 mymain() 函數:
- 使用 paper 變數取得 paper 元素的控制權
- 使用 mytarget 變數取得 mytarget 元素的控制權
- 增加 paper 的監聽事件,事件名稱為 dragstart,會處理 mstart 函數
- 增加 mytarget 的監聽事件,事件名稱為 drop,會處理 mdrop 函數
- 增加 mytarget 的監聽事件,事件名稱為 dragover,會處理 mover 函數
- 設計 mstart(mye) 函數,開始處理拖曳事件的資料提供
- 使用 mye.dataTransfer.setData ,格式為 text,資料內容為 mye.target.id,提供物件的 id 資料
- 回傳 false
- 設計 mdrop(mye) 函數,處理當拖曳物件放到此處並放下 (drop) 的行為:
- 先以 mye.preventDefault() 方法,取消瀏覽器預設不能拖曳的問題
- 使用 mye.dataTransfer.getData,取得剛剛傳來的 id 資訊
- 新增變數名稱 data,內容為透過 document.getElementById 取得上述的 id 資訊
- 最終使用 mye.target.appendChild(data) 新增這個元素到方塊上
- 設計 mover(mye) 函數,處理拖曳物件預設狀態的取消:
- 使用 mye.preventDefault() 方法
- 回傳 false
關於上面範例當中的許多資料,你可以這樣看:
- mye;就是事件本身的意思
- target:而這個事件本身如果使用 target 時,就變成這個事件指向的那個元件。所以,在 mstart(mye) 當中的 mye.target ,就會代表被拖曳的那個元件,因此, mye.target.id ,就變成是,被拖曳的那個元件的 id 名稱。
- .setData:代表塞入拖曳的行為當中的那個資料,就是一份文字資料,負責傳輸 id 名稱而已!
最終,你就可以將左側的圖形拉動到右側去放下。這就是最簡易的 HTML5 滑鼠拖曳事件的 API 使用方式。不過,你會發現, 怎麼只能從左邊拖曳到右邊呢?這是因為我們只有放行右側的方塊具有 drop 與 dragover 的事件功能,其他的方塊沒有給予啊! 那,能不能給予不同的方塊都有可以放置的功能?也不是不行,透過底下的方式來處理即可。
- 多重拖曳行為
透過底下的方式來進行拖放事件的監聽:
# 全部頁面都可以拖曳,不過,不建議這樣做! document.addEventListener("dragover", mover); document.addEventListener("drop", mdrop);
這樣整個頁面 (document) 的元件都可以讓你拖曳了。不過,這樣做不太好,這是因為我們用這個 API 的拖曳行為,大部分都是為了一個蘿蔔一個坑, 用來設計檔案上傳、資料處理等等,倒不是單純為了好看而已。所以,建議還是針對某個網頁元件來進行設計會比較好。那如何處理不同元件的設計呢?
也許很簡單喔!你可以在 drag source 拖曳來源上面通通給予一個名為 draggable 的名稱,而在允許放置的方塊容器上面 (drop target) 給予 dropped 名稱, 然後透過抓取這兩個名稱,將各自的陣列給予各自的監聽,就能夠達到這些目的了。不過要注意,一般放置行為 (drop) 很可能會影響到拖曳物件, 來看底下這個範例:
- 將 unit09-3-1.php 複製成為 unit09-3-2.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將 這個檔案內容 修改到 unit09-3-2.php 當中,並先查看瀏覽器顯示的狀態。
- 在 mymain() 函數內,修改這些資料:
- 將原本的 paper, mytarget 變數名稱與內容刪除
- 重新抓取名為 dsource 的變數,內容為透過名稱 (getElementsByName) 取得 draggable 的元件
- 重新抓取名為 dtarget 的變數,內容為透過名稱取得 dropped 的元件
- 針對 dsource ,使用 dsource.forEach 的功能,給予變數 data1 ,然後針對 data1 給予個別的 dragstart 事件監聽, 使用 mstart 函數處理
- 針對 dtarget ,使用 dtarget.forEach 的功能,給予變數 data2 ,然後針對 data2 給予個別的 drop 與 dragover 事件監聽, 分別使用 mdrop 與 mover 函數處理
- 其他資料保留原始樣式即可。
接下來開始測試之後,你就會發現到,所有的拖曳可以在兩個容器之間傳輸了!
- 避免元件被覆蓋的困擾
不過上面的範例有點困擾喔!你可以測試將剪刀、石頭、布的圖片互相放置看看,咦!怎麼圖片會被吃掉?這是因為我們的事件處理當中, 有開放元件拖曳,結果似乎也導致這些資料可以互相放置到該元件內!就導致這個困擾了。如果你不想要讓元件之間可以替換或者是發生這方面的困擾, 或許也能嘗試加入停止動作來解決。不過,這也許並不是最佳解決方案,只是針對這個案例,可以這樣處理 (多個元件多個坑的情境下)。
# 將這幾行放入 dsource.foreach 內,避免 drop 在拖曳元件上面發生問題! data1.ondrop = function (mye) { mye.stopPropagation(); }
如同前面所述,這個 stopPropagation() 方法,可以讓瀏覽器停止某些轉遞的動作,所以,我們可以將這個行為加入到圖片的放置過程中, 就可以避免圖片被吃掉!讓我們的拖曳行為會比較合理。
- 一個蘿蔔一個坑
如果用在設計遊戲上,通常遊戲的關卡,他偵測的題目、項目一般是固定的,你可以填寫的答案也是固定的一項, 不會讓你填寫兩項資料啦,所以,當然需要一個容器裡面放置一個元件而已!放置太多反而會出問題。
因此,你在設計這方面的資料時,可能需要考量到,當把兩個物件同時放置到同一個方塊時,會出現什麼問題? 大致上這方面的問題解決的方式通常是::
- 若有圖片,且指標到該圖片,就讓拖曳物件的圖片與拖曳目標的圖片對調
- 若有圖片,且指標到容器方塊 (非圖片),同樣讓拖曳目標內的子元素(就是圖片)抽換到拖曳目標的容器內。
來實際測試一下,就能知道問題了:
- 新增檔名 unit09-3-3.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將 這個檔案內容 修改到 unit09-3-3.php 當中,並先查看瀏覽器顯示的狀態。
- 讓網頁載入之後,就能立刻執行 mymain 函數
- 宣告名為 mysource 的變數,等等要用來作為拖曳物件的上層物件之用。
- 建立 mymain() 函數,主要在取得 (1)拖曳物件與放置容器的控制權 (2)設定拖曳監聽事件等任務:
- 分析網頁資料,可以允許被拖曳的圖片都有 id 以及 name,且 name 都是 draggable,因此, 設定變數名稱 dsource ,內容為使用 getElementsByName ,針對 draggable 的名稱來抓取元素控制權, 最終 dsource 應該會是陣列
- 同上,設計 dtarget 抓取可放置物件的容器,亦即名稱為 dropped 的物件,同樣的, dtarget 也會是陣列。
- 加上 dsource 陣列物件的 forEach 方法,設定函數的變數名稱為 data1,而 data1 新增的事件監聽中, (1)dragstart 事件,啟用 fstart 函數, (2)drop 事件啟用 fdrop 函數。
- 加上 dtarget 陣列物件的 forEach 方法,設定函數變數名稱為 data2,而 data2 監聽事件中, (1)drop 事件啟用 cdrop 函數, (2)dragover 啟用 cover 函數
- 建立 fstart(event) 函數,主要針對圖片拖曳,給予圖片的 id 資料傳送
- 使用 dataTransfer 的 setData 方法,傳送圖片的 id 資料
- 因為很可能要進行圖片對調,因此,設定 mysource 變數,內容為 event.target.parentNode 這個物件!
- 建立 fdrop(event) 函數,當圖片被當成容器放置的情境下,我們需要讓圖片對調才行!
- 先增加 .preventDefault() 與 .stopPropagration() 兩個函數,避免瀏覽器的干擾
- 設計 data 變數,內容使用 dataTransfer 找出 getData 的圖片 id,然後根據 id 去取得該圖片的物件,所以,最終 data 是個圖片物件。
- 因為要變更圖片,所以設計 mytarget 變數,內容為取得目前這個 target 的 id 的元素控制權,因此 mytarget 也是個圖片物件
- 讓事件目標的上層節點,就是被放置的圖片所在的父節點,增加一個子元件,加入 data 這個子元件在裡面
- 是拖曳來源 (就是 mysource),增加一個名為 mytarget 的物件在裡面即可。
- 建立 cdrop(event) 函數,當拖曳來源放置到這個容器下,要先看有沒有圖片?若有,回傳,若沒有,直接放置
- 先增加 .preventDefault() 與 .stopPropagration() 兩個函數,避免瀏覽器的干擾
- 設計 data 變數,內容使用 dataTransfer 找出 getData 的圖片 id,然後根據 id 去取得該圖片的物件,所以,最終 data 是個圖片物件。
- 判斷 event.target.childNodes.length 是否為 0,如果不是 0 ,代表這個方塊內有其他元件存在!
- 我們這裡只允許一個元件。因此,在這個判斷式裡面,讓 mysource 增加一個子元件,子元件的內容應該就是 event.target.childNodes[0] 這個被放置方塊內的第一個子元件!
- 直接讓此 target 新增一個子元素,內容就是 data
- 建立 cover(event) 函數,內容只要填入 preventDefault() 即可!主要是要讓可被放置的元件被拖曳經過時, 可以順利顯示被拖曳的圖示。
在這個案例中,你仔細分析 fdrop 與 cdrop 兩個函數,會發現在當中的 target 代表的意義實在是不一樣,同樣是 event.target, fdrop 代表一個圖片,而 cdrop 代表一個 div 喔!要注意!要注意!
另外,你可能會想要將滑鼠拖曳事件與 HTML5 的 drag and drop 結合在一起來玩,不過可惜的是,這個物件要設定成什麼 position 呢? 會有許多問題!因此,最多只能使用 dataTransfer.setDragImage 的函數來處理。不過,目前這個函數的支援還不怎麼完整, 留待未來開發囉!
9.4: 鍵盤事件處理
有很多時候我們也是會用到鍵盤來做一些遊戲或者是互動,並不是只能用滑鼠而已。過去最簡單的互動遊戲,當然就是打小飛機! 這時,都會用方向鍵控制砲台來瞄準飛機,將飛機轟炸掉這樣。當然,砲台不能夠被打到!那如果沒有這麼複雜, 我們單純要來打小蜜蜂,可以一步一步來完成一下。
- 鍵盤事件最重要的就是分析按下什麼按鈕了?
通常,我們會透過 keydown 來分析,到底使用者按下什麼按鈕。先來理解一下,如果按下的按鈕是非數值或文字的, 通常我們會用到的可能就是方向鍵、Tab 按鍵、ctrl、shift、alt 等,這些按鈕基本上都有對應的號碼 (keyCode), 大概是這樣對照的:
按鍵 | keyCode |
---|---|
倒退鍵 (backspace/delete) | 8 |
Tab | 9 |
Enter | 13 |
Shift | 16 |
Ctrl | 17 |
Alt | 18 |
escape[esc] | 27 |
空白鍵 (spacebar) | 32 |
PageUp | 33 |
PageDown | 34 |
End | 35 |
Home | 36 |
左鍵 (left arrow) | 37 |
上鍵 (up arrow) | 38 |
右鍵 (right arrow) | 39 |
下鍵 (down arrow) | 40 |
插入 (Insert) | 45 |
刪除 (del) | 46 |
數值 0 | 48 |
數值 1 | 49 |
數值 2 | 50 |
數值 3 | 51 |
數值 4 | 52 |
數值 5 | 53 |
數值 6 | 54 |
數值 7 | 55 |
數值 8 | 56 |
數值 9 | 57 |
英文 A | 65 |
英文 B | 66 |
英文 C | 67 |
英文 D | 68 |
英文 E | 69 |
英文 F | 70 |
英文 G | 71 |
英文 H | 72 |
英文 I | 73 |
英文 J | 74 |
英文 K | 75 |
英文 L | 76 |
英文 M | 77 |
英文 N | 78 |
英文 O | 79 |
英文 P | 80 |
英文 Q | 81 |
英文 R | 82 |
英文 S | 83 |
英文 T | 84 |
英文 U | 85 |
英文 V | 86 |
英文 W | 87 |
英文 X | 88 |
英文 Y | 89 |
英文 Z | 90 |
現在,就讓我們來測試一下,看看如何處理砲台的移動吧!
- 新增檔名 unit09-4-1.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將 這個檔案內容 修改到 unit09-4-1.php 當中,並先查看瀏覽器顯示的狀態。
- 讓網頁載入之後,就能立刻執行 mymain 函數
- 宣告兩個全域變數,一個名稱為 fire,目的在取得 fire 元件控制權,一個是 step,初始值為 3,這個是砲台移動像素
- 撰寫 mymain() 函數
- 建立 fire 物件,內容為 fire 元件
- 修改 fire.style.left 數值為 275px
- 增加 keydown 的事件監聽,啟用 kdown 函數
- 增加 keyup 的事件監聽,啟用 kup 函數
- 撰寫監聽鍵盤按鍵的 kdown(event) 函數
- 如果 event.keyCode 為 37 ,那麼就執行 goleft 函數
- 如果 event.keyCode 為 39 ,那麼就執行 goright 函數
- step 重新指定為 6 ,這說的是,如果沒有 kup 函數,那麼砲台滑動的速度將會增加一倍
- 撰寫向左移動的 goleft 函數
- 設計 x 變數,內容為 fire.style.left 的『整數』型態
- 因為向左,所以 x = x - step
- 當 x 碰到牆壁 (位置為 0) 時,就不能向左移動
- 變更 fire.style.left 到最新的位置上
- 撰寫向右的 goright 函數,都跟 goleft 一樣,但是 (1)必須為加上 step (2)x 碰到牆壁應該是無法往右移動
- 撰寫監聽鍵盤按鍵的 kup(event) 函數
- 因為鍵盤放開了,所以 step 調整回初始值的 3 即可。
有了砲台之後,開始準備要處理發射砲彈,發射砲彈時,如果需要連續發射,那麼就得要使用不同的元件產生,此時恐怕會有重複名稱的問題。 因為每發砲彈要讓它自己跑,就得要有獨立的 id name 才行。所以,我們打算每次發射砲彈,就在 idname 上面 +1 這樣的型態來測試看看。 如此,就不會有砲彈名稱重複的狀況。只是,在設定 timeout 的函數時,得要特別注意到 id name 是可變動的,因此得要加上單引號來處置。
- 將 unit09-4-1.php 複製成為 unit09-4-2.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 如上所述,要了要累加 id name,因此,設定一個全域變數,名為 firenu,初始值為 0 即可。
- 因為砲彈只會在 playing 元件中跑,因此,同樣設定全域變數名為 playing,預計取得 playing 元素控制權。
- 修改 mymain 函數,新增一行,取得 playing 元素的控制權,使用 playing 變數名稱即可。
- 修改 kdown 函數,增加 keyCode 為 32 (空白鍵) 時,呼叫 firenow 函數,該函數在產生砲彈以及砲彈的 id 還有 style 等相關資訊。
- 新增 firenow 函數,目的在產生新的砲彈,並提供該砲彈初始化的參數:
- 取得 fire 元素的 x 座標,並且加上 25 ,這是因為砲彈要從 fire 元件的中央發出
- 設定 y 值初始化為砲台頂部,大概在 435 即可 (因為扣掉砲台高度)
- 設定 dot 區域變數,目的為新增元件『 document.createElement('P') 』
- 將 dot 變成 playing 的子元素 (appendChild)
- 設定 dot 的大小與形狀之 style,包括 width 與 height 都是 2px,框線實心黑色,padding 與 margin 都是 0 才好。
- 設定 dot 的位置的 style,包括 position='absolute', left=x+'px', top=y+'px' 等參數
- 讓 firenu 增加 (+1)
- 設定 dot.id 這個 id 名稱,變成是 'fire' + firenu 這樣的名稱情境
- 設定區域變數 target,內容就是 dot.id 即可。
- 呼叫砲彈自動往上跑的 firego(target) 函數,記得,一定要提供 id name,所以需要有 target 變數名稱在當中。
- 新增 firego(target) 函數,目的在 (1)取得 target 的名稱元件 (2)變更該元件的位置 (3)呼叫 timeout 反覆執行 (4)超出邊線就移除該元件
- 設定區域變數 myobj,內容為 target 這個 id 名稱的元素控制權
- 取得 myobj 的 x 座標 (left) 以及 y 座標 (top),並且取整數
- 讓 y = y -2 ,這樣座標就會往上跑
- 進入 y 值的條件判斷:
- 當 y >= 0 時,(1)讓 myobj 的 top 變更維新的 y 值,(2)設定 setTimeout 函數, 內容應該要是:『 setTimeout("firego('" + target + "')",10); 』這種格式才行!否則會失敗
- 若 y 值非上述條件,就從 playing 裡面移除 myobj 元件
你可以測試一下,就能發現到,可以連續射出砲彈囉!相當有趣的呢!
- 解決 keydown 事件的邏輯問題
在上面的案例中,如果你按下方向鍵不放,然後按下空白鍵,你可以發現到,怪了,砲台竟然停止了!這跟一般的遊戲想法不太一樣啊! 如此會造成按下方向鍵,卻沒有辦法連續移動的問題。這怎麼解決呢?
透過 google 大神,找到一篇有趣的討論,他的想法是,先宣告整體視窗的一組陣列變數,並且將該陣列內容以布林值來規範。 當按下某按鍵,該按鍵的代碼就會變成 true,放開之後就會是 false。之後每 0.05 秒偵測一次,就能夠抓到確定是按下 (true) 卻沒有作用的按鈕, 並予以開始提供執行。
只是,我們的連續移動速度會變快,從 3 像素增加到 6 像素,而 0.05 秒似乎是鍵盤連續按下的頻率,因此,我們可能需要加倍偵測的頻率, 所以,最終偵測的時間頻率應該要是 0.025 秒比較好。實際撰寫如下:
- 將 unit09-4-2.php 複製成為 unit09-4-3.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 如上所述,要了要累加 id name,因此,設定一個全域變數,名為 firenu,初始值為 0 即可。
- 在全域變數的環境中,指定名為 window.keysdown 的陣列變數,其內容主要規範 keyCode 為 37, 39, 32 的資料,寫法有點像底下這樣:
window.keysdown = { 37: false, 39: false, 32: false }
- 在 kdown(event) 函數中,增加一段程式碼,可以讓 keysdown 的變數變成按下 (true) 的情境:
if ( window.keysdown.hasOwnProperty(event.keyCode)) window.keysdown[event.keyCode] = true;
- 同樣的,在 kup(event) 函數中,又得要將該按鈕取消 (false):
if ( window.keysdown.hasOwnProperty(event.keyCode)) window.keysdown[event.keyCode] = false;
- 在全域變數的環境中,增加一個 timer,內容會有點像底下這樣,每 25 毫秒偵測一次按鍵按下否?
setInterval (function() { if ( window.keysdown[37] && step == 3 ) goleft(); if ( window.keysdown[39] && step == 3 ) goright(); }, 25);
再次測試後,你就可以發現,鍵盤的移動要順暢許多了!
- 產生飛碟之後,開始打掉飛碟
再來要設計的,就是在遊戲區塊裡面,產生一堆會被打掉的飛碟~這些飛碟必須是隨機產生的,所以又得要計算位置了。 此外,是隨機產生的,因此就得要定義這些飛碟的元素名稱,因為是一堆飛碟,所以可以透過名稱的定義來處理,不需要唯一的 id 名稱。 這個 name 的參數設定比較麻煩,不像 id 設定比較簡單。 name 的設定方式為:
myobj.setAttribute("name", "新的名稱");
隨機產生這些飛碟之後,再來根據砲彈的移動方向,找到 (1)飛碟的面積位置、 (2)砲彈的位置,如果砲彈的位置在飛碟的面積內, 那就將砲彈與飛碟通通移除!而且不執行 timeout 喔!否則會產生找不到 id 的困擾。那就來處理看看:
- 將 unit09-4-3.php 複製成為 unit09-4-4.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 建立新的名為 createufo() 的函數,這個函數要產生在遊戲區域上方 100px 內的空間,隨機產生 20 個飛碟
- 建立 maxx 變數,內容為取得 playing 元素的寬度 (clientWidth)
- 使用 for 迴圈,定義 i 值在 0 到 19 以內,然後進行:
- 隨機產生 x 軸,範圍在 0~maxx-50 之間,因為等等設計飛碟有 50 個像素寬
- 隨機產生 y 軸,範圍在 0~100 之間,x 與 y 都要取整數
- 建立區域變數 ufo,內容為產生新的元素,元素標籤為 P
- 將 ufo 增加入 playing 的子元件
- 設計 ufo 的大小與位置的 style ,包括 position='absolute', left 是 x+"px",top 是 y+'px',給予 border, 給予 bacgroundColor,設定 padding 與 margin 均為 0,設計寬度與高度分別是 50x10 像素。
- 設計 ufo 的 name 成為 'ufo' (用上面談到的 .setAttribute 處理)
- 在 mymain 裡面增加執行 createufo() 函數,然後重新載入,有沒有看到 ufo 了?
- 在 firego 這個函數內,設計修改『砲彈碰撞到飛碟』的事件,請在判斷 y 是否大於 0 的條件判斷式裡面,在執行了砲彈的 top 設定後,
加入如下的設計:
- 設計區域變數 ufo,內容為取得名為 'ufo' 的名稱 (getElementsByName) 的元件,因此 ufo 會是陣列
- 設計區域變數 hit ,初始值為 0 ,這個是定義『是否被射擊到』,0 是沒有, 1 是射擊到了。
- 進入 for 迴圈,定義 i 為 0 到小於 ufo.length 之間,然後:
- 定義 x1 為 ufo[i] 的 left 座標取整數
- 定義 x2 為 x1+50 (為啥?)
- 定義 y1 為 ufo[i] 的 top 座標取整數
- 定義 y2 為 y1+10 (為啥?)
- 判斷, x 位在 x1~x2 之間,且 y 也在 y1~y2 之間,就代表碰撞然後會:
- 設定 hit = 1
- 移除 myobj 以及 ufo[i]
- 跳脫迴圈
- 重新定位 setTimeout,需要在 hit 為 0 的時候才執行這個項目 (用 if 來定義)
9.5: 課後作業
- 9-5-1、寫出一個敲擊怪物的小遊戲
以這個網頁的內容為範本,建立一個初始化的環境,你可以自行修改網頁內容的 CSS 樣式, 讓它變得比較適合你自己的樣式。假設檔名為 unit09-5-1.html,這個檔案內,會有許多的全域變數,等等請自行決定是否要使用全域變數。 另外,因為網頁內會產生很多的怪物,因此,可能會使用到多個陣列在內,請自行宣告某些全域變數為陣列即可。
先設定網頁載入就執行的函數,我們課堂上經常稱為 mymain 的那個函數。
設定時間倒數計時器: 畫面上方的兩個方塊中,右邊方塊就是用來倒數的用途!你可以寫一個名為 showtime() 的函數,同時進行:
- 將一開始就設定的全域變數 (假設為 timeout),讓該數值 - 0.1 (亦即每 0.1 秒更新顯示一次)
- 將數值帶給 mytime 這個元素的 innerHTML 裡面。
- 回去更新 mymain,設定一個新的 setInterval,內容為每 0.1 秒執行一次 showtime() 函數。
設定怪獸產生器: 畫面中央的 area 元素要放置怪獸,且這個怪獸會在大約 3 秒內自己隨機找一個時間點出現,並不是固定 3 秒出現一次! 是『 3 秒內隨機自己跳出來』的意思!假設你寫一個名為 monster(nu) 的函數,這個函數可能會被好幾個計數器呼叫, 因此,建議加上一個變數,可以讓每個怪獸的函數資料獨立運作而不會互相干擾。
- 先取得 area 這個元素的最大寬、高 (clientWidth, clientHeight),取得這個數值的用途,是需要找出放置怪獸的位置, 所以,假設你的怪物預設大小為 50 像素,因此,上面的數值記得要減去 50 喔!
- 套用上面的最大值,透過亂數產生器,計算這隻怪獸的位置,產生 x[nu] 與 y[nu] 的數值,這就是每支怪獸的座標。
- 計算怪獸哪個時間點會跑出來 (設計 gogo[nu] 數值):底下會有一隻計算這隻怪獸出現的函數 (不在這個函數內),該函數將 3 秒鐘切成 30 段, 所以,我們這裡要計算出,怪獸會在 30 段時間的哪一段出現?這裡一樣使用亂數產生器,從 0~29 之間隨機產生一個整數數值, 帶給 gogo[nu] 這個陣列即可。
- 每支怪獸計時器的初始值 (設計 mygo[nu] 數值): mygo[nu] 的初始值應該是 0。mygo[] 就是每隻怪獸的累加等待時間, 等到 mygo[] 大於 gogo[] 時,就讓怪獸跑出來的意思。這個數值與剛剛的 gogo[] 是有關的!
- 最後,呼叫顯示怪獸的 showmonster(nu) 函數。
怪獸產生器-隨機產生用:撰寫 showmonster(nu) 函數,處理的方式為:
- 這隻怪獸已經等待多久了,亦即讓 mygo[nu]++
- 開始比較 mygo 與 gogo 誰比較大,當 mygo 小於 gogo 時,就設定 0.1 秒之後 timeout,且重複呼叫 showmonster(nu) 這個函數。 要特別注意, setTimeout 當中,函數的寫法可能要變成『 "showmonster(" + nu + ")" 』這樣的格式,才能讓 nu 以實際的數值更新。
- 若 mygo 大於 gogo 時,就進行:
- 設計 img 為新的圖片 (image) 物件
- 分別指定 img.src, img.style.position, img.style.left, img.style.top, img.style.width 等
- 以 area.appendChild 增加這個物件。
- 提供被點擊殺到怪獸的監聽事件:增加 img 這個物件的 click 監聽事件,若發生 click 時,執行 killme 函數
讓怪獸產生器生效:就是在 mymain 裡面新增 monster(nu) 的計數器即可。你可以寫一個 for 迴圈,假設指定 5 次, 就執行 setInterval 然後加入類似 monster(0) 這樣的函數名稱來執行即可。先指定 5 次,未來可以自己增加怪獸產生量。此外, 依據之前的測試,讓你的怪獸是 3 秒鐘產生一次。
打怪計數器:增加 killme(event) 函數,這個函數是發生到滑鼠點擊怪獸時,才會發生的事件,因此需要有 event 變數名稱(這個名稱可自己調整)。 目的很簡單,click 到怪獸,就將怪獸元件移除,並且讓計數器 +1 即可。移除怪獸的元件要用 area.removeChild(event.target) 喔!
倒數計時結束:在 mymain 裡面,增加遊戲結束的 setTimeout 函數,這個函數會在時間到了之後 (timeout*1000) , 清除所有的 Interval 計數器,同時收集所有的 img 標籤物件,將 click 的監聽取消 (removeEventListener)。
- 9-5-2、製作會下降的飛碟
以例題 9-4-4 為基底,重新設計一下,讓隨機產生飛碟的數量降低到 10 個左右就好,然後,大概每 3 秒鐘,整個飛碟會網往下移動大約 30 或 50 像素, 然後在原本的地方再次產生新的 10 個飛碟。等到飛碟撞到遊戲區域的底部時,遊戲就算結束。最終結算打掉幾部飛碟,同時結算發射幾發砲彈。 如果可以的話,將飛碟的圖示,從醜醜的方塊,替換成飛碟的形狀,會更好!
- 9-5-3、製作拖曳的配對遊戲
設計數個方塊,等待放置答案,然後在下方提供多個答案,必需要比對成功,才能夠給予正確的答案。例如,如果以太陽系的行星為例, 如下圖的狀態,那麼,當拖動圖片到正確的位置後,按下答題的按鈕,就可以去比對每一個方塊當中的圖片 id 是否正確? 若為正確,就計數加一,否則就不計數。但是,在確定方塊內的圖片 id 前,必需要確認方塊內有沒有東西! 所以,就會得到底下的三個圖示了:這一題不可能會有相同的作法,因為每個人的題目應該都會不一樣喔! 這個星球配對遊戲,就只是個範例而已!
9.6:參考資料
- 一篇挺不錯的 HTML5 drag and drop 部落格:https://pjchender.blogspot.com/2017/08/html5-drag-and-drop-api.html
- https://web.dev/drag-and-drop/
- keycode: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
- keycode: https://keycode.info/
- 連續按鍵的問題:https://stackoverflow.com/questions/20805869/continue-keydown-event-even-when-an-additional-button-is-pressed