作品練習 :Movie Seat Booking
本練習作品學習要點為:將轉換成array的資料轉換進localStorage本地端,並使其保留在返還回介面UI上
HTML
簡單的快捷輸入法
1 | <!--左側為縮寫輸入法/右側為輸入後顯示 --> |
關於label的用法
label本身為表達語意的標籤,在表單上,用法常見為<label for>
,以及與<select>
連結的典型用法,於下方練習的code之後說明。
練習作品中的code呈現如下:
1 | <div class="movie-container"> |
關於
舉例:
1 | <label for="male">Male</label> |
呈現如右:
關於select用法(典型與label結合的用法)
舉例:
1 | <label for="cars">Choose a car:</label> |
呈現如右:
參考自:
建立座位狀態
1 | <!--建立座位狀態 --> |
建立電影螢幕及建立座位
藉由class命名來隨機標示座位狀態。
1 | <!--建立電影螢幕 --> |
總價格顯示:
span做id命名,作為後續計算座位及價格
1 | <!--總價格顯示 --> |
html完成圖如下
CSS
字型套用與通用選取器
1 | <!--import css字型 --> |
body CSS樣式設定
1 | body { |
設定下拉式選單(select)樣式
1 | .movie-container { |
比較border:0 與 border: none
border:0;
瀏覽器對border-width、border-color進行渲染,佔用記憶體。border:none;
瀏覽器不進行渲染,不佔用記憶體。
請始終把border-style
屬性宣告到border-color
屬性之前,元素必須在改變顏色之前獲得邊框。
參考資料:
border:none和border:0px區別?
appearance屬性改變元素的外觀
此屬性能對任何元素的渲染風格改變,但因為目前這個功能各瀏覽器沒有統一,有些使用-moz-appearance
或者使用-webkit-appearance
,因此在設定上會兩者都設置,好讓各瀏覽器皆可運作,可運用於select選單、input輸入框、button按鈕等等。
可參考-moz-appearance property | -webkit-appearance property得知各瀏覽器支援的部分及appearance value。
以下列出幾個可能比較常使用的value:
1 | { |
參考資料:
使用CSS3的appearance屬性改變元素的外觀
MDN:-moz-appearance (-webkit-appearance)
appearance
transform與3D三維變化
以下分別介紹transform的rotateX()功能及
perspective功能。
transform的perspective效果
perspective是transform 3D轉換效果
在撰寫程式碼的時候,一度很疑惑,在
container
設定perspective 三維效果
,但為何座位圖不會跟著有效果呢?(見下圖,座位row
也是包在container裡面的)
A: 原來是因為screen設定了transform! 設定了transform三維效果才會有效!(此處特地將screen的css code
放在一起一同說明)
1 | .container { |
參考自:
例項講解CSS3中Transform的perspective屬性的用法
transform的rotateX()效果
是transform 3D轉換效果,rotate X()
為控制水平(X)軸,去進行變形。
參考自:
MDN-rotateX()
關於transform更多2D、3D效果請參考:
CSS 2D Transforms
CSS 3D Transforms
座位樣式設定
1 | /* 原來的.seat並無文字撐開,所以無寬度高度, |
製作走道 :nth-of-type及:nth-last-of-type
使用:nth-of-type()
及:nth-last-of-type()
選擇器 ,目的是分類其中兩行座位產生間距,製造電影院走道的效果。
1 |
|
參考自:
:nth-of-type() & :nth-last-of-type() - 你覺得燒腦但其實根本不燒腦的選取器趴兔
比較:ntn-child
與:nth-of-type
的大同小異:
CSS3:nth-of-type(n)
製作座位效果 :not選擇器與:hover選擇器
我們希望做座位圖稍微放大並有點選圖標效果,讓使用者體驗優化。
此處我們使用了兩個選擇器,分別是:not
選擇器與:hover
選擇器,
- 透過:not選擇器,加入:not(類別名稱),可用來當成搜尋器的關鍵字排除器,會自動的略過該類別名稱,不套用樣式。
- :hover 選擇器用於滑鼠箭頭滑到指定元素所顯現的效果,可用於所有元素,不限於link鏈接元素。
1 | /*not與hover選擇器 */ |
參考自:
CSS3選擇器 :not() 讓CSS也支援判斷的機制
CSS :hover 选择器
showcase範例圖式移除hover效果
此處設定不讓hover設定的變化在shwcase版發生
1 | /* 不讓hover設定的變化在shwcase版發生 */ |
showcase座位狀態顯示樣式設定
1 | .showcase { |
設定選取電影座位總數及總票價文字樣式
1 | p.text { |
完整Javascript
DOM元素宣告
- ticketPrice,從const改成let,因value會更動(隨電影票價等更動)
- parseInt函式為轉換語法,加了+號可將回傳值由字串轉換為數字
- querySelector只返回匹配的第一個元素,如果沒有匹配項,返回null
- querySelectorAll返回匹配的元素集合,如果沒有匹配項,返回空的nodelist(節點陣列)ex.返回”[]”。
- (‘.row .seat:not(.occupied)’)同css選擇器,querySelectorAll也可以這樣使用排除不要的元素。
- 將函式呼叫populateUI()’放置於前面,其一重點為:因函式提升的特性,函式呼叫放前放後沒有影響,其二重點為:放置於”Save Selected movie index and price”的function前面,目的是讓票價及位置在先前的紀錄中能被回傳,讓其資料持續保持在瀏覽器介面(UI)上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// querySelector回傳匹配指定選擇器的第一個元素
const container =
document.querySelector('.container');
// 有很多的seat,所以選擇querySelectorAll回傳所有的元
// ('.row .seat:not(.occupied)')同css選擇器,querySelectorAll也可以這樣使用排除不要的元素
const seats = document.querySelectorAll('.row .seat:not(.occupied)');
const count = document.getElementById('count');
const total = document.getElementById('total');
// 先行說明movieSelect之HTML id=movie為字串,作為後續說明時更能幫助理解
const movieSelect = document.getElementById('movie');
// 呼叫下方 populateUI function,目的讓get到的本地端的資料放入(因為函式提升的特性,函式呼叫放前放後沒有影響)
populateUI();
// 小撇步:加了+號可將回傳值由字串轉換為數字(HTML id(movie)及value皆為字串)
// 因Movie select event事件監聽中更換電影,票價會改變
//所以從const改成let,因value(票價值)會更動
let ticketPrice = +movieSelect.value;設定seat事件監聽,再設定seat toggle(切換)狀態
- 使用if判斷,點擊空座位切換為selected,再點擊一次切換回未點擊狀態。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//Seat click event
// 未設定if之前,點擊任何元素皆會回傳值
container.addEventListener('click', e => {
if (
// 設定只有點擊seat,並且不是(語法:&&!)occupied才會返值
e.target.classList.contains('seat') &&
!e.target.classList.contains('occupied')
) {
// classList.toggle,切換類設定,設定點擊空座位轉換為selected的css設定(turn blue color),再點擊時"切換"為未點擊時狀態,
e.target.classList.toggle('selected');
updateSelectedCount();
}
});
補充關於function(e)的部分:
e=Event,Event對象代表事件的狀態,比如事件在其中發生的元素、鍵盤按鍵的狀態、滑鼠的位置、滑鼠按鈕(click)的狀態。
事件通常與函數結合使用,函數不會在事件發生前被執行!
參考資料:
JS基礎篇–HTML DOM classList 屬性
設定總票價的數字變化
繼設定事件監聽來讀取座位數後,我們可以進一步去設定總票價的變化。
1 | //Update total and count |
圖1.點選座位,顯示nodlist
圖2.設定length,轉換成數字
圖3.設定點選座位就會顯示座位數及票價
設定選擇不同電影跳出不同票價
這裡在movieSelect(電影選擇列)上再設定一個監聽事件,觸發的點是’change’,使選擇不同電影可以同步更新到總票價區。
change=>物件內容改變時
target=>指向觸發事件的 DOM element
引用自JavaScript DOM Event (事件處理)
1 | //Movie select event |
參考資料:
JavaScript DOM Event (事件處理)
JavaScript 從零開始 - Day34- e.target 與 nodeName
將點選的座位儲存在本地空間
這裡描述的部分,主要是在頁面重新整理之後仍可以保留資料,我們希望將資料保存在本地儲存空間。
於updateSelectedCount函式內寫入
因解說方式關係,先附上完整code,因函式內有部分已於前段說明,因此以註解方式提醒讀者注意片段,將於下方一一拆解說明。
1 | //Update total and count |
如何點選座位位置(為每個座位定上順序位置(索引位置))
我們將思路拆解為:
1.Copy selected seats into array 將已選擇的座位轉換成陣列。
2.Map through array 對陣列元素進行處理
3.return a new array indexs 回傳陣列第幾個位置
1 | // 此函式為es6簡化 另種寫法為const seatsIndex = [...selectedSeats].map(function(seat) { return [...seats].indexOf(seat)}); |
Copy selected seats into array-展開運算符介紹
這個運算符後面必定接著一個陣列。最常見的是用來組合(連接)陣列,對應的陣列方法是concat。
範例:
1 | const arr = [1,2,3] |
資料參考:
展開運算符與其餘運算符
Map through array- 處理陣列元素 map()
陣列 (arrray) 的 map() 方法用來遍歷一個陣列中的每個元素,將元素分別傳入你指定的函數,最後將所有函數的返回值組成一個新的陣列。
語法:
1 | const newArr = arr.map(function (value, index, array){ |
範例:
1 | var numbers = [1, 4, 9]; |
return a new array indexs -回傳第幾個位子: indexOf()
indexOf()
方法會找出第一個(第幾個)陣列中元素的索引位置,若不存在於陣列中則回傳 -1。
語法:ary.indexOf(searchElement)
、ary.indexOf(searchElement, fromIndex)
範例:
1 | const arr = [1, 2, 3, 4, 4]; |
參考資料:
Array.prototype.indexOf()
設定localStorge 儲存於本地儲存空間(瀏覽器)
可以跨瀏覽器分頁做使用、使用者關掉分頁或瀏覽器再打開資料仍不會消失,且資料無期效限制,資料將永久被保留。
資料儲存的格式
- 資料是以類似 JSON 的格式儲存
- keyName(屬性名稱) 和 keyValue(相對應的值) 皆需要為字串,ex, keyName: ‘Name’/ keyValue:’Shawn’
存入資料語法:localStorage.setItem(keyName, keyValue)
我們欲將選取的座位(selectedSeats)存入本地端,而點選的座位值(seatsIndex)為array(陣列),將其轉換為字串方能存入瀏覽器。
1 | // 因分開說明,這裡再將上方解釋的程式補充於此,讓讀者更方便對應,localStorage取值的部分: |
可於瀏覽器的檢查(shift+ctrl+i)中的application,找到儲存的seatsIndex。
localStorage同樣用法:於setMovieData函式使用
除了座位被儲存,選擇哪部電影及其票價,也同樣需要被存入本地儲存空間。
此次不同的地方在於,keyValue本身即為字串,不須使用JSON.stringify()
轉換。
1 | // Save selected movie index and price |
再設定上方函式之前,我們於movieSelect的監聽事件上再增加一行呼叫函式,以作為被觸發(此處為滑鼠點擊事件,即點選電影目錄)時,會存入localStorage。
1 | //Movie select event |
補充說明:selectedIndex=>設定(set)或回傳(return)多個選擇的下拉式列表,它將僅返回選擇的第一個選項的索引(index)。
引用自HTML DOM Select 對象
設定之後,於瀏覽器中呈現如下:(此範例中,我開啟檢查以方便做對比,我點選第一個Avengers電影,因此右側selectedMovieindex索引值為0(索引值從0起算),selectedMoviePrice為10(票價10))
參考資料:
JavaScript-localStorage 的使用
JS30-Day15-LocalStorage
從localStorage 取出資料,及回頭填充至UI(瀏覽器介面)
將populateUI();
函式呼叫放置在最上層的註解:”Save Selected movie index and price”的function前面,目的是讓儲存在本地端的資料能回傳放進瀏覽者介面中,得以持續保存及顯示,不因重新整理消失。
補充:持續顯示原因為本地端的資料”重新”各自放回選擇座位(seats)及選擇電影目錄(movieIndex)及電影票價(ticketPrice)等相關函式之中再計算、再判斷、再顯示於UI,最後跑到程式最後一行updateSelectedCount()
呼叫函式,讓其重整網頁時,不需經過事件觸發,直接更新,所以看起來就如持續顯示一般。
撰寫populateUI();
函式,使用localStorage 取出資料getItem()
語法,
語法:localStorage.getItem(key)
取出資料時,使用 JSON.parse() 方法,將資料轉換回原本的格式。
1 | // Get datd from localstorage and populate UI |
補充: map() v.s forEach()
map()特色為不會改變原陣列,而forEach()會修改原本陣列,另外forEach()沒有在使用return。
forEach適合於你並不打算改變數據的時候,而只是想用數據做一些事情– 比如存入數據庫或則打印出來。
範例:
1 | let arr = [ "a" , "b" , "c" , "d" ]; |
map()適用於你要改變數據值的時候。不僅僅在於它更快,而且返回一個新的數組。這樣的優點在於你可以使用複合(composition)(map(), filter(), reduce()等組合使用)來玩出更多的花樣。
範例:
讓範例更具變化,我們首先使用map將每一個元素乘以2,然後緊接著篩選出那些大於5的元素。最終結果賦值給arr2。
1 | let arr = [ 1 , 2 , 3 , 4 , 5 ]; |
參考資料:
Day13【ES6 小筆記】Array.map() - 處理陣列最佳選擇
JavaScript中Map和ForEach的區別