這次寫的是 Frontend Mentor 挑戰的太空旅遊介紹的網站,因為本身對太空很有興趣,高中還一度夢想成為太空人(?),所以看到這個挑戰的時候就很想作,當初作了一個星期,首頁都沒辦法完成,那時候對 flexbox、grid 的樣式寫法超不熟,用 position 也是作的很落漆,於是中間就先暫停,想說先把熟練度練高一點再來挑戰。
就這樣過了一年。
沒錯,一年再次開啟資料夾的時候,其實很害怕會不會我還是一樣,是那個首頁都作不出來的自己,然後我用三天就作完了...自己嚇到自己,還一度懷疑:真的嗎?(摸臉)
當下感受到自己的進步,真的很感動(擦淚)
這次挑戰內容是:
多頁式網站基本上就是 header 頁首、footer 頁尾一樣,中間內容不同,所以這裡只寫出內容的架構:
因為我是從手機版開始作,也就是 Mobile first design,再來是平板,最後才是電腦版,所以一開始的 HTML 內容順序就以手機頁面為主。
<main>
<div>
<!-- 標題 title -->
<!-- 內容 content -->
</div>
<div>
<!-- 按鈕 explore-btn -->
</div>
</main>
這裡想在電腦版的時候用 grid 來排版:
main {
display: grid;
grid-template-columns: repeat(2, 50%); /* 分成兩行 各佔 50% */
grid-template-rows: repeat(2, 1fr);/* 再分成兩列 */
}
.title {
grid-column: 1 / 2; /* SPACE 標題佔第一行 第一列 */
grid-row: 1 / 2;
}
.content {
grid-column: 1 / 2; /* 內容文字佔第一行 第二列 */
grid-row: 2 / 3;
align-self: start; /* 靠左對齊 */
}
.explore-btn {
grid-column: 2 / 3; /* 按鈕佔第二行 佔整列 */
grid-row: 1 / 3;
}
這裡稍微有點複雜,HTML 內容順序依然按照手機版。
平板的順序沒有什麼變化,
只是 MOON 內容文字下方還有兩個行星資料:
一個是距離 OOOKM 和天數 OODAYS,原本是直向排列用 flex 變成橫向排列。
電腦版用 grid 來排版,讓行星圖片靠左;切換按鈕、行星介紹與資料靠右邊。
<article>
<div>
<!-- 行星圖片 data-img -->
</div>
<div>
<!-- 切換列表 subnav-list -->
</div>
<div>
<!-- 行星介紹 data-description -->
<div>
<!-- 行星資料 planet-data -->
</div>
</div>
</article>
.planet-data {
display: flex; /* 平板改成橫向排列 */
justify-content: center;
}
article {
display: grid; /* 電腦版改成 grid 排版 */
grid-template-columns: repeat(2, 50%); /* 分成兩行 各佔 50% */
grid-template-rows: repeat(2, minmax(1px, auto)); /* 分成兩列 大小依內容 */
}
這裡看到平板的順序跟手機版幾乎相反,所以要用 column reverse 來反轉方向;
電腦版就改成 grid 就可以完成排版了。
<article>
<div>
<!-- 組員圖片 data-img -->
</div>
<div>
<!-- 切換列表 subnav-list -->
</div>
<div>
<!-- 組員介紹 data-description -->
</div>
</article>
article {
display: flex; /* 平板使用 flex 排序 */
flex-direction: column-reverse; /* 上下反轉順序 */
}
article {
display: grid; /* 電腦版使用 grid 排版 */
grid-template-columns: 60% 40%;
grid-template-rows: repeat(2, minmax(1px, auto));
}
這跟首頁一樣簡單,在電腦版時使用 row-reverse 即可。
<div>
<!-- 器械圖片 data-img -->
</div>
<div>
<!-- 切換列表 subnav-list -->
</div>
<div>
<!-- 技術介紹 data-description -->
</div>
article {
display: flex; /* 只要在電腦版使用 flex 排序 */
flex-direction: row-reverse; /* 左右反轉順序 */
}
.subnav-list {
flex-direction: column; /* 將切換列表方向改成直向 */
order: 1; /* 將順序排在最左邊 */
}
這邊要特別說明 order,這裡的 1 表示要將元素排在最後一個,但是因為在父元素使用了 row-reverse 左右反轉,所以原本排在最後的切換列表,反轉後變成在第一個位置。
如果今天沒有反轉順序的話,order 要寫成 -1。
除了首頁以外,現在我們有三個頁面:行星、組員、技術介紹頁,
其中的切換列表讓使用者點擊後,可以切換介紹資料,
挑戰中提供了所有列表的 HTML,這樣寫就會有 11 頁..
看到就心累..
還好挑戰也提供了 JSON 檔案,所有的資料都在裡面,只要在抓資料的時候,知道使用者點擊的是哪個列表即可。
在標籤中寫入 data-* 的資料,之後在 JavaScript 用 dataset 取得資料名稱就可以了。
<div class="subnav-list">
<!-- 行星切換列表 -->
<span
class="subnav-item active-destinations"
data-group="destinations"
data-name="Moon">Moon</span>
<!-- 以下類推... -->
</div>
<div class="subnav-list">
<!-- 組員切換列表 -->
<span
class="subnav-item dot active-crew"
data-group="crew"
data-name="Douglas Hurley"></span>
<!-- 以下類推... -->
</div>
<div class="subnav-list">
<!-- 技術切換列表 -->
<span
class="subnav-item active-technology"
data-group="technology"
data-name="Launch vehicle">1</span>
<!-- 以下類推... -->
</div>
首頁裡沒有切換列表 subnavList,所以如果在首頁時,後台就會報錯,因為它找不到 subnavList,監聽器不知道掛哪。
所以在 subnavList 後方加上了問號 optional chaining,表示當值是 null 或 undefined 時,就不會去掛載監聽器。
const subnavList = document.querySelector('.subnav-list')
const subnavItems = document.querySelectorAll('.subnav-item')
subnavList?.addEventListener('click', (e) => {
const item = e.target.closest('.subnav-item') // 找到點擊的列表item
if (!item) return // 如果不是點在列表 item上 則退出函式
const name = item.dataset.name // 取得 HTML 的 data資料
const group = item.dataset.group
getData(group, name) // 調用 getData函式 取得 JSON資料
subnavItems.forEach((i) => i.classList.remove(`active-${group}`))
// 迭代列表 移除 active樣式
item.classList.add(`active-${group}`) // 將 active樣式加入點擊的 item中
})
剛剛知道了使用者點擊的列表是哪個,所以現在要來抓資料:
const BASE_URL = './jsons/data.json'
const getData = async function (dataGroup, dataName) {
try {
const res = await fetch(BASE_URL)
if (!res.ok) throw new Error('Something went wrong')
const data = await res.json()
if (!data) throw new Error('Data not found')
const group = data[dataGroup]
// 依列表名稱找到 JSON 中的資料們
const activeData = group.filter((g) => g.name === dataName)
// 再從剛剛的資料們中 依使用者點擊的 item名稱 取得資料
// 底下:如果列表名稱等於OOO 就調用專屬的 display函式 更新 HTML內容
if (dataGroup === 'destinations') displayDestination(...activeData)
if (dataGroup === 'crew') displayCrew(...activeData)
if (dataGroup === 'technology') dispalyTechnology(...activeData)
} catch (err) {
console.error(err)
}
}
使用資料將 HTML 寫出來,再去更新 HTML 內容:
const dataImg = document.querySelector('.data-img')
const dataDescription = document.querySelector('.data-description')
const replaceData = function (img, content) {
dataImg.innerHTML = img
dataDescription.innerHTML = content
}
const displayDestination = function (data) {
// 將 HTML內容寫出來
const img = `<img src="${data.images.png}" alt="${data.name}">`
const content = `
<h1 class="subtitle upper">${data.name}</h1>
<p class="content barlow purple">${data.description}</p>
<div class="planet-data upper">
<div class="distance">
<p class="subheading barlow purple">Avg. distance</p>
<p class="subcontent">${data.distance}</p>
</div>
<div class="travel">
<p class="subheading barlow purple">Est. travel time</p>
<p class="subcontent">${data.travel}</p>
</div>
</div>
`
replaceData(img, content) // 調用 replaceData函式 替換 HTML內容文字
}
這樣就完成了。
完成頁面 ▶ Space Tourism
Solution ▶ Frontend Mentor - Space Tourism