2024-06-22|閱讀時間 ‧ 約 34 分鐘

從 1 開始的多頁式網站

多頁式網站

多頁式網站


從上次作完喵喵健身教練網頁後,覺得自己以前作的個人簡歷網站實在是太醜了,而且當初為了搶快,直接套用 Bootstrap 版型,根本不用寫 JavaScript(燦笑)

所以這次決定把網站重寫,就算是從 1 開始吧,1-10 分作到 8 分就好,不然完美主義真的會搞死自己,那道光很快就來了(?)

HTML 架構

多頁式網站多半是 header 跟 footer 會是一樣的,這樣使用者在瀏覽時,不會一下要去左側找導覽列、一下又去上方點選導覽列,所以只要替換中間的 main 內容就好。

<header>

<main>

<footer>

首頁的內容

接著就是 main 的內容了,首頁我想讓使用者直接看到我的作品集,並且用卡片形式:包含照片、標題、使用的工具、描述、 gitHub repo 的連結,而這些資料會寫成 JSON 檔案,最後用 JavaScript 把資料轉換成 DOM 節點,讓瀏覽器渲染。

<main>
<div class="card">
</main>

關於我的內容

關於我是最簡單的,只要放一張照片、簡歷、社群媒體的連結即可。

<main>
<section class="profile-section">
<div class="profile-avatar">
<div class="profile-content">
</section>

<section class="contact-section">
<a class="instagram">
</section>
</main>

經歷與技能的內容

經歷就用條列式的方式,技能除了條列出來外,還要有百分比表示 level,看起來比較能知道技能的程度。

<main>
<section class="experience">
<h2 class="title">
<ul class="job">
</section>

<section class="skills">
<h2 class="title">
<ul class="skill-list">
</section>

<section class="other-activities">
<h2 class="title">
<ul class="activities-list">
</section>
</main>

我真棒(雙手交叉拍拍自己肩膀)。



CSS 樣式

導覽列推出式動畫

導覽列往下推擠

導覽列覆蓋在元素上方


上一次是作淡出淡入動畫,這樣導覽列可以想像成 photoshop 的圖層,順序會蓋在頁面上面,當淡出時,不會推擠到下方的圖層。(左圖)

這次想改成在同一個圖層上,導覽列下拉時,會把其他元素往下推擠。(上圖)

這裡依舊使用 animation 與 keyframes 來做出效果,關鍵影格在初始設定高度 height 及透明度 opacity 為 0,動畫結束時將透明度 opacity 為 1,將高度設定為導覽列的高度..要怎麼知道?用 google DevTools 去看就可以了,這部分也是未來可以優化的地方

反之,如果是要將導覽列收起,就設定相反的數值即可。

@keyframes pushin {
0% {
height: 0;
opacity: 0;
}
100% {
height: 170px;
opacity: 1;
}
}

再來將 animation 設定效果呈現的秒數。

.push-in {
animation: pushin 0.4s;
}

最後去 JavaScript 掛載事件監聽器,用來觸發設定好的效果。

const toggler = document.querySelector('.toggler')
const collapse = document.querySelector('#navbarCollapse')
let isShow = false // 先設定導覽列是隱藏的

toggler.addEventListener('click', function (e) {
const btn = e.target.closest('.toggler-icon')
if (!btn) return
toggleNavigation()
})

const toggleNavigation = function () {
if (isShow) {
// 當導覽列正在顯示時 加入 push-out 並移除 push-in 動畫
collapse.classList.add('push-out')
collapse.classList.remove('push-in')
collapse.style.height = '0' // 高度設定回 0
} else {
// 當導覽列是隱藏時 加入 push-in 並移除 push-out 動畫
collapse.classList.add('push-in')
collapse.classList.remove('push-out')
collapse.style.height = '100%' // 高度設定 100%
}
isShow = !isShow // 動作結束時 將 isShow 狀態反轉
}

技能百分比圖示

百分比樣式

HTML 中將條列式的技能列出,除了技能名稱外,另外包一個 span 標籤在裡面,用來設定百分比樣式。

<li class="skill-item">
<span>技能名稱</span>

<span class="level-percentage"> <!-- 設定百分比樣式 -->
<span class="percentage""></span>
</span>
</li>

css 部分,先將 skill-item 設定為 grid,讓兩個子元素:技能名稱、百分比樣式可以排排站,為什麼不用 flex 的原因是,在排列的時候兩個子元素會一直緊緊相依,視覺上就沒有對齊的感覺。(下圖)

flex 與 grid 差異


.skill-item {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: 1fr;
margin: 0.5rem;
align-items: center;
}

.skill-item span {
flex-basis: 20%;
}


flex 就像是把兩個東西都放在同一層格子中,如果要跟上一層格子對齊,需要耐心調整,並且只要寬度不同時,就有可能無法對齊。

grid 則是把兩個東西各自放在一層兩格的櫃子,上下要對齊時,只要把東西同時置中或靠左靠右,而不用去考慮到另一個東西的位置,調整就不會太吃力。


再來設定百分比,在 HTML 中,我把百分比寫成一個父元素 level-percentage 跟一個子元素 percentage,父元素用來寫外框,也就是百分比的黑色部分,子元素則是用來寫技能的百分比%樣式。

.level-percentage {
position: relative; /* 父元素的位置設定 relative 讓子元素可以依賴父元素的位置*/
z-index: 10;
display: inline-block;
width: 100%; /* 寬度就是那格 grid 格子的寬度 */
height: 0.8rem;
border: 2px solid #434343;
border-radius: 10px;
background-color: #434343;
}

.percentage {
position: absolute; /* 讓子元素可以依賴父元素的位置去做調整 */
z-index: 20; /* 子元素的 z 軸位置較高 才能蓋在父元素上方 */
left: 0;
top: 50%; /* 垂直置中 */
transform: translateY(-50%); /* 垂直置中 */
/* width: ; 寬度之後用 JavaScript 來設定*/
height: 0.6rem;
border-radius: inherit;
background-color: #eeeeee;
}

這裡就完成了,接著就要用 JavaScript 來抓資料。



JavaScript 抓 JSON 資料

抓資料渲染畫面

不想把 HTML 寫的太長,所以想把資料寫成 JSON 檔案,未來資料有更動時,就不用逐一調整,還有可能調錯的風險存在。

這次先把作品集跟技能的資料寫成 JSON 檔案:

{
"portfolios": [
{
"title": "作品標題",
"skills": ["使用技能1", "技能2", "技能3"],
"content_zh": "中文介紹",
"content_en": "英文介紹",
"live_url": "發佈的網址",
"image_url": "圖片路徑",
"repo_url": "git 倉庫網址",
"copyright": "版權聲明"
}
]
}

再來就是抓資料,因為 JSON 檔中有幾個 key name 包含下底線,所以先把它們轉成駝峰式寫法,例如:repo_url 改成 repoUrl,就不會在使用資料時,還要回去看名稱是哪種寫法。

const renameKeys = function (obj) {
const keyValues = Object.keys(obj).map((key) => {
const index = key.indexOf('_') // 取得下底線在名稱中的位置
const newKey =index > 1 // 如果下底線不在第一個字母時
? `${key.slice(0, index)}${key
.slice(index + 1)[0]
.toUpperCase()}${key.slice(index + 2)}` // 移除下底線後將後方的字母改成大寫
: key // 反之 下底線在第一個字母時就不用轉換
return { [newKey]: obj[key] }
})
return Object.assign({}, ...keyValues) // 返回新的物件
}

接著使用 fetch 取得資料:

const displaySection = async function (url, callback) {
try {
// ​依 url 取得資料 如果沒有取得資料 就丟出錯誤
const res = await fetch(url)
if (!res.ok) throw new Error('Something went wrong!')

// 將資料解析 如果沒有資料 就丟出錯誤
const data = await res.json()
if (!data) throw new Error('Data not found')

// 將解析的資料傳入函式
callback(data)
} catch (err) {
console.error(err) // 如果有錯誤 就顯示在主控台
}
}

把 callback function 另外寫,將資料改寫成 HTML 模板後,置入 DOM tree:

const portfolioMarkup = function (data) {
const { portfolios } = data // 解構 portfolios
portfolios.forEach(function (portfolio) {
const port = renameKeys(portfolio) // 轉換 key 的名稱

// 寫成 HTML 模板​
const html = `
<div class="card">
<div class="card-img">
<img src="${port.imageUrl}" class="block" alt="${port.title}">
</div>

<div class="card-body">
<p class="card-title">${port.title}</p>
<p class="card-description">${port.contentZh}</p>
</div>

<div class="card-footer">
<p class="card-copyright">${port.copyright}</p>
</div>
</div>
`
// 置入 DOM
portfolioContainer?.insertAdjacentHTML('beforeend', html)
})
}

再來就是調用 displaySection 函式就完成了。



網頁在此:我的個人簡歷。希望大家給我愛心(???)

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.