新手入門
課程目標
完成一個完整的網站
角色
角色關係
flowchart LR A[前端]<-->B[後端]<-->C[維運]
前端
主要負責 UI 呈現與 API 串接,最容易被看到
後端
主要負責商務邏輯,不太容易被看到
維運
主要負責背後的服務穩定,一般使用者看不到
現況
狹義來說,前端的範圍侷限在 HTML, CSS, JavaScript 這三個技術基礎內
廣義來說,只要是可以將東西在網頁上呈現出來給使用者看的,都可以稱為前端
前端只有兩種人: 會寫程式的;不會寫程式的
最終,大家都要朝著一條龍的路上走去...
網站溝通模式
sequenceDiagram Browser->>Server: Request (輸入網址按下 enter) Server->>Browser: Response (畫面顯示)
網站架構
flowchart TD A[首頁] A-->B[關於我們] A-->C[商品介紹] A-->D[聯絡我們] A-->E[成功案例] C-->C1[商品列表] C1-->C2[商品詳細]
開發工具
套件
Live Server
簡易型 Web Server,模擬正式環境
Material Icon Theme
方便識別檔案型態 icon 的主題
Chrome Dev Tools
Chrome 提供的開發者工具,開發人員必備!
會『除錯』比會『開發』來得重要!
開啟方式
-
MAC
command + shift + i
-
Windows
F12
-
通用
更多工具 > 開發人員工具
頁籤功能
-
element (元素)
- 觀察頁面 HTML 結構與動態修改
-
network (網路)
- 觀察頁面傳輸檔案狀態與流量(消耗的網路頻寬)
-
console (主控台)
- 觀察頁面程式輸出狀態,寫程式必備
HTML
負責網頁結構而非呈現樣式,所以預設跟純文字差不多
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<meta name="description" content="This is my first page">
</head>
<body>
Hello
</body>
</html>
vscode 快速產生 html:5
+ tab
or enter
<!DOCTYPE html>
宣告文件類型為 HTML
<html lang="en"></html>
HTML
主體,lang="en"
預設顯示語言(不用改)
<head></head>
表頭,給瀏覽器看的
<meta charset="UTF-8">
網頁語系,統一使用 UTF-8
<meta http-equiv="X-UA-Compatible" content="IE=edge">
相容 IE
模式
<meta name="viewport" content="width=device-width, initial-scale=1.0">
預設寬度為裝置寬度 (RWD 必備!)
<title>Document</title>
標題,顯示在瀏覽器頁籤上面
<meta name="description" content="This is my first page">
網頁說明,顯示在搜尋結果下方說明
建議字數:
- 英文 230 ~ 320 字
- 中文 110 ~ 150 字
無輸入時,搜尋引擎會抓取頁面內容自動產生
<body>Hello</body>
給人看的,要顯示的東西都寫在這邊
Favorite icon
除了網頁標題以外,代表網頁的形象圖是也很重要
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- favorite icon start -->
<link rel="apple-touch-icon" sizes="57x57" href="/icon/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/icon/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/icon/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/icon/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/icon/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/icon/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/icon/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icon/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icon/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/icon/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/icon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/icon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/icon/favicon-16x16.png">
<link rel="manifest" href="/icon/manifest.json">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="/icon/ms-icon-144x144.png">
<meta name="theme-color" content="#ffffff">
<!-- favorite icon end -->
</head>
<body>
Hello
</body>
</html>
Facebook meta
支援 facebook 資訊抓取
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Facebook meta demo.</title>
<link rel="icon" type="image/png" sizes="180x180" href="https://book.niceinfos.com/frontend/assets/html_facebook_meta_v3/images/logo.png" />
<meta property="og:url" content="https://book.niceinfos.com/frontend/assets/html_facebook_meta_v3/" />
<meta property="og:type" content="website" />
<meta property="og:title" content="My First Page" />
<meta property="og:description" content="This is my first page" />
<meta property="og:image" content="https://book.niceinfos.com/frontend/assets/html_facebook_meta_v3/images/logo.png" />
<meta property="og:image:secure_url" content="https://book.niceinfos.com/frontend/assets/html_facebook_meta_v3/images/logo.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="310" />
<meta property="og:image:height" content="310" />
<meta property="fb:app_id" content="" />
</head>
<body>
Facebook meta demo.
</body>
</html>
og:url
頁面網址
og:type
頁面類型,預設帶入 website
og:title
頁面標題
og:description
頁面說明,對應 <meta name="description" content="This is my first page">
即可
og:image
顯示圖片,最低寬高 200 x 200
og:image:secure_url
顯示圖片 https
協定
og:image:type
顯示圖片類型
og:image:width
顯示圖片寬度
og:image:height
顯示圖片高度
fb:app_id
應用程式 ID,需要做洞察報告才需要填寫
元素 (element)
給個顯示內容都需要給予一個標籤包住,此標籤就是元素
宣告
使用 <
與 >
將元素名稱包住
<div>Hello, world!</div>
宣告一個 div
元素,顯示內容為 Hello, world!
內容
顯示給使用者看的東西,稱為內容
只要元素有內容,就必須做關閉
<div>Hello, world!
這是錯誤的宣告,打開瀏覽器後會發現好像沒什麼差別
這是因為瀏覽器會幫你做補充的行為(本質上還是漏掉的)
補充一時爽,套版火葬場!
一定要再三檢查,該關閉的就要關閉
- 開啟
Chrome dev tools
查看元素頁籤 (補充行為) - 檢視原始碼查看
Command + Alt + u
屬性
描述元素,在宣告元素與 >
之間宣告
屬性不會顯示給使用者看,通常用於跟 CSS
與 JavaScript
串接使用
<div id="first-name" class="fullname">Lin</div>
宣告一個 div
元素,擁有屬性 id
值為 first-name
以及 class
值為 fullname
,顯示內容為 Lin
能夠口語化描述自己宣告的元素,表示知道自己在做什麼了
屬性格式
屬性跟屬性之間『至少』一個空格區分
錯誤
<div id="first-name"class="fullname">Lin</div>
正確
<div id="first-name" class="fullname">Lin</div>
<div id="first-name" class="fullname">Lin</div>
<div id="first-name" class="fullname">Lin</div>
屬性數值使用『雙引號』包起來
錯誤
<div id=first-name class=fullname>Lin</div>
<div id='first-name' class='fullname'>Lin</div>
正確
<div id="first-name" class="fullname">Lin</div>
使用單引號看似沒問題,但是不符合規範,在特定情境下一樣爆炸給你看!
同一屬性多個數值時,數值使用空格區分
錯誤
<div id="first-name" class="fullname" class="my-name">Lin</div>
<div id="first-name" class="fullnamemy-name">Lin</div>
正確
<div id="first-name" class="fullname my-name">Lin</div>
顯示方式
HTML
元素顯示基本上分為兩個
區塊 (block)
佔據整列,強制後面的元素換行
內聯 (inline)
『不會』佔據整列,後面的元素會接著顯示
元素跟元素之間會有小縫隙(要視為 Bug 或是 Feature 就看個人了)
標題 h1 ~ h6
如同文書處理軟體一樣,擁有
h1
~h6
標籤代表標題
h1
最大,h6
最小
<h1>h1</h1>
<h2>h2</h2>
<h3>h3</h3>
<h4>h4</h4>
<h5>h5</h5>
<h6>h6</h6>
注重語意與結構而非呈現樣式
頁面上看到的 h6
比起 h1
小,這是因為瀏覽器預設有定義一些基本的 CSS
樣式
開啟 Chrome dev tools
> 元素
> 樣式
觀察
超連結 a
提供網頁連結能力,連結位置與目前網域不同時,稱為『外部連結』,反之為『內部連結』
外部連結
<a href="https://www.google.com/">To Google</a>
建立一個連結到 https://www.google.com/
的超連結,顯示內容為 To Google
內部連結
<a href="/about.html">About</a>
建立一個連結到 https://{{ domain }}/about.html
的超連結,顯示內容為 About
{{ domain }}
頁面當下網域
例如當下網域為 https://www.test.com/
則此超連結為 https://www.test.com/about.html
開啟目標
目前的超連結,都會將頁面直接轉跳到目的網頁
這是因為還有一個屬性 target
我們沒宣告,預設為 _self
如果想要點擊後,跳出一個新的分頁顯示,將 target
的數值設定為 _blank
<a href="https://www.google.com/" target="_blank">To Google</a>
錨點
id
當成目標位置,使用 #
表示
<div id="top">Top</div>
<a href="#top">To top</a>
捲軸滑順效果
<style>
html,
body {
scroll-behavior: smooth;
}
</style>
圖片 img
提供網頁顯示圖片能力
<img src="dog.jpeg" alt="this is dog" title="this is dog" />
src
圖片位置
alt
當圖片不存在時顯示 (無障礙友善)
title
移動到圖片上時顯示說明 (Options)
區塊 div
用於規劃範圍,沒有任何語意
具備語意的區塊元素
article
表示一篇文章
section
表示一段主題
初學階段,不用太在意語意。先求有,再求好。
內聯 span
用於同一區塊內特定範圍呈現需求,沒有任何語意
<div>
<span style="color:red;">我</span>很好!
</div>
按鈕 button
用於與使用者互動,經常搭配 form
表單一起出現
也常綁定事件來做互動
<button id="do-btn">Click Me</button>
<script>
let btn = document.querySelector('#do-btn')
btn.addEventListener('click', (e) => {
alert('You click me!')
})
</script>
在
form
內時,預設為type="submit"
所以按下去就會送出表單。如果不要送出,要設定type="button"
列舉 ul > li
用於呈現同性質多項目內容,例如:選單
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
刪除黑點點
預設樣式每個項目都有一個黑色點點,可以透過 CSS
來取消
list-style-type: none;
表格 table
用於呈現欄位項目長度相同內容,例如:學生資料
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<tr>
<td>David</td>
<td>18</td>
</tr>
<tr>
<td>Helen</td>
<td>20</td>
</tr>
</tbody>
</table>
thead
表頭
tbody
表身
tr
table row,橫向
th
table head,縱向
td
table body,縱向
沒有宣告
thead
與tbody
對於顯示沒有影響,但是架構非正規。在後續使用第三方套件時,可能無法運作
顯示格線
透過 CSS
顯示格線
<style>
th, td {
border: 1px solid #000;
}
</style>
消除間距
透過 CSS
消除間距
<style>
table {
border-spacing: 0;
border-collapse: collapse;
}
</style>
嵌入頁面 iframe
提供其他頁面顯示
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3640.6532286461784!2d120.68338871602994!3d24.148813384394593!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x34693d691d272657%3A0x2ed7da18282bc17c!2z6LWr57a16Zu76IWm6Kit6KiI5Z-56KiT5a246ZmiLeWPsOS4reWtuOmZog!5e0!3m2!1szh-TW!2stw!4v1664443717678!5m2!1szh-TW!2stw"
width="600"
height="450"
style="border: 0"
allowfullscreen=""
loading="lazy"
referrerpolicy="no-referrer-when-downgrade"
></iframe>
google map
youtube
注意跨站(CORS)與安全性問題
表單 form
提供發送資料功能,無外觀
<form method="get" action="url">
</form>
method
表單發送方法,預設為 get
。發送方法有: get
, post
, put
, delete
action
表單發送目的地,沒有宣告表示自己
GET
會將發送資料顯示在網址列,有長度限制(瀏覽器限制與伺服器限制)
無法發送二進制內容(檔案)
POST
不會將發送資料顯示在網址列,無長度限制(瀏覽器不限制,但是伺服器還是會有限制)
可以發送二進制內容(檔案)
輸入 input
提供表單輸入介面,透過屬性表示不同用途
文字
<input type="text" name="account">
type="text"
表示文字類型
name="account"
表示送出時,欄位名稱為 account
密碼
<input type="password" name="pwd">
type="password"
表示密碼類型
該類型會將輸入的內容使用 *
來表示,無法看到真實的內容
密碼類型使用
*
表示內容,感覺很安全,真的嗎?
數字
<input type="number" name="money">
type="number"
代表數字類型
e 為數學符號,可以輸入是正常的
電話
<input type="tel" name="phone">
type="tel"
代表電話類型
手機介面時,只會出現單純數字鍵
顏色
<input type="color" name="bg-color">
type="color"
代表顏色類型
不同瀏覽器顯示的介面會有所不同
日期
<input type="date" name="schedule-date">
type="date"
代表日期類型
不同瀏覽器顯示的介面會有所不同
時間
<input type="time" name="schedule-time">
type="time"
代表日期類型
不同瀏覽器顯示的介面會有所不同
提交
<input type="submit">
type="submit"
代表提交類型
該類型外觀會長得跟按鈕一樣,預設文字為『提交』
建議使用
<button type="submit">提交</button>
來表示更為適合
檔案
<input type="file" name="my-pic">
type="file"
代表檔案類型
該類型外觀由各瀏覽器定義,提供選取本地檔案功能
如要上傳檔案,
form
須滿足一定條件
form
須設定屬性enctype="multipart/form-data"
form
必須為POST
方法
測試 API: https://book.niceinfos.com/frontend/assets/html_file_upload/response.php
單選
<input type="radio" name="gender" value="male" /> 男
<input type="radio" name="gender" value="female" /> 女
使用
name
屬性為分類群組
<input type="radio" name="male" value="male" /> 男
<input type="radio" name="female" value="female" /> 女
多選
<input type="checkbox" name="music" value="a1" /> A1
<input type="checkbox" name="music" value="a2" /> A2
<input type="checkbox" name="music" value="a3" /> A3
提交後發現,music 只有一個數值,但是我明明勾選兩個以上
使用『陣列』([]
)儲存多數值
<input type="checkbox" name="music[]" value="a1" /> A1
<input type="checkbox" name="music[]" value="a2" /> A2
<input type="checkbox" name="music[]" value="a3" /> A3
下拉選單 select
提供下拉式挑選列表
單選
<select name="county">
<option value="">選擇縣市</option>
<option value="TP">台北</option>
<option value="台中">台中</option>
<option>高雄</option>
</select>
value
為數值,如沒有宣告,帶入內容
多選
<select name="county[]" multiple>
<option value="">選擇縣市</option>
<option value="TP">台北</option>
<option value="台中">台中</option>
<option>高雄</option>
</select>
multiple
為多選屬性
county[]
為陣列類型,才可容納多數值
多行輸入
允許換行輸入
<textarea name="remark" cols="30" rows="10"></textarea>
cols
欄數,影響寬度
rows
列數,影響高度
禁止拉伸
textarea {
resize: none;
}
標籤 label
提供表單元素說明,並可透過 for
屬性對應 id
代理錨點執行 focus
鎖定動作
<div>
<label for="f-fullname">全名</label>
<input type="text" name="fullname" id="f-fullname">
</div>
聯絡表單實作
API
https://book.niceinfos.com/frontend/assets/html_contact_form/api.php
上傳作業
將完成檔案上傳,使用英文名字,例如: david.html
訪問密碼
BIQGy
CSS
負責樣式呈現,建構在 HTML 架構之上
宣告格式
h1 {
color: red;
}
h1
選擇器,對應 HTML
特徵
color
屬性名稱
red
屬性數值
每個屬性結束要記得用
;
關閉,最後一個屬性可以不用關閉
宣告位置
HTML 屬性
<div style="color:#ffffff; background: #000000">屬性宣告</div>
使用
HTML
屬性style
來宣告
HTML 元素
<style>
div {
color: #ffffff;
background: #000000;
}
</style>
CSS 檔案
css/main.css
div {
color: #ffffff;
background: #000000;
}
在
CSS
檔案內不要宣告<style>
標籤
index.html
<head>
<link rel="stylesheet" href="css/main.css">
</head>
rel="stylesheet"
使用樣式表 (CSS
)
href
CSS
位置
選擇器特性
範圍 scope
精準度決定影響範圍
<style>
div {
color: red;
}
</style>
<div>一號</div>
<span>二號</span>
<span>
<div>三號</div>
</span>
一號
符合 div
選擇器,顯示為紅色
二號
不符合 div
選擇器,顯示為黑色
三號
符合 div
選擇器,顯示為紅色
雖然三號的
div
在span
內,但此選擇器嚴謹度為:只要是div
就可以
繼承 inherit
子層有條件的繼承父層屬性
<style>
span {
color: blue;
}
</style>
<div>一號</div>
<span>二號</span>
<span>
<div>三號</div>
</span>
一號
不符合 span
選擇器,顯示為黑色
二號
符合 span
選擇器,顯示為藍色
三號
不符合 span
選擇器,但是上層符合 span
選擇器,屬性繼承後顯示為藍色
複寫 overwrite
越接近目標,優先權越高
HTML 屬性 > HTML 元素 > CSS 檔案
<style>
div {
color: red;
background: #000000;
}
</style>
<div>一號</div>
<div style="color: yellow;">二號</div>
由上往下複寫
後面宣告屬性會覆寫前面宣告屬性
<style>
div {
color: red;
}
div {
color: yellow;
}
</style>
<div>一號</div>
權重 priority
遇到同樣精準度屬性時,採用複寫機制處理
!important
> HTML 屬性
> ID 選擇器
> Class 選擇器 | 偽類 | 屬性選擇器
> 元素 (Element)
> 全域 (*)
<style>
div {
color: red !important;
}
div {
color: yellow;
}
span > div {
color: blue;
}
</style>
<div>一號</div>
<span>
<div>二號</div>
</span>
依據複寫機制,理論上 一號
要呈現黃色,但在 !important
的干涉下,後面的宣告無法複寫(權重)
<style>
div#sp {
color: green;
}
div {
color: yellow;
}
span > div {
color: blue;
}
div {
color: red;
}
</style>
<div>一號</div>
<span>
<div>二號</div>
</span>
<span>
<div id="sp">三號</div>
</span>
一號
- 符合
yellow
- 符合
red
- 最終顯示
red
二號
- 符合
yellow
- 符合
blue
- 符合
red
因red
精準度較低,複寫失敗 - 最後顯示
blue
三號
- 符合
green
- 符合
yellow
因yellow
權重較低 (ID
>Element
),複寫失敗 - 符合
blue
因blue
權重較低 (ID
>Element
),複寫失敗 - 符合
red
因red
權重較低 (ID
>Element
),複寫失敗 - 最後顯示
green
ID 選擇器
使用符號
#
表示,同一個頁面不可以出現重複的 ID 名稱
<style>
#one {
color: #ffffff;
background: #000000;
}
</style>
<div id="one">One</div>
CLASS 選擇器
使用符號
.
表示,同一個頁面可以出現重複的 CLASS 名稱
<style>
.more {
color: #ffffff;
background: #000000;
}
</style>
<div class="more">One</div>
<div class="more">Two</div>
盒子模型
盒子模型表示一個元素佔據範圍
觀察方式:
Chrome Developer Tool > Elements > Computed
組成:
content > padding > border > margin
內容 content
除了內容文字本身外,寬度(
width
)與高度(height
)兩個屬性也會影響
寬度 width
.w-100 {
width: 100px;
}
數值由一個數字與一個單位組成,像素(px
)表示固定單位
高度 height
.h-100 {
height: 100px;
}
數值由一個數字與一個單位組成,像素(px
)表示固定單位
建立一個正方形
.cube {
width: 100px;
height: 100px;
background: #000;
color: #fff;
}
行高 line-height
.nh-100 {
line-height: 100px;
}
當行高與高度數值一樣時,會產生垂直置中的效果
換行觸發條件:
- 多個區塊元素
<br/>
換行元素- 內容超過元素本身寬度
使用行高要注意破版問題
文字顏色 color
.color-red {
color: red;
}
.color-custom {
color: #dedede;
}
使用個別顏色的英文名稱,或使用十六進制的色碼皆可 (#dedede
)
文字大小 font-size
.fs-18 {
font-size: 18px;
}
數值由一個數字與一個單位組成,像素(px
)表示固定單位
文字線條 font-weight
.fw-100 {
font-weight: 100;
}
.fw-900 {
font-weight: 900;
}
100 線條最細,900 線條最粗
不可以帶單位,例如
100px
這樣是錯誤的
水平對齊
.text-left {
text-align: left;
}
.text-center {
text-align: center;
}
.text-right {
text-align: right;
}
預設是 left
背景顏色 background-color
.bg-red {
background-color: red;
}
背景圖片 background-image
.bg-image {
background-image: url(images/some.png);
}
使用 url
抓取目標圖片
當圖片小於元素寬高時,會自動重複顯示
使用 background-repeat: no-repeat;
來取消重複
.bg-image {
background-image: url(images/some.png);
background-repeat: no-repeat;
}
內距 padding
content 與 border 之間的距離
.pt-20 {
padding-top: 20px;
}
.pe-20 {
padding-right: 20px;
}
.pb-20 {
padding-bottom: 20px;
}
.ps-20 {
padding-left: 20px;
}
.p-20 {
padding: 20px;
}
.p-20-40 {
padding: 20px 40px;
}
.p-custom {
padding: 10px 20px 30px 40px;
}
padding: 20px
表示『上下左右』都是 20px
padding: 20px 40px
表示『上下』是 20px
,『左右』是 40px
padding: 10px 20px 30px 40px
表示『上』『右』『下』『左』分別為 10px
20px
30px
40px
邊匡 border
.border {
border-width: 3px;
border-color: #000000;
border-style: solid;
/* border: 3px solid #000000; */
}
.border-radius {
border-top-right-radius: 10px;
border-top-left-radius: 10px;
border-bottom-right-radius: 10px;
border-bottom-left-radius: 10px;
/* border-radius: 10px; */
}
border: 3px solid #000000;
等同三個屬性合併宣告
border-radius: 10px;
倒圓角
畫圓圈
.circle {
width: 100px;
height: 100px;
border-radius: 100px;
}
條件:
- 正方型
- 倒圓角數據大於等於寬高
外距 margin
border 與其他區塊之間的距離
.mt-20 {
margin-top: 20px;
}
.me-20 {
margin-right: 20px;
}
.mb-20 {
margin-bottom: 20px;
}
.ms-20 {
margin-left: 20px;
}
.m-20 {
margin: 20px;
}
.m-20-40 {
margin: 20px 40px;
}
.m-custom {
margin: 10px 20px 30px 40px;
}
margin: 20px
表示『上下左右』都是 20px
margin: 20px 40px
表示『上下』是 20px
,『左右』是 40px
margin: 10px 20px 30px 40px
表示『上』『右』『下』『左』分別為 10px
20px
30px
40px
將左右外距設為
auto
,區塊會水平置中
計算模式 box-sizing
.block {
width: 200px;
height: 100px;
padding: 20px;
border: 2px solid #000;
}
明明設定了寬 200 高 100,但是卻沒有如預期的寬高顯示?
content-box
預設計算模式為 content-box
,此模式下內容寬高計算公式為
content + padding + border
帶入上面的範例得出寬高
寬 = 200 + (20 * 2) + (2 * 2) = 244
高 = 100 + (20 * 2) + (2 * 2) = 144
如此計算方式,在排版上造成很大的困擾(每次異動都要計算)
border-box
計算模式改為 border-box
,此模式下寬高計算公式為
content = width - padding - border
表示設定寬高後,content
可用範圍為扣除 padding
與 border
如此,寬高就不會異動
.border-box {
box-sizing: border-box;
}
顯示模式
每個元素都有預設的顯示模式(瀏覽器定義),可以透過
CSS
來修改
區塊
.block {
display: block;
}
- 會佔據一列
- 具備寬度(
width
)與高度(height
)的概念
內聯
display: inline;
- 不會佔據一列
- 不具備寬度(
width
)與高度(height
)的概念 - 預設兩個元素之間會有空隙,且無法消除
內聯區塊
display: inline-block;
- 不會佔據一列
- 具備寬度(
width
)與高度(height
)的概念 - 預設兩個元素之間會有空隙,且無法消除
隱藏區塊
display: none;
超出寬高處理
overflow: hidden;
overflow: auto;
overflow: scroll;
overflow-y: auto;
overflow-x: auto;
初始化
由於每個瀏覽器都會預設不同的預設樣式,所以在開始切版之前,要先將樣式初始化,讓每個瀏覽器的預設值一致
初始化套件 reset css
簡單暴力的初始化套件,將所以元素都重置,適合 0% -> 100% 樣式打造
優點
- 適合充滿想法的設計師
- 所有樣式都一致
缺點
- 全部樣式都要自己寫
- 使用第三方套件時,可能會導致樣式無法正常顯示問題
初始化套件 Normalize.css
現代化的初始化套件,保留部分預設樣式並且修正一些瀏覽器 bug
,適合 50% -> 100% 樣式打造
優點
- 不需要全部樣式自己寫就可以得到一個還能看得樣式
- 原始碼註解清楚
- 第三方套件友善
缺點
- 不同瀏覽器還是有可能發生樣式不一致問題
- 充滿想法的設計師可能無法正常發揮
簡易初始化
*,
*::before,
*::after {
padding: 0;
margin: 0;
box-sizing: border-box;
}
切版初體驗
定位
區塊內容可以透過定位做進階排版
靜態定位 static
預設定位方式
.pos-static {
position: static;
}
由於是靜態,所以無法自由移動
相對定位 relative
原地浮起,佔據寬高,定位原點為自己
.pos-relative {
position: relative;
}
原則上使用 relative
都是作為下一層的定位原點用,不會拿來做移動使用
絕對定位 absolute
原地浮起,不佔寬高,定位原點為第一個『非靜態』上層
.pos-absolute {
position: absolute;
}
原則上使用 absolute
上一層要設定為 relative
當成定位原點,都沒找到時,使用根元素(html
)
固定定位 fixed
原地浮起,不佔寬高,定位原點為根元素
.pos-fixed {
position: fixed;
}
原則上使用 fixed
都會是第一層元素,因為該方式會抓取根元素(html
),如非在第一層,會使結構錯亂
圖層 z-index
決定浮起區塊的上下位置,類似
PS
中的圖層
.z-99 {
z-index: 99;
}
數字越大,圖層越高
移動
漂浮後,可進行『上』『下』『左』『右』移動
.left-10 {
left: 10px;
}
.right-10 {
right: 10px;
}
.top-10 {
top: 10px;
}
.bottom-10 {
bottom: 10px;
}
畫臉
使用目前所學畫出一張臉
單位
像素 px
絕對大小
繼承倍數 em
1em
= 100%
使用上一層的大小當 base
去相乘
.parent {
font-size: 20px;
}
.child-1 {
font-size: 1.5em;
}
.child-2 {
font-size: 1.5em;
}
<div class="parent">
<div class="child-1">
<div class="child-2">
Child-2
</div>
</div>
</div>
Child-2
= 20px * 1.5 * 1.5
= 45px
根倍數 rem
使用 html
大小當 base
去相乘
html {
font-size: 20px;
}
.parent {
font-size: 40px;
}
.child-1 {
font-size: 1.5em;
}
.child-2 {
font-size: 1.5rem;
}
<div class="parent">
<div class="child-1">
<div class="child-2">
Child-2
</div>
</div>
</div>
Child-2
= 20px * 1.5rem =
45px`
百分比 %
同 em
會繼承,120%
= 1.2em
點陣 pt
點陣式印表機單位, 1pt
= 1/72in
72 dpi
時1px
=1pt
96 dpi
時1px
=0.75pt (72 / 96 = 0.75)
英吋 in
96 dpi
時 1in
= 96px
公分 cm
96 dpi
時 1cm
= 37.795275593333px
公釐 mm
96 dpi
時 1mm
= 3.779527559333px
『pt』『im』『cm』『mm』 為印刷單位,實際上幾乎很少用到
flexbox
由主軸跟副軸組成,目前排版主流
宣告 display: flex
display: flex;
display: inline-flex;
<style>
*,
*::after,
*::before {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.flex {
display: flex;
background: yellow;
width: 90%;
margin: 20px auto;
height: 300px;
}
.flex-item {
width: 100px;
height: 100px;
background: #000;
color: #fff;
margin: 0 10px;
}
</style>
<div class="flex">
<div class="flex-item">1</div>
<div class="flex-item">2</div>
<div class="flex-item">3</div>
</div>
flex
內的區塊元素,不會佔據整列
方向 flex-direction
flex-direction: row;
flex-direction: row-reverse;
flex-direction: column;
flex-direction: column-reverse;
row
由左到右 (預設)
row-reverse
由右到左
column
由上到下
column-reverse
由下到上
內容超出行為 flex-wrap
flex-wrap: no-wrap;
flex-wrap: wrap;
flex-wrap: wrap-reverse;
no-wrap
不換行,內容寬高會自動調整 (預設)
wrap
換行
wrap-reverse
換行,上下反轉
主軸對齊 justify-content
justify-content: flex-start;
justify-content: flex-end;
justify-content: center;
justify-content: space-around;
justify-content: space-between;
justify-content: space-evenly;
flex-start
靠左 (預設)
flex-end
靠右
center
置中
space-around
左右平均寬度分散
space-between
左右貼平分散
space-evenly
平均分散
副軸對齊 align-content
align-content: flex-start;
align-content: flex-end;
align-content: center;
align-content: space-around;
align-content: space-between;
align-content: space-evenly;
flex-start
靠上 (預設)
flex-end
靠下
center
置中
space-around
上下平均寬度分散
space-between
上下貼平分散
space-evenly
平均分散
要設定
flex-wrap: wrap;
才會有效果
副軸內容對齊 align-items
align-items: stretch;
align-items: flex-start;
align-items: flex-end;
align-items: center;
align-items: baseline;
stretch
高度延伸,內容區塊有設定高度時,則依據高度為主 (預設)
flex-start
靠上
flex-end
靠下
center
置中
baseline
內容基準線
排序 order
order: 1;
order: -1;
數字越小越前面,預設 0
練習
使用 flex
重新切版 切版初體驗
參考資源
偽類
元素狀態,使用
:
符號
游標停留 :hover
.block {
width: 100px;
height: 100px;
background: #000;
color: #fff;
margin: 10px auto;
display: flex;
justify-content: center;
align-items: center;
}
.block:hover {
background: red;
}
已選取 :checked
用於可切換選取狀態的元素
- radio
- checkbox
.on-checked:checked {
width: 100px;
height: 100px;
}
反向 :not(selector)
.block {
background: red;
}
.block div:not(.item) {
color: yellow;
}
子層指定 :nth-child(n)
ul li:nth-child(3) {
background: red;
color: yellow;
}
子層倍數指定 :nth-child(an+b)
a
基數
n
從 0
開始自動遞增
b
常數
ul.n2 li:nth-child(2n) {
background: red;
color: yellow;
}
ul.n2_1 li:nth-child(2n + 1) {
background: red;
color: yellow;
}
ul.n3 li:nth-child(3n) {
background: red;
color: yellow;
}
子層第一個 :first-child
等同 :nth-child(1)
ul li:first-child {
background: red;
color: yellow;
}
子層最後一個 :last-child
ul li:last-child {
background: red;
color: yellow;
}
偽元素
不存在於
document tree
(DOM
),符號::
前方插入 ::before
.block::before {
content: 'David ';
}
<div class="block">Say Hi!</div>
由於不存在於
DOM
內,無法選取
content
為必要屬性,如沒有此屬性,::before
無法生效
.block::before {
content: attr(data-name) ' ';
}
<div class="block" data-name="David">Say Hi!</div>
content
也可以搭配attr
抓取元素本身屬性數值來呈現
後方插入 ::after
.block::after {
content: ' David';
}
<div class="block">Hello</div>
文字反白 ::selection
.blue {
height: 50px;
line-height: 50px;
border: 1px solid #dedede;
padding: 10px;
}
.blue::selection {
background: blue;
color: #fff;
}
<div class="blue">Select Me Show Blue.</div>
第一行 ::first-line
.block::first-line {
color: red;
}
<div class="block">
我是誰?<br />
我在哪?
</div>
第一個字 ::first-letter
.block::first-letter {
color: red;
font-size: 40px;
}
<div class="block">
我是誰?<br />
我在哪?
</div>
進階選擇器
後代 空格
指定子層樣式,不分階層
.block .item {
background: red;
color: yellow;
}
<div class="block">
<div class="item">1</div>
<div class="item">2</div>
<div>
<div class="item">3</div>
</div>
</div>
子階 >
指定子層樣式,僅限第一層
.block > .item {
background: red;
color: yellow;
}
<div class="block">
<div class="item">1</div>
<div class="item">2</div>
<div>
<div class="item">3</div>
</div>
</div>
嚴謹相鄰 +
元素
x
之後的第一個元素y
.x-item + .y-item {
color: red;
font-size: 30px;
}
<div class="block">
<div class="x-item">X</div>
<div class="y-item">Y</div>
<div class="y-item">Y</div>
<div class="y-item">Y</div>
</div>
寬鬆相鄰 ~
元素
x
之後的元素y
.x-item ~ .y-item {
color: red;
font-size: 30px;
}
<div class="block">
<div class="x-item">X</div>
<div class="y-item">Y</div>
<div class="y-item">Y</div>
<div class="y-item">Y</div>
</div>
屬性選擇器
基本 [attr]
符合屬性名稱
.item[title] {
color: red;
}
<div class="block">
<div class="item" title="1">1</div>
<div class="item" title="2">2</div>
<div class="item">3</div>
</div>
指定值 [attr='value']
符合屬性名稱與數值
.item[title='1'] {
color: red;
}
<div class="block">
<div class="item" title="1">1</div>
<div class="item" title="2">2</div>
<div class="item">3</div>
</div>
包含值 [attr*='value']
符合屬性名稱與包含數值(模糊比對)
.item[title*='1'] {
color: red;
}
<div class="block">
<div class="item" title="123">1</div>
<div class="item" title="213">2</div>
<div class="item">3</div>
</div>
開頭值 [attr^='value']
符合屬性名稱與數值開頭
.item[title^='1'] {
color: red;
}
<div class="block">
<div class="item" title="123">1</div>
<div class="item" title="213">2</div>
<div class="item">3</div>
</div>
結尾值 [attr$='value']
符合屬性名稱與數值最後
.item[title$='1'] {
color: red;
}
<div class="block">
<div class="item" title="123">1</div>
<div class="item" title="251">2</div>
<div class="item">3</div>
</div>
多值 [attr~='value']
符合屬性名稱與其中一個數值
.item[title~='888'] {
color: red;
}
<div class="block">
<div class="item" title="123 888">1</div>
<div class="item" title="213">2</div>
<div class="item" title="888">3</div>
</div>
手風琴選單
提示
:checked
- 相鄰選擇器
position
Step 1
建立 html
架構
<div class="accrodion">
<div class="tab">
<input type="checkbox" />
<label for="">One</label>
<div class="tab-content">Hello 1</div>
</div>
</div>
Step 2
定義基本樣式
.accrodion {
width: 800px;
margin: 20px auto;
}
.accrodion .tab {
border: 1px solid #1459e3;
}
.accrodion .tab label {
display: flex;
align-items: center;
width: 100%;
height: 50px;
padding: 0 10px;
background: #1459e3;
color: #fff;
font-size: 20px;
}
.accrodion .tab .tab-content {
padding: 10px;
}
Step 3
串接 label
與 input
<div class="accrodion">
<div class="tab">
<input type="checkbox" id="tab-1" />
<label for="tab-1">One</label>
<div class="tab-content">Hello 1</div>
</div>
</div>
Step 4
建立 .tab-content
連動樣式
.accrodion .tab .tab-content {
padding: 10px;
display: none;
}
.accrodion .tab input:checked ~ .tab-content {
display: block;
}
Step 5
建立 label
符號
.accrodion .tab label {
display: flex;
align-items: center;
width: 100%;
height: 50px;
padding: 0 10px;
background: #1459e3;
color: #fff;
font-size: 20px;
cursor: pointer;
position: relative;
}
.accrodion .tab label::after {
content: '+';
position: absolute;
width: 50px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
right: 0;
top: 0;
font-size: 30px;
}
.accrodion .tab input:checked ~ label::after {
content: '-';
}
Step 6
隱藏 checkbox
.accrodion .tab input[type='checkbox'] {
display: none;
}
done
複製兩個 tab
更改 id
<div class="accrodion">
<div class="tab">
<input type="checkbox" id="tab-1" />
<label for="tab-1">One</label>
<div class="tab-content">Hello 1</div>
</div>
<div class="tab">
<input type="checkbox" id="tab-2" />
<label for="tab-2">TWO</label>
<div class="tab-content">Hello 2</div>
</div>
<div class="tab">
<input type="checkbox" id="tab-3" />
<label for="tab-3">THREE</label>
<div class="tab-content">Hello 3</div>
</div>
</div>
SCSS
可程式化階層式開發
CSS
安裝編譯套件
VSCode
> 延伸套件
> Live Sass Complier
建議專案結構
flowchart LR A[fa:fa-folder project root] A-->dist[fa:fa-folder dist] dist-->css[fa:fa-folder css] dist-->js[fa:fa-folder js] dist-->images[fa:fa-folder images] dist-->index[fa:fa-file index.html] css-->|fa:fa-ban 禁止異動| cssfile[fa:fa-file some.css] A-->src[fa:fa-folder src] src-->scss[fa:fa-folder scss] scss-->scssfile[fa:fa-file some.scss] scssfile-->|fa:fa-bolt 編譯產生| cssfile
編譯設定
Windows: Ctrl
+ Shift
+ P
Mac: Command
+ Shift
+ P
> Open Workspace Settings (JSON)
{
"liveSassCompile.settings.formats": [
{
"format": "expanded",
"extensionName": ".css",
"savePath": "/dist/css"
}
],
"liveServer.settings.root": "/dist",
"liveServer.settings.CustomBrowser": "chrome"
}
format
編譯格式
compressed
最小化 (Production)expanded
展開 (Development)
extensionName
編譯後副檔名
savePath
編譯後檔案放置路徑
啟動 watch
VSCode
右下角 Watch my Sass
變數
scss
$main-color: red;
.container {
color: $main-color;
}
css
.container {
color: red;
}
$
大部分語言都是變數的意思
運算
scss
$header-height: 70px;
#header {
height: $header-height;
}
#wrap {
padding-top: $header-height + 70;
}
css
#header {
height: 70px;
}
#wrap {
padding-top: 140px;
}
巢狀 (Nesting)
scss
#header {
background: yellow;
&:hover {
background: blue;
}
.title {
color: red;
}
}
css
#header {
background: yellow;
}
#header:hover {
background: blue;
}
#header .title {
color: red;
}
預設為『後代』選擇器,
&
代表自己
匯入 @import
將檔案內容複製進來
_reset.scss
*,
*::after,
*::before {
margin: 0;
padding: 0;
box-sizing: border-box;
}
scss
@import 'reset';
#header {
background: yellow;
}
css
*,
*::after,
*::before {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#header {
background: yellow;
}
- 使用
@import
匯入檔案 - 匯入檔案名稱不需要加
_
與副檔名.scss
- 被匯入檔案,檔案名稱最前面為
_
可避免被編譯器編譯 - 被匯入檔案,通常為共用屬性,不會單獨使用
混合 @mixin
scss
@mixin container {
width: 1140px;
margin: 10px auto;
}
#header {
color: red;
@include container;
}
css
#header {
color: red;
width: 1140px;
margin: 10px auto;
}
@mixin
宣告
@include
使用
擴展 @extend
scss
#header {
color: red;
height: 300px;
}
#header2 {
@extend #header;
background: yellow;
color: blue;
}
css
#header, #header2 {
color: red;
height: 300px;
}
#header2 {
background: yellow;
color: blue;
}
附加選擇器到
@extend
目的位置
函數 @function
scss
@function add-margin($value, $type: px) {
@return $value + 30 + $type;
}
#header {
background: red;
margin: add-margin(20);
}
#wrap {
margin: add-margin(10, em);
}
css
#header {
background: red;
margin: 50px;
}
#wrap {
margin: 40em;
}
切版實戰
前置作業
參考 SCSS 建立專案
flowchart LR A[fa:fa-folder project root] A-->dist[fa:fa-folder dist] dist-->css[fa:fa-folder css] dist-->js[fa:fa-folder js] dist-->images[fa:fa-folder images] dist-->index[fa:fa-file index.html] css-->|fa:fa-ban 禁止異動| cssfile[fa:fa-file some.css] A-->src[fa:fa-folder src] src-->scss[fa:fa-folder scss] scss-->scssfile[fa:fa-file some.scss] scssfile-->|fa:fa-bolt 編譯產生| cssfile
建立 src/scss/_reset.scss
*,
*::after,
*::before {
padding: 0;
margin: 0;
box-sizing: border-box;
}
建立 src/scss/index.scss
@import 'reset';
使用 Watch Sass
編譯,看到 dist/css/index.css
表示前置作業完成
Step 1
建置 header
架構
dist/index.html
<div id="header">
<div>Logo</div>
<div>Menu</div>
</div>
Step 2
編輯 header
樣式
看起來內容有局限在一定範圍內,所以先將 header
套入 .container
dist/index.html
<div id="header">
<div class="container">
<div class="header-content">
<div>Logo</div>
<div>Menu</div>
</div>
</div>
</div>
增加 .container
樣式
src/scss/_reset.scss
.container {
max-width: 1000px;
height: 100%;
padding: 0 30px;
position: relative;
margin: auto;
}
加入 .header-content
樣式
src/scss/index.scss
#header {
height: 80px;
.header-content {
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
background: rgb(240, 240, 240);
padding: 0 15px;
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
}
}
Step 3
編輯 logo
與 menu
dist/index.html
<div>
<a href=""><img src="images/logo.svg" alt="appedu" /></a>
</div>
<div class="header-menu">
<ul>
<li>
<a href="#">
<div class="en">About Us</div>
<div>關於赫綵</div>
</a>
</li>
<li>
<a href="#">
<div class="en">Join Us</div>
<div>加入赫綵</div>
</a>
</li>
<li>
<a href="#">
<div class="en">Courses</div>
<div>課程總覽</div>
</a>
</li>
</ul>
</div>
加入 ul
與 a
的初始化
src/scss/_reset.scss
ul {
list-style-type: none;
}
a {
color: inherit;
text-decoration: none;
}
加入 header-menu
樣式
src/scss/index.scss
#header {
// ...
.header-menu {
ul {
display: flex;
li {
font-weight: 700;
text-align: center;
margin-right: 30px;
&:last-child {
margin-right: 0;
}
.en {
text-transform: uppercase;
font-size: 10px;
}
}
}
}
}
Step 4
建立一頁式表單區塊
dist/index.html
<div id="join-form">
<div class="container">
<div>LEFT</div>
<div>RIGHT</div>
</div>
</div>
加入 #join-form
樣式
src/scss/index.scss
#join-form {
min-height: 100vh;
background-color: #000;
background-image: url(../images/banner.jpeg);
background-size: cover;
color: #fff;
}
將 #header
設定為 fixed
src/scss/index.scss
#header {
position: fixed;
}
此時發現區塊沒有滿版,調整一下
src/scss/index.scss
#header {
position: fixed;
width: 100%;
}
滿版了,但是點不到選單
src/scss/index.scss
#header {
position: fixed;
width: 100%;
z-index: 9;
}
Step 5
編輯表單內容區塊
dist/index.html
<div id="join-form">
<div class="container">
<div class="join-content">
<div>LEFT</div>
<div>RIGHT</div>
</div>
</div>
</div>
加入 .join-content
樣式
src/scss/index.scss
#join-form {
// ...
.join-content {
width: 100%;
height: 500px;
background: rgba(10, 70, 161, 0.879);
}
}
因為要讓區塊在畫面中間,使用 flex
水平垂直置中
增加 #join-form
樣式
src/scss/index.scss
#join-form {
// ...
display: flex;
align-content: center;
justify-content: center;
// ...
}
此時發現 .join-content
沒有如預期的寬度滿版
發現是 .container
受到 flex
影響沒有滿版
增加 .container
樣式
src/scss/_reset.scss
.container {
// ...
flex: 1;
}
Step 6
設計表單內容
dist/index.html
<div id="join-form">
<div class="container">
<div class="join-content">
<div class="join-content-left">
<h3 class="slogan">加入赫綵,人生精彩</h3>
<div class="join-text">
就我個人來說,加入赫綵對我的意義,不能不說非常重大。一般來說,儘管如此,我們仍然需要對加入赫綵保持懷疑的態度。加入赫綵可以說是有著成為常識的趨勢。這必定是個前衛大膽的想法。培根說過一句很有意思的話,知識貧乏最能讓人生出許多懷疑。請諸位將這段話在心中默念三遍。儘管如此,別人往往卻不這麼想。
</div>
</div>
<div class="join-content-right">
<h3 class="slogan">加入赫綵,就是現在</h3>
<div>
<input type="text" placeholder="姓名" class="form-control" />
</div>
<div>
<input type="text" placeholder="信箱" class="form-control" />
</div>
<div class="text-end">
<button class="btn">立即加入</button>
</div>
</div>
</div>
</div>
</div>
增加樣式
src/scss/index.scss
#join-form {
// ..,
.join-content {
// ...
display: flex;
padding: 20px;
> div {
width: 50%;
}
.join-content-left {
border-right: 2px dashed #fff;
padding-right: 20px;
}
.join-content-right {
padding-left: 20px;
}
}
}
加入 .slogan
通用樣式
src/scss/_reset.scss
.slogan {
text-align: center;
font-size: 26px;
padding-top: 30px;
padding-bottom: 10px;
margin-bottom: 20px;
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: calc(50% - 140px);
width: 0;
height: 3px;
border-left: solid 100px #1072c2;
border-right: solid 180px #fff;
}
}
加入 .join-text
樣式
src/scss/index.scss
#join-form {
// ..,
.join-content {
// ...
.join-text {
line-height: 30px;
text-align: justify;
}
}
}
加入 .form-control
與 .btn
樣式
src/scss/index.scss
#join-form {
// ..,
.join-content {
// ...
.form-control {
width: 100%;
height: 40px;
margin-bottom: 20px;
padding: 0 15px;
font-size: 16px;
border-radius: 3px;
border: 0;
outline: 0;
}
.btn {
padding: 10px 15px;
border: 0;
outline: 0;
background: rgb(64, 5, 165);
color: #fff;
font-size: 14px;
font-weight: 700;
letter-spacing: 1px;
border-radius: 3px;
cursor: pointer;
&:hover {
background: rgb(84, 31, 175);
}
}
}
}
加入 .text-end
通用樣式
src/scss/_reset.scss
.text-end {
text-align: right;
}
將 #join-content
高度改為為最小高度,讓內容自動擴增
src/scss/index.scss
#join-form {
// ...
.join-content {
// ...
// height: 500px;
min-height: 200px;
// ...
}
}
Step 7
建立課程顧問頁面
dist/index.html
<div id="courses">
<h3 class="slogan">課程顧問</h3>
</div>
加入 #courses
樣式
src/scss/index.scss
#courses {
height: 50vh;
background-image: url(../images/process.jpeg);
background-size: cover;
padding-top: 30px;
padding-bottom: 30px;
color: #fff;
}
此時發現背景圖片白色與字體白色色調太近,文字可讀性低
Step 8
加入 .mask
樣式
src/scss/_reset.scss
.mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.438);
}
#courses
區塊調整
dist/index.html
<div id="courses">
<div class="courses-content">
<h3 class="slogan">課程顧問</h3>
</div>
<div class="mask"></div>
</div>
增加對應樣式
src/scss/index.scss
#courses {
// ...
position: relative;
.courses-content {
position: relative;
z-index: 2;
}
}
Step 9
製作 timeline
dist/index.html
<div class="timeline">
<div class="item">
<div class="circle"></div>
<div class="text">
<h3>需求訪談</h3>
<div>深度了解需求,給予最適合的建議</div>
</div>
</div>
</div>
增加 .timeline
樣式
src/scss/_reset.scss
.timeline {
position: relative;
width: 100%;
}
增加 .item
樣式
src/scss/_reset.scss
.timeline {
// ...
.item {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
position: relative;
padding-top: 20px;
padding-bottom: 20px;
word-break: break-all;
}
}
增加 .circle
樣式
src/scss/_reset.scss
.timeline {
// ...
.item {
// ...
.circle {
width: 24px;
height: 24px;
border-radius: 24px;
background: red;
}
}
}
此時 .circle
並沒有在中間,因為他與 .text
一同分享 .item
的 flex
空間
增加 .text
樣式
scr/scss/_reset.scss
.timeline {
// ...
.item {
// ...
.text {
position: absolute;
padding: 20px;
background: red;
border-radius: 8px;
}
}
}
透過 padding
產生內距,由內容稱出寬高,避免固定寬高破版
此時 .text
蓋在 .circle
上面,須將 .text
推到旁邊
.timeline {
// ...
.item {
// ...
.text {
// ...
left: calc(50% + 50px);
}
}
}
此時 .circle
因為 .text
設定為 position: absolute;
已經不佔據寬高,所以獨享 .item
calc
的 +
符號兩邊要留空白,不然編譯會出問題
增加白色虛線
src/scss/_reset.scss
.timeline {
// ...
&::after {
content: '';
position: absolute;
border-left: 4px dashed #fff;
height: 100%;
left: calc(50% - 2px);
top: 0;
}
// ...
}
此時 .circle
被白色虛線壓住了,調整一下
src/scss/_reset.scss
.timeline {
// ...
.item {
// ...
z-index: 2;
}
}
增加第二個 .item
dist/index.html
<div class="timeline">
<!-- ... -->
<div class="item">
<div class="circle"></div>
<div class="text">
<h3>課程搭配</h3>
<div>提供專業課程配套方案</div>
</div>
</div>
</div>
將偶數 .item
內的 .text
往另一邊推
src/scss/_reset.scss
.timeline {
// ..
.item {
// ...
&:nth-child(even) {
.text {
right: calc(50% + 50px);
text-align: right;
}
}
}
}
看起來怪怪的,這是因為原先的 .text
已經有 left
屬性,在 CSS
覆寫疊加機制下,最終偶數 .item
的 .text
會變成
.text {
// ...
left: calc(50% + 50px);
right: calc(50% + 50px);
}
一下子要左邊一下子要右邊,搞得我好亂啊!
使用 unset
將 left
取消設定
src/scss/_reset.scss
.timeline {
// ..
.item {
// ...
&:nth-child(even) {
.text {
// ...
left: unset;
}
}
}
}
將剩下的區塊補完
dist/index.html
<div class="timeline">
<div class="item">
<div class="circle" data-color="yellow"></div>
<div class="text">
<h3>需求訪談</h3>
<div>深度了解需求,給予最適合的建議</div>
</div>
</div>
<div class="item">
<div class="circle"></div>
<div class="text">
<h3>課程搭配</h3>
<div>提供專業課程配套方案</div>
</div>
</div>
<div class="item">
<div class="circle"></div>
<div class="text">
<h3>免費試聽</h3>
<div>實際體驗課程狀況</div>
</div>
</div>
<div class="item">
<div class="circle"></div>
<div class="text">
<h3>分期付款</h3>
<div>提供無壓力的付款方式</div>
</div>
</div>
<div class="item">
<div class="circle"></div>
<div class="text">
<h3>課後關懷</h3>
<div>關心上課情況,給予適度調整</div>
</div>
</div>
<div class="item">
<div class="circle"></div>
<div class="text">
<h3>就業輔導</h3>
<div>結訓後,提供就業媒合</div>
</div>
</div>
</div>
補完發現,內容跑去外面了(破版),這是因為 #courses
設定固定高度 height: 50vh;
src/scss/index.scss
#courses {
// height: 50vh;
min-height: 50vh;
}
將 .timeline
內的第一個 .item
與最後一個 .item
調整一下,讓線條可以突出
src/scss/_reset.scss
.timeline {
// ...
.item {
// ...
&:first-child {
padding-top: 50px;
}
&:last-child {
padding-bottom: 50px;
}
}
}
設定交錯顏色
src/scss/_reset.scss
.flow {
// ...
.item {
// ...
.circle {
// ...
background: rgb(10, 149, 151);
}
.text {
// ...
background: rgb(10, 149, 151);
}
&:nth-child(even) {
.circle {
background: blue;
}
.text {
// ...
background: blue;
}
}
&:nth-child(3n) {
.circle {
background: red;
}
.text {
background: red;
}
}
&:nth-child(4n) {
.circle {
background: rgb(10, 149, 151);
}
.text {
background: rgb(10, 149, 151);
}
}
&:nth-child(5n) {
.circle {
background: blue;
}
.text {
background: blue;
}
}
}
}
Step 10
製作頁尾 #footer
dist/index.html
<div id="footer">
<div class="container">
<div class="footer-content">
<div>
<img src="images/logo.svg" alt="" />
</div>
<div>© appedu 2022.</div>
</div>
</div>
</div>
增加 #footer
樣式
src/scss/index.scss
#footer {
height: 100px;
.footer-content {
height: 100%;
padding: 0 15px;
display: flex;
align-items: center;
justify-content: space-between;
}
}
變形
將元素進行『旋轉』『位移』『縮放』『傾斜』等效果
使用 transform
屬性
旋轉
可將元素進行角度旋轉
使用 transform
屬性來定義 rotate
,單位為 deg
宣告
transform: rotate(30deg);
html
<div class="block"></div>
<div class="block rotate"></div>
css
.block {
width: 100px;
height: 100px;
background: #dedede;
margin: 10px auto;
}
.rotate {
transform: rotate(90deg);
}
春字
html
<div class="spring">春</div>
css
.spring {
width: 100px;
height: 100px;
background-color: red;
color: #fff;
font-size: 20px;
font-weight: 800;
transform: rotate(135deg);
margin: 50px auto;
display: flex;
align-items: center;
justify-content: center;
}
此時文字因為旋轉的影響,沒有上下顛倒,而是斜了一邊
將 html
結構加工
<div class="spring">
<div class="text">春</div>
</div>
上下顛倒的角度為 180deg
,目前旋轉 135deg
,將剩下的 45deg
補給 .text
.spring .text {
transform: rotate(45deg);
}
特價條
html
<div class="product-discount">
<div class="product-image">
<div>Image</div>
<div class="discount-bar">off 20%</div>
</div>
<div class="content">
<h3 class="product-title">Product Name</h3>
<div class="product-description">
<p>Product description.</p>
<p>Product description.</p>
</div>
</div>
</div>
scss
*,
*::after,
*::before {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.product-discount {
margin: 10px auto;
height: 500px;
width: 300px;
background: #dedede;
padding: 10px;
.product-image {
width: 100%;
height: 200px;
background: rgb(255, 127, 95);
color: #fff;
display: flex;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
.discount-bar {
position: absolute;
top: 25px;
right: -120px;
background: #fff;
color: red;
transform: rotate(45deg);
width: 300px;
text-align: center;
font-size: 14px;
font-weight: 800;
}
}
.product-title {
margin: 10px 0;
font-size: 20px;
font-weight: 700;
}
.product-description {
line-height: 30px;
}
}
原點
變形參考點,類似 position
的參考原點概念,預設原點為 x 50%, y 50% (中心點)
宣告
transform-origin: x y;
x
除了使用數值外,也可以使用 left
, center
, right
y
除了使用數值外,也可以使用 top
, center
, bottom
位移
可將元素進行位置移動
使用 transform
屬性來定義 translate
宣告
transform: translate(x, y);
與 position offset 差異
translate
會盡量使用 GPU
加速,製作移動影格時,使用 translate
效能會比較好
如果網頁需要支援 IE8
瀏覽器,就只能使用 position offset
兩者可互相搭配使用
縮放
可將元素進行比例縮放
使用 transform
屬性來定義 scale
宣告
transform: scale(2);
超小字
大部分的瀏覽器都有最小字體的限制,scale
可以突破此限制
.super-mini {
font-size: 10px;
transform: scale(0.5);
}
特價條圖片放大
.product-discount{
&:hover {
.product-image > div:first-child {
transform: scale(2);
}
}
}
傾斜
可將元素進行角度傾斜
使用 transform
屬性來定義 skew
宣告
transform: skew(xdeg, ydeg);
轉場
兩點之間變化的影格補禎
限制
兩點必須是確切數值轉換
無效範例
.f1 {
width: auto;
&:hover {
width: 100px;
}
}
.f2 {
display: block;
&.hide {
didplay: none;
}
}
auto
並非確切的數值
display
顯示方式變化也無法補禎
補禎屬性
兩點變化可能包含許多屬性,此屬性可設定特定屬性進行補禎
宣告
transition-property: height; /* 單屬性 */
transition-property: height, width; /* 多屬性 */
transition-property: all; /* 預設 */
transition-property: none;
補禎時間
兩點變化補禎時間
宣告
transition-duration: 0.5s;
transition-duration: 500ms;
延遲執行
兩點變化延遲執行
宣告
transition-delay: 0.5s;
transition-delay: 500ms;
速度曲線
兩點變化補禎速度曲線
宣告
transition-timing-function: linear; /* 均速 */
transition-timing-function: ease; /* 緩入,中間快,緩出,預設值 */
transition-timing-function: ease-in; /* 緩入 */
transition-timing-function: ease-out; /* 緩出 */
transition-timing-function: ease-in-out; /* 緩入緩出 */
transition-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); /* 貝茲曲線 */
特價條圖片滑順放大
.product-discount{
&:hover {
.product-image > div:first-child {
transform: scale(2);
transition: all 1s;
}
}
}
動畫
搭配
@keyframes
使用
keyframes
宣告關鍵影格
html
<div class="block"></div>
scss
.block {
width: 100px;
height: 100px;
position: fixed;
top: 10px;
left: 10px;
background: #000;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
.run {
animation-name: move;
animation-duration: 3s;
}
}
@keyframes move {
from {
left: 10px;
}
to {
left: 300px;
}
}
from
等同 0%
to
等同 100%
多關鍵影格
除了使用 from
與 to
來表示
也可以使用百分比的方式來將細節更完善
scss
@keyframes move {
0% {
left: 10px;
}
20% {
top: 100px;
transform: rotate(180deg);
}
50% {
top: 150px;
transform: rotate(360deg) scale(2);
}
100% {
left: 300px;
}
}
動畫名稱
animation-name
使用 @keyframes
.run {
animation-name: move;
}
動畫時間
animation-duration
動畫執行時間
.run {
animation-duration: 3s;
animation-duration: 3000ms;
}
動畫延遲
animation-delay
動畫延遲時間
.run {
animation-delay: 3s;
animation-delay: 3000ms;
}
動畫速度曲線
animation-timing-function
.linear {
animation-timing-function: linear;
}
.ease {
animation-timing-function: ease;
}
.ease-in {
animation-timing-function: ease-in;
}
.ease-out {
animation-timing-function: ease-out;
}
.ease-in-out {
animation-timing-function: ease-in-out;
}
.cubic-bezier {
animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1);
}
.steps {
animation-timing-function: steps(3, start);
}
.step-start {
animation-timing-function: step-start;
}
.step-end {
animation-timing-function: step-end;
}
.initial {
animation-timing-function: initial;
}
.inherit {
animation-timing-function: inherit;
}
執行次數
animation-iteration-count
預設一次,infinite
表示不停止
播放方向
animation-direction
順向
0% -> 100%,預設值
.normal {
animation-direction: normal;
}
逆向
100% -> 0%
.reverse {
animation-direction: reverse;
}
輪播
奇數 0% -> 100%,偶數 100% -> 0%
參考播放次數 animation-iteration-count
.alternate {
animation-direction: alternate;
}
逆向輪播
奇數 100% -> 0%,偶數 0% -> 100%
參考播放次數 animation-iteration-count
.alternate-reverse {
animation-direction: alternate-reverse;
}
模式
動畫結束後模式
預設
動畫停留在當下樣式
.none {
animation-fill-mode: none;
}
最後影格
動畫停留在最後影格
.forwards {
animation-fill-mode: forwards;
}
開始影格
動畫停留在開始影格,搭配 animation-delay
才看得出效果
.backwards {
animation-fill-mode: backwards;
}
開始最後影格
結合 forwards
與 backwards
特性
.both {
animation-fill-mode: both;
}
綜合練習
藍天
body {
background: rgb(49, 141, 216);
}
建立雲朵
.cloud {
width: 500px;
height: 120px;
border-radius: 1000px;
background: #fff;
position: absolute;
top: 200px;
left: 0;
&::after {
content: '';
position: absolute;
width: 100px;
height: 100px;
border-radius: 100px;
background: #fff;
top: -50px;
left: 100px;
}
&::before {
content: '';
position: absolute;
width: 230px;
height: 200px;
border-radius: 300px;
background: #fff;
top: -100px;
left: 190px;
}
}
建立影格
@keyframes move {
from {
margin-left: 0;
}
to {
margin-left: 100%;
}
}
套用動畫
.cloud {
// ...
animation-name: move;
animation-duration: 10s;
animation-iteration-count: infinite;
animation-direction: alternate;
animation-timing-function: linear;
// ...
}
大小雲朵
.xl {
transform: scale(1.2);
}
.small {
transform: scale(0.5);
}
時間差異
animation-duration
套件
響應式網頁
解決跨裝置不同寬度顯示問題
RWD 與 AWD
RWD | AWD | |
---|---|---|
網址數量 | 1 | 2 |
維護數量 | 1 | 2 |
SEO | 優 | 重複內容與流量分散 |
載入速度 | 快 | 更快 |
開發成本 | 較低 | 高 |
維護成本 | 高 | 較低 |
相容性 | 高 | 較低 |
Fluid image
img
這個不太受控的元素,常常會導致實作 RWD
時破版
.fluid-image {
width: 100%;
height: auto;
}
由於在意的是寬度會不會破版,所以將 width
限制在 100%
高度設定 auto
是因為圖片有比例問題,如果高度也設定固定高度,會造成圖片比例失真
寬高其中一邊為固定,另外一邊必定為 auto
設定 width:100%
有個問題,如果圖片寬度不夠時,會被硬拉到滿版,此時也可能會造成圖片顯示失真,改為 max-width
解決此問題
.fluid-image {
max-width: 100%;
// width: 100%;
height: auto;
}
Media Query
依據不同條件套用對應 CSS
樣式
@media <not | only> <media type> and (<media feature> <and | or | not> <media feature>){
// CSS
}
media type
type | description |
---|---|
all | 全部 (預設) |
印表機 | |
braille | 點字機 |
screen | 視窗螢幕大小 |
handheld | 行動裝置 |
tv | 電視 |
projection | 投影機 |
常用的有 screen
跟 print
media feature
feature | description |
---|---|
device-width | 裝置寬度 |
device-height | 裝置高度 |
width | 視窗寬度 |
height | 視窗高度 |
max-device-width | 最大裝置寬度 |
max-device-height | 最大裝置高度 |
max-width | 最大寬度 |
max-height | 最大高度 |
min-device-width | 最小裝置寬度 |
min-device-height | 最小裝置高度 |
min-width | 最小寬度 |
min-height | 最小高度 |
orientation | 裝置方向,portrait(直向) landscape(橫向) |
常用的有 min-width
跟 max-width
min-width
大於等於時套用
max-width
小於等於時套用
一個網站
min-width
與max-width
不要同時使用,避免規則衝突
- 設定一個區塊,背景顏色黃色
- 小於等於
800px
時變藍色 - 小於等於
600px
時變紅色
Grid System
使用 class 讓頁面適應各種裝置寬度
格數 Column
每一列切割的數量,可自定義,常見為 12 格系統 (12 column grid system)
溝槽 Gutter
格與格之間的距離,可使用 margin
或 padding
實作
主流 CSS 框架都有實作 grid system,不用自己實作,知道原理即可
RWD實戰
切版實戰支援 RWD
前置作業
先將原本的 src/scss/index.scss
改為 desktop.scss
建立 src/scss/mobile.scss
@import 'desktop';
dist/index.html
引入 css/mobile.css
Step 1
增加漢堡盒樣式
dist/index.html
<div id="header">
<div class="container">
<div class="header-content">
<!-- ... -->
<div class="mobile-menu">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</div>
src/scss/mobile.scss
.mobile-menu {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
span {
display: inline-block;
width: 25px;
height: 4px;
margin-bottom: 4px;
background: #000;
&:last-child {
margin-bottom: 0;
}
}
}
Step 2
- 預設漢堡盒隱藏
- 當寬度小於
768px
時,隱藏選單,顯示漢堡盒
src/scss/mobile.scss
.mobile-menu {
// ...
// display: flex;
display: none
}
@media screen and (max-width: 768px) {
.mobile-menu {
display: flex;
}
.header-menu {
display: none;
}
}
Step 3
點擊漢堡盒時
- 變換樣式為 X
- 顏色為紅色
src/scss/mobile.scss
.mobile-menu {
// ...
span {
// ...
transition: all 0.3s
}
&:hover {
span:first-child {
transform: rotate(45deg);
margin-top: 4px;
position: absolute;
background: red;
}
span:nth-child(2) {
display: none;
}
span:last-child {
transform: rotate(-45deg);
background: red;
}
}
}
Step 4
目前使用 :hover
來觸發
但是手機版沒有 :hover
可以用,所以要改用相鄰選擇器來處理狀態
增加 checkbox
dist/index.html
<div id="header">
<div class="container">
<div class="header-content">
<input type="checkbox" id="mobile-switch" />
<!-- ... -->
</div>
</div>
</div>
src/scss/mobile.scss
#mobile-switch {
display: none;
}
選單改為 label
觸發 checkbox
dist/index.html
<div id="header">
<div class="container">
<div class="header-content">
<!-- ... -->
<label for="mobile-switch" class="mobile-menu">
<span></span>
<span></span>
<span></span>
</label>
</div>
</div>
</div>
將原本的 :hover
樣式搬移到相鄰選擇器
src/scss/mobile.scss
#mobile-switch {
// ...
&:checked ~ .mobile-menu {
span:first-child {
transform: rotate(45deg);
margin-top: 4px;
position: absolute;
background: red;
}
span:nth-child(2) {
display: none;
}
span:last-child {
transform: rotate(-45deg);
background: red;
}
}
}
Step 5
當 checkbox
被勾選時,顯示選單
src/scss/mobile.scss
#mobile-switch {
// ...
&:checked ~ .header-menu {
display: flex;
}
}
手機版選單位置
src/scss/mobile.scss
@media screen and (max-width: 768px) {
// ...
.header-menu {
// ...
position: absolute;
top: 70px;
left: 0;
width: 100%;
background: inherit;
}
}
發現寬度怪怪的
src/scss/mobile.scss
.header-content {
position: relative;
}
手機版選單樣式
src/scss/mobile.scss
@media screen and (max-width: 768px) {
// ...
.header-menu {
// ...
justify-content: center;
padding-top: 20px;
ul {
flex-direction: column;
li {
margin-bottom: 40px;
margin-right: 0;
}
}
}
}
選單歪一邊??
複寫 priority
問題
src/scss/mobile.scss
@media screen and (max-width: 768px) {
// ...
#header {
.header-menu {
// ...
}
}
}
Step 6
加入表單手機版處理
翻轉區塊
src/scss/mobile.scss
@media screen and (max-width: 768px) {
// ...
#join-form {
.join-content {
flex-direction: column;
}
}
}
翻轉之後兩個區塊沒有滿版
src/scss/mobile.scss
@media screen and (max-width: 768px) {
// ...
#join-form {
.join-content {
// ...
> div {
width: 100%;
}
}
}
}
左邊區塊去除右邊虛線與內距
src/scss/mobile.scss
@media screen and (max-width: 768px) {
// ...
#join-form {
.join-content {
// ...
.join-content-left {
padding: 0;
border-right: unset;
}
}
}
}
右邊區塊去除內距
src/scss/mobile.scss
@media screen and (max-width: 768px) {
// ...
#join-form {
.join-content {
// ...
.join-content-right {
padding: 0;
}
}
}
}
Step 7
timeline
手機版
將文字區塊統一放到右邊
src/scss/mobile.scss
@media screen and (max-width: 768px) {
// ...
.timeline {
.item:nth-child(even) {
.text {
right: unset;
left: calc(50% + 50px);
}
}
}
}
文字區塊擠在一起了
src/scss/mobile.scss
@media screen and (max-width: 768px) {
// ...
.timeline {
// ...
.item {
min-height: 130px;
}
}
}
將區塊靠左邊
src/scss/mobile.scss
@media screen and (max-width: 768px) {
// ...
.timeline {
// ...
.item {
// ...
justify-content: flex-start;
padding-left: 20px;
}
}
}
虛線對齊
src/scss/mobile.scss
@media screen and (max-width: 768px) {
// ...
.timeline {
// ...
&::after {
left: 30px;
}
}
}
文字區塊靠左
src/scss/mobile.scss
@media screen and (max-width: 768px) {
// ...
.timeline {
.item:nth-child(even) {
.text {
right: unset;
// left: calc(50% + 50px);
left: calc(20px + 50px);
}
}
.item {
// ...
.text {
right: unset;
left: calc(20px + 50px);
}
}
}
}
CSS 框架
優點
- 規範一致
- 快速開發
- 維護便利
Bootstrap
優點
- 完整度高
- 學習曲線低
- 可擴充 (SCSS)
- 官方文件齊全
- RWD 控制方便
- 透過
class
組合,簡單的需求甚至不需要自己寫css
- 不需要編譯器編譯 (相對於
Tailwind CSS
)
缺點
- 只能滿足 90% 需求
component
構成複雜(善用文件)- 眾多
!important
,覆寫彈性降低 - 大量
class
組合,主體辨識度較低 - 不支援 IE 11 以下瀏覽器
引用
Breakpoints
Breakpoint | Class infix | Dimensions |
---|---|---|
Extra small | None | < 576px |
Small | sm | >= 576px |
Medium | md | >= 768px |
Large | lg | >= 992px |
Extra large | xl | >= 1200px |
Extra extra large | xxl | >= 1400px |
Containers
Extra small | Small | Medium | Large | X-Large | XX-Large | |
---|---|---|---|---|---|---|
.container | 100% | 540px | 720px | 960px | 1140px | 1320px |
.container-sm | 100% | 540px | 720px | 960px | 1140px | 1320px |
.container-md | 100% | 100% | 720px | 960px | 1140px | 1320px |
.container-lg | 100% | 100% | 100% | 960px | 1140px | 1320px |
.container-xl | 100% | 100% | 100% | 100% | 1140px | 1320px |
.container-xxl | 100% | 100% | 100% | 100% | 100% | 1320px |
.container-fluid | 100% | 100% | 100% | 100% | 100% | 100% |
Grid
xs | sm | md | lg | xl | xxl | |
---|---|---|---|---|---|---|
Container | 100% | 540px | 720px | 960px | 1140px | 1320px |
Class prefix | .col- | .col-sm- | .col-md- | .col-lg- | .col-xl- | .col-xxl- |
# of columns | 12 | 12 | 12 | 12 | 12 | 12 |
Gutter width | 1.5rem | 1.5rem | 1.5rem | 1.5rem | 1.5rem | 1.5rem |
Images
https://getbootstrap.com/docs/5.2/content/images/
Tables
https://getbootstrap.com/docs/5.2/content/tables/
Alerts
https://getbootstrap.com/docs/5.2/components/alerts/
Buttons
https://getbootstrap.com/docs/5.2/components/buttons/
Forms
https://getbootstrap.com/docs/5.2/forms/form-control/
Accordion
https://getbootstrap.com/docs/5.2/components/accordion/
Spinners
https://getbootstrap.com/docs/5.2/components/spinners/
挑戰
無 CSS
登入畫面
<div class="d-flex justify-content-center align-items-center vh-100">
<div class="p-5 bg-dark text-white">
<h2 class="text-center ps-5 pe-5">System Login</h2>
<hr />
<div class="mb-3">
<label for="login-account" class="form-label">帳號</label>
<input type="text" class="form-control" id="login-account" />
</div>
<div class="mb-3">
<label for="login-password" class="form-label">密碼</label>
<input type="password" class="form-control" id="login-password" />
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="login-remember" />
<label for="login-remember" class="form-check-label">記住密碼</label>
</div>
<div class="text-end">
<button class="btn btn-sm btn-danger">登入</button>
</div>
</div>
</div>
程式概論
語言類型
靜態 | 動態 | |
---|---|---|
編譯 | 需要 | 不需要 |
型態 | 強調(強型態) | 不強調(弱型態) |
效能 | 較好 | 較差 |
代表語言 | C, C++, Java | JavaScript, PHP, Python, Ruby |
型態
型態類型 | 關鍵字 | 位元數 | 範圍 |
---|---|---|---|
整數 | byte | 8 | -128 ~ 127 |
整數 | short | 16 | -32768 ~ 32767 |
整數 | int | 32 | -2147483648 ~ 2147483647 |
整數 | long | 64 | -9223372036854775808 ~ 9223372036854775807 |
浮點數 | float | 32 | 依據 IEEE 754 標準 |
浮點數 | double | 64 | 依據 IEEE 754 標準 |
布林值 | boolean | 1 | true, flase |
字元 | char | 16 | '\u0000' - '\uffff' |
攸關記憶體使用量
強型態
int hp;
hp = 100;
hp = 200;
hp = "一百"; // error
弱型態
let hp;
hp = 100;
hp = 200;
hp = "一百"; // success
事件驅動 Event-driven
傳統流程
flowchart LR A[客戶] B[店家] C[餐點] D[確認] E[客戶 N] E-->| 排隊 | A A-->| 1.點餐 | B B-->| 2.製作 | C C-->| 3.完成 | D D-->| 4.提供 | A
- 客戶需要在原地等待餐點製作完成才可以離開
- 店家一次也只能服務一個客戶
優點
缺點
- 無法有效消化大量人流
事件驅動
flowchart LR A[客戶] B[店家] C[餐點] D[確認] E[客戶 N] F[廚師 N] G[自由行動] E-->| 排隊 | A A-->| 1.點餐 | B B-->| 2.給呼叫器| A A-->| 3.離開 | G B-->| 3.派單 | F B-->| 3N.服務 | E E-->| 3N-1.點餐 | B B-->| 3N-2.給呼叫器| E F-->| 4.製作| C C-->| 5.完成 | D D-->| 6.呼叫 | A A-->| 7.取餐 | B
優點
- 可快速消化人潮
- 透過增加廚師理論上可以增加餐點提供數量
缺點
- 餐點可能做錯
- 餐點可能給錯
- 收單後發現庫存不足
名詞補充
原子性 Atomic
- 操作內所有的動作、變更都順利完成
- 操作失敗,內部的動作、變更全部不存在
有序性 Ordering
FIFO
(First In First Out)Queue
可見性 Visibility
- 當有異動時,其他人也會得知異動後的值
基本使用
腳本宣告
html 內
使用 <script></script>
元素宣告
<script>
let age = 18;
</script>
獨立檔案
副檔名為 .js
使用 <script src="jsfile"></script>
引入
js/index.js
let age = 18;
index.html
<script src="js/index.js"></script>
終端機輸出
使用 console.log()
js/index.js
let age = 18;
console.log(age);
變數宣告
使用 let
進行宣告
let age = 18;
let age = 18;
宣告一個變數 age
內容為 18
變數名稱只能是英文跟
_
開頭,不可用數字開頭以及特殊符號
變數型態
雖然 JavaScript
是弱型態語言,但不代表沒有型態
字串
使用 '
或是 "
將內容包起來代表字串
let firstName = 'David';
let lastName = "Lin";
let fullName = firstName + ' ' + lastName;
使用
+
運算子執行字串串接
數字
整數或是浮點數(小數點)
let n1 = 1;
let n2 = 1.25;
let n3 = n1 + n2;
console.log(n3);
使用運算子來做處理
當數字遇到字串
let n1 = 1;
let s1 = '10';
let ns = n1 + s1;
console.log(ns);
只要運算過程遇到一個字串型態,全部會被轉為字串處理
顯示型態
typeof
let s1 = '10';
console.log(typeof s1);
型態轉換
字串轉為數字
parseInt(string, radix)
radix
進位系統,使用 10 進制轉換就輸入 10
let n1 = 1;
let s1 = '10';
let ns = parseInt(s1, 10) + n1;
console.log(typeof ns);
偷雞法
let n1 = 1;
let s1 = '10';
let ns = +s1 + n1;
console.log(typeof ns);
變數前面加上
+
轉型為數字(不要有空格)
數字轉文字
使用 .toString()
進行轉型
let n1 = 1;
console.log(typeof n1.toString());
布林 boolean
真(true)跟假(false)
let b1 = true;
let b2 = false;
let b3 = 1;
let b4 = 0;
let b5 = 's';
let b6 = '';
let b7 = -1;
let b8 = 2;
陣列 array
使用 []
宣告
let a1 = []; // 宣告空陣列
let a2 = [1, 2, 3, 4]; // 宣告並給予初始值
console.log(a2[0]);
索引從
0
開始
增加
a2.push('aaa');
轉字串
a2.join(':');
:
分隔符號,可自訂
尋找
let index = a2.indexOf(2);
回傳符合內容的索引值,找不到時回傳 -1
刪除
.splice(index, delete_count)
a2.splice(0, 1);
從索引 0
刪除 1
個
物件 object
使用 {}
宣告
let student = {
name: 'David',
age: 18,
};
console.log(student);
student.name = 'John';
student.age = 20;
console.log(student);
函數
將事情給予名稱包裝
- 好理解
- 可重複使用
- 彈性高 (可傳參數)
宣告
function add(num1, num2) {
return num1 + num2;
}
function
宣告關鍵字
add
函數名稱
num1
第一個參數
num2
第二個參數
return num1 + num2
回傳數值
呼叫
函數名稱加上 ()
進行呼叫
let calc = add(1, 10);
console.log(calc);
函數與變數
函數也是變數的一種
function add(num1, num2) {
return num1 + num2;
}
console.log(add(1, 10));
add = 100;
console.log(add(1, 10));
add 將被覆寫為 100,呼叫失敗
關於參數
呼叫函數時,傳遞的參數會自動做變數宣告
flowchart LR A[add 1, 10] B[num1 = 1 num2 = 10] C[return num1 + num2] A-->| 初始化參數 | B B-->| 執行回傳 | C
DOM 操作
Document Object Model
HTML
上面的任何元素都是 DOM
抓取
單一
document.querySelector(selector)
單一抓取時,無論符合的 selector
有幾個,都只會回傳第一個
html
<div id="uid">UID</div>
<div class="student">Student 1</div>
<div class="student">Student 2</div>
js
let uid = document.querySelector('#uid');
let students = document.querySelector('.student');
console.log(uid);
console.log(students);
多選
document.querySelectorAll(selector)
單一抓取時,無論符合的 selector
有幾個,都會回傳陣列型態
html
<div id="uid">UID</div>
<div class="student">Student 1</div>
<div class="student">Student 2</div>
js
let uid = document.querySelectorAll('#uid');
let students = document.querySelectorAll('.student');
console.log(uid);
console.log(students);
控制
內容
dom.innerHTML
html
<div id="uid">UID</div>
js
let uid = document.querySelector('#uid');
console.log(uid.innerHTML);
uid.innerHTML = 'New UID';
數值
dom.value
html
<input type="text" value="David Lin" id="full-name">
js
let fullName = document.querySelector('#full-name');
console.log(fullName.value);
fullName.value = 'John Chen';
CSS
dom.style
遇到屬性有 -
連接的,使用小駝峰命名
background-color
=> backgroundColor
html
<div id="uid">UID</div>
js
let uid = document.querySelector('#uid');
uid.style.color = 'red';
uid.style.backgroundColor = 'yellow';
class
dom.classList
附加
dom.classList.add('class name')
重複附加也只會顯示一個
html
<div id="uid">UID</div>
js
let uid = document.querySelector('#uid');
uid.classList.add('some');
uid.classList.add('some');
uid.classList.add('some');
移除
dom.classList.remove('class name')
移除不存在的 class name
不會發生錯誤
html
<div id="uid">UID</div>
js
let uid = document.querySelector('#uid');
uid.classList.remove('some');
uid.classList.remove('some');
uid.classList.remove('some');
取得
dom.className
回傳字串,使用 String.split(' ')
切割成陣列
html
<div id="uid" class="first name full">UID</div>
js
let uid = document.querySelector('#uid');
let className = uid.className;
console.log(className);
let classes = className.split(' ');
console.log(classes);
確認名稱存在
dom.classList.contains('class name')
html
<div id="uid" class="first name full">UID</div>
js
let uid = document.querySelector('#uid');
let exists = uid.classList.contains('first');
console.log(exists);
dataset
dom.dataset
對應 data-*
屬性
html
<div id="uid" class="first name full" data-id="1">UID</div>
js
let uid = document.querySelector('#uid');
console.log(uid.dataset.id);
uid.dataset.id = 999;
事件監聽
dom.addEventListener(event, function)
event
事件類型
function
執行函數
html
<button id="btn">Click</button>
js
let btn = document.querySelector('#btn');
btn.addEventListener('click', function(e) {
console.log(e);
})
click
點擊事件
e
事件物件,紀錄事件的詳細資料
互動初體驗
- 建立一顆按鈕
- 建立一姓名輸入匡
- 建立一個顯示區塊
- 當點擊按鈕時,顯示區塊顯示『Hi!, {姓名輸入匡內容}』
程式基本功
計算運算子
加法 +
let num1 = 10;
let num2 = 20;
let num3 = num1 + num2;
練習
- 建立兩個數字輸入匡 (A)(B)
- 建立一顆計算按鈕 (C)
- 建立一顯示區塊 (D)
- 當按下按鈕時,抓取兩數字輸入匡進行加法計算
- 將結果顯示在顯示區塊
flowchart LR A[A] B[B] C[C] D[D] E[+] C-->| 抓取 | A C-->| 抓取 | B A-->E B-->E E-->| 顯示 | D
減法 -
let num1 = 10;
let num2 = 20;
let num3 = num1 - num2;
練習
- 建立兩個數字輸入匡 (A)(B)
- 建立一顆計算按鈕 (C)
- 建立一顯示區塊 (D)
- 當按下按鈕時,抓取兩數字輸入匡進行減法計算
- 將結果顯示在顯示區塊
flowchart LR A[A] B[B] C[C] D[D] E[-] C-->| 抓取 | A C-->| 抓取 | B A-->E B-->E E-->| 顯示 | D
乘法 *
let num1 = 10;
let num2 = 20;
let num3 = num1 * num2;
練習
- 建立兩個數字輸入匡 (A)(B)
- 建立一顆計算按鈕 (C)
- 建立一顯示區塊 (D)
- 當按下按鈕時,抓取兩數字輸入匡進行乘法計算
- 將結果顯示在顯示區塊
flowchart LR A[A] B[B] C[C] D[D] E[*] C-->| 抓取 | A C-->| 抓取 | B A-->E B-->E E-->| 顯示 | D
除法 /
let num1 = 10;
let num2 = 20;
let num3 = num1 / num2;
練習
- 建立兩個數字輸入匡 (A)(B)
- 建立一顆計算按鈕 (C)
- 建立一顯示區塊 (D)
- 當按下按鈕時,抓取兩數字輸入匡進行除法計算
- 將結果顯示在顯示區塊
flowchart LR A[A] B[B] C[C] D[D] E[ / ] C-->| 抓取 | A C-->| 抓取 | B A-->E B-->E E-->| 顯示 | D
餘數 %
let num1 = 10;
let num2 = 20;
let num3 = num1 % num2;
練習
- 建立兩個數字輸入匡 (A)(B)
- 建立一顆計算按鈕 (C)
- 建立一顯示區塊 (D)
- 當按下按鈕時,抓取兩數字輸入匡進行餘數計算
- 將結果顯示在顯示區塊
flowchart LR A[A] B[B] C[C] D[D] E[%] C-->| 抓取 | A C-->| 抓取 | B A-->E B-->E E-->| 顯示 | D
執行後遞增
let num1 = 1;
num++;
console.log(num1);
遞增後執行
let num1 = 1;
++num;
console.log(num1);
執行後遞減
let num1 = 1;
num--;
console.log(num1);
遞減後執行
let num1 = 1;
--num;
console.log(num1);
關於遞增遞減
let num1 = 1;
let num2 = num1++ + 1;
let num3 = ++num1 + 1;
num1
, num2
, num3
分別為多少?
判斷式
if
if (condition) {
console.log('is true');
}
condition
必然是一個 boolean
if...else...
if (condition) {
console.log('is true');
} else {
console.log('is false');
}
if...else if...else...
if (condition) {
console.log('is true');
} else if (condition2){
console.log('condition2 is false');
} else {
console.log('is false');
}
iif
let response = true ? 'is true' : 'is false';
三元判斷,適合用於單一動作時,同等於
if (true) {
let response = 'is true';
} else {
let response = 'is false';
}
switch
switch (some) {
case 'match 1':
console.log('is match 1');
break;
case 'match 2':
console.log('is match 2');
break;
default:
console.log('not match');
break;
}
some
任意值
case match 1
some
等於 match 1
時
break
強制結束
判斷運算子
最終得到一個
boolean
等於
let num1 = 1;
let num2 = 10;
let condition = num1 == num2;
完全等於
let num1 = 10;
let num2 = '10';
let condition = num1 == num2;
let condition2 = num1 === num2;
連同型態一起比對
不等於
let num1 = 1;
let num2 = 10;
let condition = num1 != num2;
大於
let num1 = 10;
let num2 = 10;
let condition = num1 > num2;
大於等於
let num1 = 10;
let num2 = 10;
let condition = num1 >= num2;
小於
let num1 = 10;
let num2 = 10;
let condition = num1 < num2;
小於等於
let num1 = 10;
let num2 = 10;
let condition = num1 <= num2;
及閘 AND
let b1 = true;
let b2 = false;
let condition = b1 && $b2;
同時成立為真
b1 | b2 | condition |
---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
或閘 OR
let b1 = true;
let b2 = false;
let condition = b1 || $b2;
其一成立為真
b1 | b2 | condition |
---|---|---|
true | true | true |
true | false | true |
false | true | true |
false | false | false |
反閘 NOT
let b1 = true;
let condition = !b1;
真假反轉
b1 | condition |
---|---|
true | false |
false | true |
迴圈
for
for (start; condition; next) {}
start
起始值
condition
滿足條件
next
每次執行完後執行動作
for (let i = 0; i < 10; i++) {
console.log(i);
}
用於已知次數的重複執行
練習
乘法表
- 建立兩個數字輸入匡 (A)(B)
- 建立一顆按鈕 (C)
- 建立一張表格 (D)
- 按下按鈕後,抓取兩數字輸入匡內容
- 將兩數字使用
for
迴圈建立乘法表呈現在表格
flowchart LR A[A] B[B] C[C] D[D] E[ for 迴圈] C-->| 抓取 | A C-->| 抓取 | B A-->E B-->E E-->| 顯示 | D
foreach
for (let key in object) {}
key
每次執行時的 object
key
object
要執行迴圈的物件
let student = {
name: 'David',
age: 18
};
for (let field in student) {
let value = student[field];
console.log(field, value);
}
用於未知次數的重複執行
陣列內建
let scope = [100, 40, 20, 10, 0];
scope.forEach(function(value, index) {
console.log(index, value);
});
while
while (condition) {}
condition
滿足條件,為 boolean
型態
持續執行,直到條件不滿足
while (true) {
console.log('run');
}
注意無限迴圈
let num = 0;
while (num < 10) {
console.log(num);
num++;
}
等同於
for (let num = 0; num < 10; num++) {
console.log(num);
}
do...while
do {} while (condition)
condition
滿足條件,為 boolean
型態
先執行一次,條件滿足時,持續執行
let num = 0;
do {
console.log(num);
num++;
} while (num < 10)
注意無限迴圈
生命週期
變數有效範圍
情境一
let num1 = 10;
function run() {
let num2 = 20;
num1 = 30;
console.log(num1, num2);
}
run();
console.log(num1);
console.log(num2);
num2
在run
執行完畢後消失
情境二
let num1 = 10;
function run(num1) {
let num2 = 20;
num1 = 30;
console.log(num1, num2);
}
run(num1);
console.log(num1);
console.log(num2);
num1
參數與外部的num1
不同
情境三
let num1 = 10;
function run() {
num2 = 20;
num1 = 30;
console.log(num1, num2);
}
run();
console.log(num1);
console.log(num2);
沒有使用
let
關鍵字宣告,預設為全域變數(Global
)
Call by value
let num1 = 1;
let num2 = num;
num1++;
console.log(num1, num2);
num1
將內容複製一份給num2
,本質上是兩個不同的東西
Call by reference
let student = {
name: 'David',
};
let student2 = student;
student.name = 'John';
console.log(student.name, student2.name);
student2.name = 'Dan';
console.log(student.name, student2.name);
student
將記憶體位置複製一份給student2
,所以兩個變數參考到同一份內容
具備此特性的類型
- Object
- Array
簡易型計算機實作
事件使用
監聽
dom.addEventListener(event, function)
event
事件類型
function
執行函數
let dom = document.querySelector('#dom');
dom.addEventListener('click', function(e) {
console.log(e);
});
e
事件氣泡,事件觸發時,系統自動傳遞
事件類型
click
點擊觸發
let btn = document.querySelector('#btn');
btn.addEventListener('click', function(e) {
console.log(e);
});
change
焦點移出時且數值變化時觸發,用於輸入類型元素(有 value
屬性)
let name = document.querySelector('#name');
name.addEventListener('change', function(e) {
console.log(name.value);
});
blur
焦點移出時觸發(focus
-> unfocus
)
let name = document.querySelector('#name');
name.addEventListener('blur', function(e) {
console.log(name.value);
});
與
chagne
類似,差別在於無論數值有沒有變化都會觸發
keypress
鍵盤按下時觸發
let name = document.querySelector('#name');
name.addEventListener('keypress', function(e) {
console.log(name.value);
});
keyup
鍵盤放開時觸發
let name = document.querySelector('#name');
name.addEventListener('keyup', function(e) {
console.log(name.value);
});
補充
keypress
與 keyup
產生的 e
事件物件會附帶觸發的按鍵
使用 e.key
可以取得觸發的按鍵
let name = document.querySelector('#name');
name.addEventListener('keyup', function(e) {
console.log(e.key);
});
練習
分數等級轉換
- 建立一個數字輸入匡
- 建立一個計算按鈕
- 當按下計算按鈕時,抓取數字輸入匡進行判斷
- 分數等級如下
>= 90 甲
>= 80 乙
>= 70 丙
>= 60 丁
< 60 不及格
- 數字輸入匡按下
enter
時,也可以進行計算
事件氣泡
事件監聽很好用,綁定都是針對單一元素處理,如果需要大量綁定,該如何處理?
html
<ul id="menu">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
點擊每一個 li
都印出當下 li
內容
js
let lis = document.querySelectorAll('#menu li')
lis.forEach(function (li) {
li.addEventListener('click', function (e) {
console.log(li.innerHTML)
})
})
問題
- 如果
li
有一萬個,會發生什麼事? - 如果透過事件去增加
li
,被新增的li
被點擊時是否也會印出自己內容?
html
<ul id="menu">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<button id="add-btn">add li</button>
js
let lis = document.querySelectorAll('#menu li')
lis.forEach(function (li) {
li.addEventListener('click', function (e) {
console.log(li.innerHTML)
})
})
let addBtn = document.querySelector('#add-btn')
addBtn.addEventListener('click', function (e) {
let menu = document.querySelector('#menu')
let li = document.createElement('li')
li.innerHTML = 'new li'
menu.appendChild(li)
})
觸發流程
先抓取,再冒泡

事件氣泡
了解事件觸發流程後,針對氣泡特性,改善先前 n
個 li
的事件綁定問題
let menu = document.querySelector('#menu')
menu.addEventListener('click', function (e) {
let li = e.target
console.log(li.innerHTML)
})
e.target
事件氣泡觸發元素
有沒有機會觸發到
ul
?
let menu = document.querySelector('#menu')
menu.addEventListener('click', function (e) {
let li = e.target
if (e.tagName == 'LI') {
console.log(li.innerHTML)
}
})
e.tagName
觸發元素標籤,統一為大寫
中斷冒泡
事件氣泡無論節點是否有處理,都會往上冒泡。如果不想氣泡繼續往上,使用 e.stopPropagation()
中斷冒泡
中斷預設行為
特定元素無需事件監聽就會進行預設行為,例如 a 元素會進行網頁跳轉。如想要禁止這種預設行為執行,使用 e.preventDefault()
中斷預設行為
html
<a href="https://www.google.com" id="link">Google</a>
let link = document.querySelector('#link')
link.addEventListener('click', function (e) {
e.preventDefault()
console.log(e.target.innerHTML)
})
單次計時器
在特定秒數後,執行特定函數,只執行一次
宣告
setTimeout(function, ms)
setTimeout(function() {
console.log('Run after 1000ms')
}, 1000)
1s = 1000ms
取消
clearTimeout(timer)
timer
為 setTimeout
回傳的計時器編號
let timer = setTimeout(function() {
console.log('Run after 1000ms')
}, 3000)
clearTimeout(timer)
重複計時器
在特定秒數後,執行特定函數,重複執行直到取消為止
宣告
setInterval(function, ms)
setInterval(function() {
console.log('Run after 1000ms')
}, 1000)
1s = 1000ms
取消
clearInterval(timer)
timer
為 setTimeout
回傳的計時器編號
let timer = setInterval(function() {
console.log('Run after 1000ms')
}, 3000)
clearInterval(timer)
練習
電子鐘

- 建立 UI
- 每秒更新該 UI 時間
時間取得方式
let d = new Date();
let hh = d.getHours();
let mm = d.getMinutes();
let ss = d.getSeconds();
綜合練習
TODO List

- 建立文字輸入匡,與一顆新增按鈕
- 建立列表區塊,每個項目前面加入 checkbox,如打勾則開項目顯示刪除線樣式。(
text-decoration: line-through;
) - 按下新增按鈕後,將目前文字輸入框內容新增至下方列表
永久性儲存
前端網頁重新整理後就會恢復到初始狀態,所以中間的數據異動無法被保存與還原
這時需要倚賴本地端儲存方法進行數據保存與還原
本地端儲存方式
特性 | cookie | localStorage | sessionStorage |
---|---|---|---|
生命週期 | 可設定失效時間,預設為關閉瀏覽器失效 | 永久保存,手動清除 | 關閉頁面後清除 |
可用容量 | 4KB 左右 | 5MB 左右 | 5MB 左右 |
與 Server 溝通 | 每次溝通都會附加,保存過多數據會帶來效能問題 | 不參與溝通 | 不參與溝通 |
瀏覽器與主機溝通架構
flowchart LR A[cookie id] B[service side seesion] C[session content] A-->| Send | B B-->| Read | C C-->| Response | A
為何網站登入後,關閉瀏覽器再打開,一樣在登入狀態?
查看資料
Chrome Devtool
-> Application
-> Storage
localStorage
寫入
localStorage.setItem(key, content)
key
數據識別值,如果寫入一樣的識別值,複寫
content
數據內容,只接受字串
localStorage.setItem('name', 'david');
let students = [];
students.push({name: 'David', age: 18});
students.push({name: 'John', age: 20});
localStorage.setItem('students', students);
查看
students
數據內容為何?
序列化
由於 localStorage
只接受字串類型的數據內容,所以 Array
或是 Object
這種非字串類型的數據,需要轉為字串,這個過程稱為序列化 (serialize
)
JSON.stringify(object)
let students = [];
students.push({name: 'David', age: 18});
students.push({name: 'John', age: 20});
let studentsStr = JSON.stringify(students);
localStorage.setItem('students', studentsStr);
此時觀察數據內容發現已經改為字串方式存取
關於 JSON
JavaScript Object Notation
一種資料交換格式,對比 XML
資料交換格式而言,來的輕巧與方便存取
讀取
localStorage.getItem(key)
let students = localStorage.getItem('students');
console.log(students);
查看
students
數據內容為何?
反序列化
將被序列化的 Array
或 Object
返回原始類型數據
JSON.parse(serialize)
let studentsStr = localStorage.getItem('students');
students = JSON.parse(studentsStr);
console.log(students);
刪除
localStorage.removeItem(key)
localStorage.removeItem('students');
練習
本地儲存版 Todo List
將上一個章節練習的 Todo List 擴充以下功能
- 進入時要求使用者輸入帳號作為儲存
key
值 - 新增儲存功能
- 新增還原功能
- 自動儲存功能
API & AJAX
API
Application Programming Interface
目的
- 降低耦合性 (單一原則)
- 跨系統串接 (不同作業系統,不同程式語言)
- 簡化使用方式 (封裝)
URL
組成
https://www.google.com/?s=hello
https
通訊協定
www.google.com
網址
?s=hello
參數
請求方式
- GET
- POST
- PUT (有定義未實作)
- DELETE (有定義未實作)
對應 CRUD
類型 | C | R | U | D |
---|---|---|---|---|
全名 | Create | Read | Update | Delete |
HTTP | POST/PUT | GET | POST/PUT | DELETE |
SQL | INSERT | SELECT | UPDATE | DELETE |
Web API
每次都請求都是一個 API 的發起與完成
flowchart LR A[Browser] B[Google] C[Backend] A-->| Request Method With URL | B B-->| Run Process | C C-->| Return | B B-->| Response | A
將 Browser
這個視覺化的角色拿掉,發起 Request
就是大眾所謂的 API
通常
Response
會使用JSON
格式回傳
初體驗
觀察兩個網址回應差別
https://book.niceinfos.com/frontend/api/
https://book.niceinfos.com/frontend/api/?action=demo
Ajax
Asnychronous JavaScript And XML
同步與非同步
flowchart LR A[Browser] B[Google] C[Backend] A-->| Request Method With URL | B B-->| Run Process | C C-->| Return after 10s | B B-->| Response | A
在等待的時候,還可以點畫面其他的互動嗎?
非同步請求
html
<button id="btn">AJAX</button>
<div id="response"></div>
js
let btn = document.querySelector('#btn')
let response = document.querySelector('#response')
btn.addEventListener('click', doAjax)
function doAjax() {
let request = new XMLHttpRequest()
request.addEventListener('load', () => {
response.innerHTML = request.responseText
})
request.open('GET', 'https://book.niceinfos.com/frontend/api/?action=demo')
request.send()
}
透過此方法,就算請求需要時間,也不會影響主畫面的操作 (背景作業)
模擬主機端睡眠
- 使用上面的範例,將
url
改為https://book.niceinfos.com/frontend/api/?action=sleep
- 觀察
開發者工具 > 網路
fetch
原本的 XMLHttpRequest
使用上不夠直覺,ES6 之後推出包裝過後的 fetch
方便使用
js
function doAjax() {
let api = 'https://book.niceinfos.com/frontend/api/?action=demo';
fetch(api)
.then((response) => {
return response.text()
// return response.json()
})
.then((data) => {
console.log(data)
})
}
response.text()
將回傳資料解析為純文字
response.json()
將回傳資料解析為 json
POST 發送
js
function doAjax() {
let api = 'https://book.niceinfos.com/frontend/api/'
let params = {
action: 'demo',
data: { a: 1, b: 2 },
}
let options = {
method: 'POST',
body: JSON.stringify(params),
}
fetch(api, options)
.then((response) => {
return response.text()
// return response.json()
})
.then((data) => {
response.innerHTML = data
console.log(data)
})
}
params
傳送資料
options
設定選項
- method
請求方式,預設 GET
- body
請求主體,GET
不可以使用此屬性,透過序列化將資料打為 json
格式發送
關於 then
fetch
本身的包裝是希望讓開發者透過類同步的開發方式寫程式,所以使用 then
來等待非同步
底層使用的是 Promise
物件來處理
檔案上傳
html
<input type="file" id="file" />
<button id="btn">Upload</button>
<div id="response"></div>
js
let btn = document.querySelector('#btn')
let response = document.querySelector('#response')
btn.addEventListener('click', doAjax)
function doAjax() {
let domFile = document.querySelector('#file')
let file = domFile.files[0]
if (!file) {
return
}
let api = 'https://book.niceinfos.com/frontend/api/'
let form = new FormData()
form.append('action', 'upload')
form.append('file', file)
let options = {
method: 'POST',
body: form,
}
fetch(api, options)
.then((response) => {
return response.text()
// return response.json()
})
.then((data) => {
response.innerHTML = data
console.log(data)
})
}
使用 FormData
物件傳送檔案,因為檔案是二進制格式,不可序列化為文字
練習
雲端儲存版 Todo List
將先前開發好的本地儲存版 Todo List 近一步改良
- 新增雲端儲存功能
- 新增雲端載入功能
tip
- 儲存功能對應請求方式為
POST
,請求API
為https://book.niceinfos.com/frontend/api/
- 載入功能對應請求方式為
GET
請求API
為https://book.niceinfos.com/frontend/api/?action=todo&uid=your_uid
jQuery
引用
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.3/dist/jquery.min.js"></script>
介紹
使用 $
代表 jQuery
,提供簡單方便的使用方法
$
也是一個變數,在某些複雜的專案下,可能被其他功能先使用,導致$
已非jQuery
抓取 DOM
html
<input type="text" id="account">
<div class="item">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
<div class="item">Item 4</div>
<div class="item">Item 5</div>
js
let account = $('#account');
let items = $('.item');
console.log(account.val());
console.log(items.html());
html()
等同 innerHTML
val()
等同 value
無論是一個或是多個,都是統一使用
$(selector)
來抓取建立jQuery
物件並自動整理成Array
DOM each
將抓取到的 dom
做迴圈(iterate
)一個一個讀取
items.each(function(index, item) {})
與 Array
的 forEach(function(item, index){})
不同,參數剛好是顛倒的
js
items.each((index, item) => {
console.log(item, index);
console.log($(item).html());
})
item
原生 dom
$(item)
將原生 dom
封裝為 jQuery
物件
取得特定 DOM
items.eq(0).html()
取得第一個 jQuery
物件
取得原生 DOM
items[0]
取得第一個原生 dom
事件綁定
items.on('click', function(e) {})
on
等同 addEventListener
自動將
items
內的jQuery
物件都進行綁定
class 控制
附加
account.addClass('active')
移除
account.removeClass('active')
切換
account.toggleClass('active')
檢查存在
account.hasClass('active')
屬性控制
html
<input type="checkbox" id="agree">
js
let agree = $('#agree')
agree.prop('checked', true)
console.log(agree.prop('checked'))
agree.prop('checked', false)
沒有第二個參數表示 getter
取值
套件應用
WOWJS
結合 animate.css
依據捲軸位置呈現動畫效果
swiperjs
部署需求條件
網域 Domain
非買斷機制,以『年』為單位進行續約
一個網域可以設定 N
個網址(子網域)
註冊商
網域名稱服務 DNS
設定網址對應 IP
位置
服務商
- 網域註冊商本身提供
- cloudflare
雲端主機
提供網站服務與儲存空間
技術門檻 | 彈性 | 費用 | |
---|---|---|---|
自建主機 | 最高 | 最高 | 高 |
虛擬空間 hosting | 最低 | 最低 | 最低 |
虛擬主機 VPS | 高 | 中 | 中 |
實體主機託管 | 高 | 高 | 高 |
虛擬空間
虛擬主機
服務架構

專案開發

部署工具
FTP
- filezilla
- vscode 套件
SFTP
網頁檔案總管
一般租用 hosting
,都會提供類似的工具給你上傳檔案
指令
- git
- 其他指令工具
Firebase Hosting
提供有限免費的雲端空間
安裝工具
- nodejs
- firebase-tools
npm install -g firebase-tools
部署 firebase hosting
- 建立專案(不要打中文跟特殊符號)
- 啟用 hosting
- 登入
firebase
- 終端機輸入
firebase login
- 網頁進行驗證
- 終端機輸入
- 進入專案資料夾
- 終端機輸入
cd 專案資料夾位置
- 終端機輸入
- 初始化專案
- 終端機輸入
firebase init
- 選擇
Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
Use an existing project
- 選擇專案
- 終端機輸入
- 部署
- 終端機輸入
firebase deploy
- 終端機輸入
部署的檔案會放在
public
資料夾內
部署的檔案無法下載,所以要留好原始檔案,建議搭配 git
管理
網址對應
- 新增自訂網域
- 輸入網址
- 前往 DNS 建立對應
TXT
與A
紀錄 - 等待生效
git
人生不能重來,git 可以!
安裝
設定使用者
git config --global user.name "My Name"
設定信箱
git config --global user.email "myemail@example.com"
初始化
git init
提交第一個版本
先建立專案結構,參考 建議專案結構
建立完畢後,執行
git add .
將目前檔案加入 stage change
等待提交
執行提交
git commit -m "my first commit."
查看紀錄
git log
查看紀錄明細
git log --stat
圖形化顯示
git log --stat --pretty=short --graph
查看目前變化
git status
放棄目前變化
單一檔案
git restore <file>
全部檔案
git restore .
目前變化放入等待提交 stage change
單一檔案
git add <file>
全部檔案
git add .
提交
git commit -m "add reset style."
跳到特定版本
保留檔案跳版本,未納入版本的檔案會變成未提交狀態
git reset <commit hash>
不保留檔案跳版本,未納入版本的檔案會直接消失
連同提交紀錄也會消失,但是如果知道
commit hash
還是可以恢復
git reset --hard <commit hash>
提交線上資源庫
- 註冊
- 建立資源庫 (
Respositories
)
增加線上資源庫位置
git branch -M main
git remote add origin https://github.com/<account>/<repositories>.git
提交
git push -u origin main
過程會要求登入
建立 readme.md
說明專案使用,使用 markdown
格式
readme.md
# Frontend Project
This is my first frontend project.
git add .
git commit -m "add readme."
git push -u origin main
複製資源庫 clone
從現有資源庫複製一份出來,如無指定資料夾名稱,會自動產生 repositories
名稱的資料夾
不要自己建立資料夾!
git clone https://github.com/<account>/<repositories>.git <custom dir>
同步資源庫 pull
多人開發時,可能資源庫與你現在的有差異,這時候可以將線上資源庫同步下來
git pull origin main
Firebase Realtime
啟用
Realtime Database
-> 建立資料庫
規則
Realtime Database
-> 規則
預設都是 false
,先改為 true
方便測試
實際上線需依據狀況設定為
false
,避免資料安全性問題
建立應用程式
- 專案總覽 -> 專案設定 -> 網頁應用程式
</>
符號 - 輸入程式暱稱(英文數字) -> 不要
firebase 託管
- 複製
const firebaeConfig
設定值
初始化
引用 SDK
<script src="https://cdnjs.cloudflare.com/ajax/libs/firebase/8.10.0/firebase.js"
integrity="sha512-7NBxPO/qRUrKc+Wi9GbYz0YEzMpi2UMP3mtLcswnvzI0vUFP5Jb7HNVd1V8NmEhXpSe3ZLsoGEhwRHpAZDpYPQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
建立 firebase.js
-> 將剛剛複製的 firebaseConfig
貼上
const firebaseConfig = {};
const model = firebase.initializeApp(firebaseConfig, firebaseConfig.appId);
async function write(value, path) {
try {
await model.database().ref(path).set(value)
return true
} catch (err) {
return false
}
}
async function read(path) {
let snapshot = await model.database().ref(path).get()
return snapshot.val()
}
function listen(path, callback) {
model
.database()
.ref(path)
.on('value', (snapshot) => {
if (typeof callback === 'function') {
callback(snapshot.val())
}
})
}
;(async () => {
let result = await write('BBB', 'test')
console.log(result)
let response = await read('test')
console.log(response)
listen('test', (value) => {
console.log(value)
})
})()