0%

JS Dungeon for Rookies Fourth Boss World Clock

新手JavaScript地下城第四關 - 時區

這是系列文的第二篇,但因為我 柿子挑軟的吃 最近想精進一些有關用 JS 操作時間的技巧,所以決定先選擇這個題目來處理。

  • 首先,從老師提供的設計稿來大致看一下複雜的程度。

[design concept]

  • 從圖中可以得知,版面排列和樣式簡潔明瞭,應該在 CSS 和 HTML 結構設計上不會有太多挑戰,可以很快進入 JS 取得時間、調整時區和更新時間的部分。

重要處理步驟和技巧

HTML, CSS
  • 這邊先要說明一下,其實這個時鐘的關卡我前後做了兩個不同但類似的成品,第一個是由六角提供的設計稿,那時的想法很單純,所有的 HTML 標籤就直接先通通刻好,再分別使用 JS 來選取、操控元素,後來才發現如果要擴充其他城市會有點麻煩。第二份是採用 Bootstrap 4 來調整版面和樣式的版本,HTML 元素則使用 JS 內的資料陣列,以迴圈和 innerHTML() 來塞入,整體擴充性較佳
  • 六角的那份,採用的方法是給每個城市外層定義一個區塊 <div class="timebox">,且 CSS 定義 .timebox{display: flex},讓左右側的子元素 (區塊元素) 可以併排呈現。同時,預先填入不會變動的各地地名 (慕尼黑是朋友要求加上的),會即時變動的左右側時間元素,都一一設定好 id 供 JS 選擇。
    • 其他 CSS 較特殊的部分,我從設計稿中推測依照時間不同,各地區的文字和背景色有黑底白字 (夜間)、白底黑字 (日間) 兩種樣式,所以另外定義了 .timebox.night{background-color: #000000; color: #ffffff} 讓 JS 可以順利切換到黑夜。
    • 下圖為結果的黑白效果,定義白天時間為 06:00 到 17:59。

[day vs. night]

  • 第二份是幫朋友寫的 Bootstrap 版本,因 BS 本身就已經定義了很多兼具彈性和美觀的樣式類別,所以在 CSS 的設計上就相當簡單,在實作時有兩個經過思考的點:
  1. 因為是使用陣列資料和迴圈塞入,如果要使用 BS 定義好的樣式,在每個城市的物件資料中也要加入相關的訊息,比如說想依次對 cityA, cityB, cityC 加入 bg-primary, bg-secondary, bg-info 的樣式,則必須讓 JS 的陣列記錄資料用類似下面的方法,並在迴圈組字串時搭配 <div class="${city.background}"> 才能讓套用 BS 的樣式:
1
2
3
4
5
let dataArray = [
{name: "cityA", background: "bg-primary"},
{name: "cityB", background: "bg-seconday"},
{name: "cityC", background: "bg-info"},
]
  1. 由於 BS 的網格系統對響應式設計支援算完整,所以記得使用 <div class="col-md"> 或是 <div class="col-lg"> 之類的設定來調整版面,另外使用網格系統要記得用 <div class="row"> 包覆網格內容,我採取的是在大裝置時,三個地區成為一列,所以使用 JS 組字串時,要使用判斷式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let htmlStr = "";
for (let i = 0; i < newCities.length; i++) {
if (i % 3 == 0) {
// 除 3 的餘數為零時,加 .row 的起始標籤
htmlStr += `<div class="row mt-md-3">`;
}
htmlStr += `
<div class="col-md">
<div class="timebox">
</div>
</div>`;
// 除 3 的餘數為 2 時,或是最後一個地區時,加上 .row 的關閉標籤
if (i % 3 == 2 || i == newCities.length - 1) {
htmlStr += `</div>`;
}
}
mainNode.innerHTML = htmlStr;

[the layout using BS grid system]

JavaScript
  • 不論是六角版或是 BS 版,最重要的都是使用 JS 內建物件 Date,使用const date = new Date()語法,即可取得瀏覽器的本地時間,一般常見使用下列語法:
1
2
3
4
5
6
7
8
9
10
const date = new Date(); // 建立新的 Date 物件
date.getDate(); // 取得為該月幾號
date.getDay(); // 取得星期幾,回傳 0-6,0 為星期日
date.getMinutes(); // 取得幾分
date.getHours(); // 取得幾點鐘
date.getMonths(); // 取得月份,回傳 0-11,0 為一月
date.getFullYear(); // 取得公元紀年
date.getTime();
// ※ 取得自 1970/01/01 00:00:00 (UTC) 到當下的毫秒數
// 非常實用,因為是獨一無二的,常做為存取資料時的 unique ID 使用
  • 一開始我想法是抓取本地時間後,用 date.getTimeZoneOffset 的方法來回算取得 UTC 時間,接著再把各地與 UTC 的標準時差放入,算得各地的時間,是比較硬幹的做法,但在完成測試時,發現歐美普遍施行的夏令時間會造成錯誤,只好重新來過。
  • 經過研究後發現,可以使用 date.toLocaleString(locales, options) 的方法直接取得各地時間,詳細的 locales 和 options 設定,可參考 Ref. by W3Schools ,各時區的參考則是 Ref. by timezonedb
  • 我的設定是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let newCities = cities.map(function (cityObj) {
let option = {
day: "numeric", // "10"
month: "short", // "01"
year: "numeric", // "2020"
hour: "2-digit", // "24"
minute: "2-digit", // "59"
hour12: false, // 24 時制
timeZone: cityObj.timezone,
}
cityObj.date = new Date().toLocaleDateString("zh-TW", option);
return cityObj;
});
// 對既有的 cities 陣列,使用 map() 方法,讓原陣列成員 (各城市物件) 取得當地的時間後,變成物件的新屬性 "timezone",並建立一個新的陣列 newCities
  • 雜記:
    • 對取得的各地時間字串,可以用 string.split() 方法處理成陣列,再使用陣列方法如: array.splice() 來取得想要的值。
    • 判斷黑夜白天可以用一個小函式配判斷式,再加上 setAttribute() 方法處理。
1
2
3
4
5
6
7
8
9
10
11
12
13
function isNight(hour) {
if (hour >= 6 && hour < 18) {
return false;
} else {
return true;
}
}
// ... 各城市的幾點鐘做為參數帶入判斷式
if (isNight(cityHour)) {
rightDiv.parentNode.setAttribute("class", "timebox night");
} else {
rightDiv.parentNode.setAttribute("class", "timebox");
}