前端程式碼準則

開發出靈活、穩定、可持續發展的 HTML 與 CSS 標準。

目錄

HTML

CSS

最佳實踐

不二法門

不管是下面的準則或您自己的準則,只要用同樣的準則即可。如有任何不對之處,請不吝指教。若想新增或貢獻內容,請至 GitHub 來提 issue

不管有多少個貢獻者,每行程式碼都應該像是同一人所寫。

HTML

語法

<!DOCTYPE html>
<html lang="zh-Hant-TW">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Page title - 建議控制在 65 個字元以內, Google 搜尋結果平均可以顯示 35 個字 (中文以 2 個字元計算)</title>
    </head>
    <body>
        <img src="images/company-logo.png" alt="Company">
        <h1 class="hello-world">Hello, world!</h1>
    </body>
</html>

HTML5 doctype

每個 HTML 頁面開頭使用這個簡單的 doctype,來啟用標準模式。每個瀏覽器將會有更加一致的 render 結果。

<!DOCTYPE html>
<html>
    <head>
    </head>
</html>

語言屬性

引用自 HTML5 規格:

鼓勵作者指定根 HTML 元素的 lang 屬性,給予文件使用的語言。這有助於語音合成工具判定要使用的發音以及翻譯工具判斷使用的規則等等。

深入閱讀規格裡的 lang 屬性.

zh-Hant-TW 係根據目前的規範依照「RFC 5646 Tags for Identifying Languages

<html lang="zh-Hant-TW">
  <!-- ... -->
</html>

IE 相容性模式

IE 支援使用 <meta> 標籤來指定 IE 是否應該呈現指定的版本。除非另有其他考量,否則它是最實用指定 IE 瀏覽器使用最新模式 edge mode 的方法

深入閱讀請查閱 stackoverflow 的討論 What's the difference if <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> exists or not?.

<meta http-equiv="x-ua-compatible" content="ie=edge">

字元編碼

明確宣告字元編碼來快速簡單地確保內容 render 正確無誤。

<head>
    <meta charset="UTF-8">
</head>

引用 CSS 與 JavaScript

根據 HTML5 規範,引用 JavaScript 與 CSS 檔案時,無需指定 CSS 與 JavaScript 的 type。因為預設值便是 text/csstext/javascript

HTML5 規範出處

<!-- External CSS -->
<link rel="stylesheet" href="code-guide.css">

<!-- In-document CSS -->
<style>
    /* ... */
</style>

<!-- JavaScript -->
<script src="code-guide.js"></script>

實用性勝過純粹性

在不犧牲實用性的前提下,盡力維持 HTML 的標準與語義。盡量使用簡潔、簡單的 Markup。

屬性順序

HTML 屬性應按照特定順序撰寫,確保程式碼的易讀性。

Class 是為了重用的元素而生,應該排第一位。ID 具體得多,應盡量少用(可用場景像是頁內書籤),所以排第二位。

<a class="..." id="..." data-modal="toggle" href="#">
    Example link
</a>

<input class="form-control" type="text">

<img src="..." alt="...">

Boolean attributes

布林屬性是不需要寫值的屬性。XHTML 要求你宣告數值,但 HTML5 不需要。

深入閱讀請查閱 WhatWG 關於布林屬性一節

元素有布林屬性存在即代表 true value,反之不存在則代表 false value。

如果一定要附上屬性的數值的話,則無需遵守 WhatWG 的這條規則:

如果有寫屬性的話,其數值必須是空字串或是屬性的標準名稱(需符合 ASCII 不分大小寫的規則),且前後不可有空白。

長話短說,不用寫數值。

<input type="text" disabled>

<input type="checkbox" value="1" checked>

<select>
    <option value="1" selected>1</option>
</select>

簡化 Markup

撰寫 HTML 時,盡量避免多餘的父元素。這需要反覆重寫與重構,才能寫出更少的 HTML。看看右邊這個例子:

<!-- Not so great -->
<span class="avatar">
    <img src="...">
</span>

<!-- Better -->
<img class="avatar" src="...">

JavaScript 產生的 Markup

Markup 寫在 JavaScript 裡,不僅難找、也更難編輯,性能更是差。盡量避免在 JavaScript 裡撰寫 Markup。

基本版面區塊

.navbar
.header
    .logo
.workspace
    .content
        .content__header
        .content__body
    .sidebar
.footer

常用狀態的 Class

CSS

語法

對於這裡使用的術語有任何問題嗎?請參考維基百科上關於層疊樣式表條目裡的語法小節

/* Bad CSS */
.selector, .selector-secondary, .selector[type=text] {
    padding:15px;
    margin:0px 0px 15px;
    background-color:rgba(0, 0, 0, 0.5);
    box-shadow:0 1px 2px #CCC,inset 0 1px 0 #FFFFFF
}

/* Good CSS */
.selector,
.selector-secondary,
.selector[type="text"] {
    padding: 15px;
    margin: 0 0 15px;
    background-color: rgba(0, 0, 0, .5);
    box-shadow: 0 1px 2px #ccc, inset 0 1px 0 #fff;
}

宣告順序

相關的屬性宣告應以下列順序分組:

  1. Content
  2. Positioning
  3. 顯示 & Box Model
  4. Typography
  5. Visual
  6. Misc
  7. Animation

Content: 內容本身最重要,需定義在最前面較清楚(例如:偽元素內容)。

Positioning: 可以從正常文件順序裡移除元素(out of flow)的位置宣告,或是覆寫牽扯到 Box Model 的樣式。

顯示 & Box Model: 決定元件的位置與尺寸 或 與 Box Model 相關。

其它只在元件內部起作用的屬性放後面,因為這不會影響前面。

Typography: 文字樣式呈現。

Visual: 其他視覺樣式呈現。

Animation: 動畫因為較不影響排版,所以放最後。

宣告原則

  1. 重要性或影響性高的屬性放前面,例如:Positioning 區塊 放 Box Model 區塊前面。
  2. 相依性的屬性要排序,例如:position 放 top 前面。
  3. 相關性高的放一起,例如:flex 相關屬性。

關於屬性順序的完整列表,請參考 Recessidiomatic-css #declaration-order

.declaration-order {
    /* 元素內容 Content */
    content: ".";

    /* 位置 Positioning */
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 2;
    float: right;

    /* 顯示 & Box Model */
    display: block;
    flex-direction: column;
    flex-grow: 1;
    justify-content: space-between;
    align-items: center;
    overflow: hidden;
    box-sizing: border-box;
    width: 100px;
    height: 100px;
    border: 1px solid #e5e5e5;
    margin: 10px;
    padding: 10px;
    vertical-align: middle;

    /* 文字樣式 Typography */
    font: normal 13px "Helvetica Neue", sans-serif;
    font-weight: 600;
    font-size: 12px;
    line-height: 1.5;
    letter-spacing: 2px;
    color: #333;
    text-align: center;
    text-overflow: ellipsis;

    /* 可視化 Visual */
    background-color: #f5f5f5;
    background-image: url(/img/icon-flash.svg);
    border-radius: 3px;
    border-collapse: collapse;
    box-shadow:  0 1px 3px 0 #222;

    /* 其他雜項 Misc */
    opacity: 1;
    visibility: visible;
    white-space: nowrap;

    /* 動畫效果 Animation */
    animation-name: hiddenTip;
    animation-duration: .5s;
    animation-delay: 3s;
    animation-fill-mode: forwards;
    transform: translateY(10px);
    transition: opacity .3s;
}

Media Query 擺放位置

將 Media Query 與其最相關的規則放在一起。別把他們放在 CSS 檔案最後面,或是獨立成另外的樣式表。這樣只會讓之後接手的人錯過他們。右邊是個經典範例。

.element { ... }
.element-avatar { ... }
.element-selected { ... }

@media (min-width: 480px) {
    .element { ...}
    .element-avatar { ... }
    .element-selected { ... }
}

具前綴的屬性

使用帶有各家廠商前綴的屬性時,縮排每個屬性、垂直對齊,以便多行編輯。

/* Prefixed properties */
.selector {
    -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15);
            box-shadow: 0 1px 2px rgba(0,0,0,.15);
}

單行宣告

當一組規則只包含單條宣告的的情況裡,考慮移除換行;寫成單行的可讀性更高、更容易編輯。任何包含多條宣告的一組規則應該要分為多行。

這麼分的關鍵因素是錯誤偵測─比如:CSS Validator 表示 183 行有語法錯誤。如果是單行宣告便就是那行,多條宣告沒分行的話則會找到抓狂。

/* Single declarations on one line */
.span1 { width: 60px; }
.span2 { width: 140px; }
.span3 { width: 220px; }

/* Multiple declarations, one per line */
.sprite {
    display: inline-block;
    width: 16px;
    height: 15px;
    background-image: url(../img/sprite.png);
}
.icon           { background-position: 0 0; }
.icon-home      { background-position: 0 -20px; }
.icon-account   { background-position: 0 -40px; }

簡寫記法

宣告盡量少用簡寫,最好明確的將所有的值寫出來。過度濫用簡寫的特性包含:

通常我們只需設定需要的值,簡寫會設定到多餘的值。舉例來說,HTML 標題只會設定 top 與 bottom margin,所以只要更改這兩個值即可。濫用特性縮寫只會寫出更差的程式碼,也會有無謂的覆寫與不預期的副作用。

Mozilla Developer Network 有篇很好的專文,給不熟悉記法與行為的開發者:特性簡寫

/* Bad example */
.element {
    margin: 0 0 10px;
    background: red;
    background: url("image.jpg");
    border-radius: 3px 3px 0 0;
}

/* Good example */
.element {
    margin-bottom: 10px;
    background-color: red;
    background-image: url("image.jpg");
    border-top-left-radius: 3px;
    border-top-right-radius: 3px;
}

CSS preprocessor (SASS, SCSS ...) 裡的巢狀

避免無謂的巢狀。可用巢狀不表示都要用巢狀。
只在多元素需要巢狀,或是需要在父元素下增加樣式的場景下使用巢狀。

// Without nesting
.table > thead > tr > th {  }
.table > thead > tr > td {  }

// With nesting
.table > thead > tr {
    > th {  }
    > td {  }
}

註解

程式碼是由人來撰寫與維護的。
確保程式碼精準描述、有良好的註解,讓別人看起來是很親切。
好的註解傳遞意圖、意境。
註解不要只是重複元件或是 Class 的名稱。

長註解記得使用完整的句子,一般的筆記用簡潔的用語。

/* Bad example */
/* Modal header */
.modal-header {
    ...
}

/* Good example */
/* Wrapping element for .modal-title and .modal-close */
.modal-header {
    ...
}

Class 名稱

/* Bad example */
.t { ... }
.red { ... }
.header { ... }

/* Good example */
.tweet { ... }
.important { ... }
.tweet-header { ... }

選擇器

延伸閱讀:

/* Bad example */
span { ... }
.page-container #stream .stream-item .tweet .tweet-header .username { ... }
.avatar { ... }

/* Good example */
.avatar { ... }
.tweet-header .username { ... }
.tweet .avatar { ... }

組織

/*
 * Component section heading
 */

.element { ... }


/*
 * Component section heading
 *
 * Sometimes you need to include optional context for the entire component. Do that up here if it's important enough.
 */

.element { ... }

/* Contextual sub-component or modifer */
.element-heading { ... }

z-index 管理

羅列目前較有 z-index 問題如下:

  1. @首頁: globalbar
  2. @前台: topbar
  3. @前台: AD, MIB.freeze
  4. @後台: dialog

基本 z-index = order 數字 * 1000,
有異動需更新文件

font-family 中文字型

使用 webpack v1.10.1 uglify 或是 css-loader v0.28.7 minimize, 中文字元後會多一個 \(比如: \5FAE\8EDF\6B63\9ED1\9AD4 變成 \\5FAE\8EDF\6B63\9ED1\9AD4

因此只使用英文字體名稱,並且在字型名稱中有空格時加上雙引號,避免 bundle 的結果 encode 錯誤。

.selector {
    /* 可能造成 encode 錯誤 */
    font-family: "微軟正黑體", Helvetica, Verdana, sans-serif;
    font-family: \5FAE\8EDF\6B63\9ED1\9AD4, Helvetica, Verdana, sans-serif;
    /* 建議的寫法 */
    font-family: "Microsoft JhengHei", Helvetica, Verdana, sans-serif;
}

@keyframes 管理

若專案上有使用到cssnano,在使用css Animation的@keyframes時cssnano會自動將@keyframes的名稱重新命名,並依照字母順序排列(如右側範例)。

為了避免不同專案的@keyframes命名衝突,請於:

/* reduceIdents */
/* before */
@keyframes fadeOut {
    from {
        opacity: 1;
    }
    to {
        opacity: 0;
    }
}
.box {
    animation-name: fadeOut;
}

/* after */
@keyframes a {
    from {
        opacity: 1;
    }
    to {
        opacity: 0;
    }
}
.box {
    animation-name: a;
}

最佳實踐

HTML

推薦閱讀 Essential Image Optimization 深入瞭解圖片的最佳化技巧

Images

推薦閱讀 Essential Image Optimization 深入瞭解圖片的最佳化技巧

JavaScript

V8 6.0 引擎的新架構 TurboFan 對 ES2015 的語法有更好的效能 資料來源

// 聲明式 (Declarative), V8 引擎處理的效能較佳
if (obj !== undefined) {
    return obj.x;
}

/*
 ... ...........
 23 cmpq [r13-0x60],rax
 27 jz 72
 ... ...........
*/


// 晦澀難解 (Obscure),V8 引擎轉譯較多的 bytes code,效能不彰
if (obj) {
    return obj.x;
}

/*
... ...........
 27 cmpq [r13-0x40],rax
 31 jz 128
 37 test al,0x1
 39 setzl bl
 42 movzxbl rbx,rbx
 45 cmpl rbx,0x0
 48 jnz 185
 54 cmpq [r13-0x38],rax
 58 jz 128
 64 movq rdx,[rax-0x1]
 68 testb [rdx+0xc],0x10
 72 jnz 128
 78 cmpq [r13+0x50],rdx
 82 jz 160
 160 vmovsd xmm0,[rax+0x7]
 165 movq [rbp-0x18],rbx
 169 vxorpd xmm1,xmm1,xmm1
 173 vucomisd xmm1,xmm0
 177 jz 128
 179 movq rbx,[rbp-0x18]
 183 jmp 88
 185 movq [rbp-0x18],rbx
 189 cmpq rax,0x0
 193 jz 128
 195 movq rbx,[rbp-0x18]
 199 jmp 88
... ...........
*/

// -----------------------------------------
// 聲明式 (Declarative)
function foo6 (f, ...args) {
  return f(...args);
}


// 晦澀難解 (Obscure)
function foo5 (f) {
  switch (arguments.length) {
    case 1: return f();
    case 2: return f(arguments[1]);
    case 3: return f(arguments[1], arguments[2]);
    default: {
      var args = [];
      for (var i = 1; i < arguments.length; ++i) {
        args[i - 1] = arguments[i];
      }
      return f.apply(undefined, args);
    }
  }
}

編輯喜好

使用下列設定來設定編輯器,避免掉常見的程式碼不一致和醜陋的 diffs:

考慮將以上偏好應用到專案的 .editorconfig 檔案並撰寫文件說明。舉個例子,參考 Bootstrap 的 .editorconfig。瞭解更多內容,請參考 editorconfig.org