2021新生知訊網開發筆記

前言

這個暑假作為新生知訊網的程設組員,和不少夥伴打造了全新的 2021 新生知訊網,無論是使用的框架還是概念都和以往不同,再加上這個專案是我首次做 Web 的實戰開發,學到了不少技巧,因此決定把學到的知識整理到這篇文章中,除了可以把概念整理一遍,便於未來複習外,也能給予其他人不小的幫助。

正文

專案架構

簡單的放一張醜醜的圖再說明

專案架構圖

User View 表示使用者操作的瀏覽器視圖,一開始 Server 收到前端頁面的 request 時,會先經過 NGINX 的反向代理,再轉給 Nuxt 的前端伺服器去處理。Nuxt 就會把需要的資源還有整個 Vue app 傳給 client。client 解析完就會看到我們精心打造的網頁了!

當使用者在視圖上操作時,Vue app 會即時的在前端把畫面刷新。若是需要用到後端資料的話,則利用axios對 Server 提出 api request,請求經過 NGINX 的反向代理轉給 Nuxt,再由 Nuxt 的 proxy 設定轉給 Express Server,Server 回應 client 後再由 Vue 刷新使用者的視圖。

關於 Nuxt

簡介

Nuxt.js 是一個 Vue 的 SSR(Server Side Rendering)框架,可以解決純 js 網頁沒辦法被搜尋引擎爬蟲抓到的問題(SEO)。

雖然 SSR 很潮,但是也有要注意的地方,以下是來自官方文檔的 Nuxt Lifecycle

Nuxt Lifecycle

這張圖解釋了生命週期階段能使用的資料,如果在錯誤的生命週期想調用資料就會出錯。

Nuxt 專案結構

以下是 Nuxt 的專案結構 專案結構

  • .nuxt Nuxt 自動生成的檔案,之後要 deploy 前 build 完的檔案也會被丟在裡面
  • assets 要用到的資源檔,主要是圖片和字體那些
  • components Vue 的 component
  • layouts 用來放頁面版型,在新知網中各分頁的全域 CSS 也放在這
  • middleware 跑進頁面之前要先跑的 middleware 都會放這
  • pages 頁面的檔案,結構會被 Nuxt 當成路由路徑
  • plugins 如果有什麼插件要先載入的要把注入的 code 放這
  • static Server 的靜態檔案,像是 icon 之類的
  • nuxt.config.js nuxt 的設定,包了插件、全域 CSS、Proxy 等一堆設定,如果不知道要去哪改設定的時候可以到這邊看看

官方文檔有更詳細的說明

Nuxt 的路由

由於 Nuxt 自己製作了前端路由的系統,因此我們要把網站中不同頁面的跳轉從一般超連結換成 NuxtLink

1
2
3
4
5
<!--bad example-->
<a href="/xxx">link</a>

<!--good example-->
<nuxt-link to="/xxx">link</nuxt-link>

換掉連結後,就算進行分頁跳轉,瀏覽器也不會把整個頁面刷新。

其實 NuxtLink 雷還不少,如果要在 NuxtLink 上用@clink要改寫成@click.native,否則不會觸發。而就算改用@click.native,在事件當中使用emit還是會有問題。此外,NuxtLink 似乎不能使用相對路徑來做路由

Nuxt 頁面的路由路徑是由目錄路徑決定的,若是訪問目錄的位置,則會 render 該目錄的index.vue

若要調用路由的資料,可使用this.$route,要對路由做操作可使用this.$router,如$router.back()$router.go()

動態路由

若要使用動態路由,可將頁面名稱前方加上_(如_type.vue),此時type就會變成路由參數,可以在validate函式中做驗證或是用來 render 頁面。

以下是動態路由結構實例(新知網的頁面)

動態路由實例

動態路由驗證

簡單的動態路由驗證可以像下面這樣子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default {
...
validate({ params }) {
const club = [
"learn",
"religion",
"music",
"sport",
"skill",
"serve",
"friend",
"fun",
"team"
];
return club.includes(params.type);
},
}

如果type不在清單上就會變成 404(找不到頁面)

ClientOnly

有時會碰上 template 中的 component 只能在 client side render 的狀況。此時可以用

1
<ClientOnly> ... </ClientOnly>

如此 Nuxt 就只會在 Client side 的時候才渲染內部元素。

本次用到的 vue-fullpage.js 就有要求一定要在 Client side 才能正常執行,因此 fullpage 的 component 一定要包在<ClientOnly>

Nuxt Event Bus

Vue 本身就有emit可以在 component 之間傳遞資料了,不過 Nuxt 依舊提供一套 emit 的系統給我們使用。在使用妥當的情況下,甚至比原生 Vue 給的emit好用不少。

當我們要使用 Nuxt 提供的 emit 前,我們要先在要接收的 component 註冊。

1
2
3
4
5
6
7
8
9
10
11
export default {
...
created() {
this.$nuxt.$on('some-event', () => {
// do something
});
},
beforeDestroy() {
this.$nuxt.$off('some-event');
},
}

之後要發出事件就簡單多了,只要加一句

1
this.$nuxt.$emit("some-event");

整個頁面都能接收到 Nuxt emit 出去的事件,解決了 Vue 提供的emit連續向上傳遞或橫向傳遞時難以使用的問題。除此之外也能向原生的emit一樣傳參數,如:

1
2
3
4
5
6
7
8
9
10
11
export default {
...
created() {
this.$nuxt.$on('some-event', arg => {
console.log(arg);
});
},
beforeDestroy() {
this.$nuxt.$off('some-event');
},
}
1
this.$nuxt.$emit("some-event", "test");

CSS Tricks

RWD 圖片配上定位

我的頁面中有需要同時做 RWD 圖片並且相對定位不能跑掉的需求,最後找到的方法如下:

  1. 圖片只能做寬度或高度的其中一項
  2. 用多層div交錯使用相對定位和絕對定位,並調整大小
  3. 讓圖片的大小切齊父元素div的高或寬

看起來有點攏統,簡單解釋一下

圖片如果在做 RWD 時同時設定了寬高,就會導致圖片比例扭曲

1
2
3
4
5
6
7
8
9
10
img {
// wrong
height: 100%;
width: 100%;

// correct
height: 100%;
// also correct
width: 100%;
}

圖片比例扭曲除了會造成使用者體驗不佳外,也會導致我們圖片定位跑版

使用多層div並交錯使用絕對位置和相對位置的原因則是為了讓子元素可以使用絕對定位來控制大小和位置,實例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.blocks {
// 最外層的div將大小與位置先設定好
position: absolute;
height: 90%;
left: 31%;
top: 10%;
.blocks-inside {
// 內層relative大小貼合父層即可
position: relative;
height: 100%;
.go-wrapper {
// 要把圖片在父層的相對位置釘死就要靠這個absolute
position: absolute;
// 調整圖片的大小
height: 16%;
img {
// 裡面的圖片大小跟父層貼合即可
position: relative;
height: 100%;
}
}
}
}

之後調整圖片大小就只要動absolutediv。從這裡可以看出讓子元素大小貼齊的好處就是維護方便與可讀性。

最後效果可以弄成下圖的樣子

社團介紹頁面

各式各樣的對齊方法

置中等對齊方式一直是 CSS 新手的痛,下面整理幾個常用的對齊方式

置中

絕對位置

絕對位置要置中可以用下面的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
div {
// 水平置中
&.a {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
// 垂直置中
&.b {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
// 水平垂直都置中
&.c {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
}

&表示父階層的 selector(scss 的語法)

原理算是透過硬推的方式把位置推到中間,以水平置中為例,先用left: 50%向右推父元素 50%的寬度,此時子元素最左方會切齊父元素中線。

再利用transform: translateX(-50%)將子元素向左推自身 50%的寬度,最後就會得到剛好置中的效果。

如果這裡看不懂的話可以畫圖模擬看看

相對位置

如果要用相對位置來置中,我傾向使用 flex。

1
2
3
4
5
6
7
8
9
10
11
12
div {
&.parent {
display: flex;
justify-content: center;
align-items: center;
.child {
position: relative;
height: 50%;
width: 50%;
}
}
}

display: flex會讓該元素成為 flex container,裡面的子元素位置會自動被 flex container 調整

利用 flex container 可以設定主軸對齊和次軸對齊的特性,可以把child這個長寬剛好是parent一半大的元素置中。

此處的效果是水平及垂直都置中,若是只需要其中一項,可以搭配flex direction並調整兩軸對齊的參數

可以滾動的清單

這項功能在手機板很常見,具體實做起來用 flex container 配上overflow屬性非常快。以下是新知網中的實際應用:

手機板滾動清單

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
div {
.table-list-wrapper {
// 由於是浮動選單,所以設成fixed
position: fixed;
// width調成滿版可以省下水平對齊的困擾
width: 100vw;
// 清單最大的顯示範圍
height: 60vh;
top: 20vh;
z-index: 200;
.table-list-wrapper-inside {
position: relative;
// 與父層大小貼合
height: 100%;
width: 100%;
// 利用flex container來自動調整底下清單的對齊
display: flex;
flex-direction: column;
// 在這裡次軸是水平軸,所以flex-end就會是元素最右方
align-items: flex-end;
z-index: 200;
// 讓y軸方向可以滾動
overflow-y: scroll;
.table-block {
// 用來定義每個方塊的大小,之後再讓背景大小切齊
position: relative;
height: 13%;
width: 57%;
.block-background {
// 讓背景圖片切齊父元素
position: relative;
height: 100%;
width: 100%;
}
}
}
}
}

fullpage.js 的幾個雷點

本次專案有很多人有滿版顯示還有分段下滑的需求,於是買了 Vue-fullpage.js,不過坑還不少,下面有幾項需要注意的地方。

滿版畫面絕對不能超出區段

fullpage 中可以滾動的區段一定要加上section的 CSS clsss,在裡面的任何元素也不能超出section的區塊。

fullpage 會自動抓取section的高度,再把這個高度當成本頁的高度,在滾動時也會被當成參數來用。

實際上 fullpage 會修改 DOM 結構,因此會多很多 fullpage 弄出來的 div,其中多會被加上style="height: <section-hight>px",導致section子元素超出範圍就會跑版。 fullpage DOM結構

data-anchor 不能是英文

實際測試的時候,發現採用data-anchor作為跳轉區段的辨識時,只要data-anchor的值為英數組成,在滾動到該頁面後就會跑版。

檢查過後發現是 fullpage 在修改 DOM 時,因為錯誤的操作將我的 class 改掉了。

fullpage 操作DOM

當有特定data-anchor出現時,fullpage 修改 body 的 class 就可能出現錯誤,導致groups-layout的 class 名稱改變,最終導致跑版。

最後只能把顯示的標題和data-anchor的值分開,以避免跑板問題。

有設定data-anchor的情況下,要注意同頁面是否有id的值和data-anchor撞名。若是發生撞名也會導致一系列錯誤。

影響頁面上可滾動的元素

頁面上如果有可滾動元素,在沒有正確設定下,滾動就會被 fullpage 吃掉,最終變成切換頁面。所幸是解決方法不難,只要在 fullpage 的初始化設定內調整即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
export default {
...
data() {
return {
...
options: {
...
normalScrollElements:
".table-list-wrapper-inside, .content-area, .block-text-wrapper",
},
}
},
};

normalScrollElements設定要在頁面上能正常滾動的元素,採用 CSS selector 的方式設定,中間用,分隔

Safari 的 vh 坑

Safari 的vh算法跟一般的瀏覽器不太一樣,從示意圖來看就可以感受到 Apple 滿滿的惡意。

Safari vh示意圖

既然踩坑的不會只有我一個,想當然耳,解決方法當然不少。可惜這是在最後測試期才發現的問題,不想花太多時間修復的我,直接把overflow的 scroll bar 還給 user,果斷放棄滿版。

iT 邦幫忙的文章有整理了一個簡單方法來克服這個問題。如果要用在 Vue/Nuxt 的專案上的話,可以採用 mixin 的方式,讓每個頁面都能載入到我們自己設置的 vh 變數。

結語

這篇文差不多就先寫到這裡,如果我之後又想起甚麼重點的話會再回來補。近期還會有一個學校的小專案,會再整理一篇筆記到網站上。