比較與比較;比較字幕

lang 屬性只能與一種語言建立關聯。這表示 <html> 屬性只能有一種語言,即使網頁上有多種語言也一樣。將 lang 設為頁面的主要語言。

錯誤做法
<html lang="ar,en,fr,pt">...</html>
不支援多種語言。
正確做法
<html lang="ar">...</html>
請僅設定網頁的主要語言。在本例中,語言為阿拉伯文。

與按鈕類似,連結主要會從文字內容中取得易於存取的名稱。建立連結時,最好的做法就是將最有意義的文字加入連結本身,不要像「在這裡」或「閱讀完整」這類充斥字詞。

敘述不夠豐富
Check out our guide to web performance <a href="/guide">here</a>.
實用內容!
Check out <a href="/guide">our guide to web performance</a>.

檢查動畫是否觸發版面配置

如果動畫使用 transform 以外的內容移動元素,則速度可能較慢。在以下範例中,我達到相同的視覺化結果為 topleft 動畫,並使用 transform

錯誤做法
.box {
  position: absolute;
  top: 10px;
  left: 10px;
  animation: move 3s ease infinite;
}

@keyframes move {
  50% {
     top: calc(90vh - 160px);
     left: calc(90vw - 200px);
  }
}
正確做法
.box {
  position: absolute;
  top: 10px;
  left: 10px;
  animation: move 3s ease infinite;
}

@keyframes move {
  50% {
     transform: translate(calc(90vw - 200px), calc(90vh - 160px));
  }
}

您可以透過下列兩個 Glitch 範例進行測試,並使用開發人員工具探索效能。

透過相同的標記,我們可以將 padding-top: 56.25% 替換為 aspect-ratio: 16 / 9,將 aspect-ratio 設為指定比例 width / height

使用 padding-top
.container {
  width: 100%;
  padding-top: 56.25%;
}
使用長寬比
.container {
  width: 100%;
  aspect-ratio: 16 / 9;
}

使用 aspect-ratio 取代 padding-top 會比較清楚,而且不會過度調整邊框間距屬性,以便執行正常範圍以外的操作。

沒錯,我正在使用 reduce 鏈結一連串的承諾。我聰明。但這看起來聰明的程式碼編寫方式相當完善。

然而,在將上述資料轉換為非同步函式時,系統會嘗試「依序」執行以下動作:

不建議 - 語氣太慢
async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
看起來很後面,但我的第二個擷取作業要到我的第一個擷取作業完全讀取完畢之後,才會開始執行,依此類推。這比同時執行擷取的承諾範例慢很多。幸好有理想的中間地。
建議:兼具友善與並合性
function markHandled(...promises) {
  Promise.allSettled(promises);
}

async function logInOrder(urls) {
  // fetch all the URLs in parallel
  const textPromises = urls.map(async (url) => {
    const response = await fetch(url);
    return response.text();
  });

  markHandled(...textPromises);

  // log them in sequence
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}
在這個範例中,系統會並行擷取及讀取網址,但「智慧型」reduce 位元會替換為標準無邊框且可讀取的迴圈。

寫入 Houdini 自訂屬性

以下範例說明如何設定自訂屬性 (例如 CSS 變數),但現在加入了語法 (類型)、初始值 (備用),以及繼承布林值 (是否繼承父項的值?)。目前方法是在 JavaScript 中使用 CSS.registerProperty(),不過在 Chromium 85 以上版本中,CSS 檔案將支援 @property 語法:

獨立的 JavaScript 檔案 (Chromium 78)
CSS.registerProperty({
  name: '--colorPrimary',
  syntax: '',
  initialValue: 'magenta',
  inherits: false
});
CSS 檔案包含 (Chromium 85)
@property --colorPrimary {
  syntax: '';
  initial-value: magenta;
  inherits: false;
}

您現在可以透過 var(--colorPrimary) 存取 --colorPrimary,就像其他 CSS 自訂屬性一樣。不過,差別在於 --colorPrimary 不是以字串的形式讀取。它具有資料!

CSS backdrop-filter 會將一或多項效果套用至半透明或透明的元素。請查看下圖。

沒有前景透明度
疊加在圓圈上的三角形。無法透過三角形顯示圓形。
.frosty-glass-pane {
  backdrop-filter: blur(2px);
}
前景透明度
疊加在圓圈上的三角形。三角形是半透明的,讓圓圈可以透過它看到。
.frosty-glass-pane {
  opacity: .9;
  backdrop-filter: blur(2px);
}

左圖顯示未使用或支援 backdrop-filter 時,重疊元素的顯示方式。右側的圖片使用 backdrop-filter 套用模糊效果。請注意,除了 backdrop-filter 之外,它使用了 opacity。如果沒有 opacity,就無法套用模糊效果。當 opacity 設為 1 (完全不透明) 時,背景不會造成任何影響。

但與 unload 事件不同,beforeunload 確實有正當用途。舉例來說,當您想要警告使用者尚未儲存的變更,一旦他們離開頁面,這些變更就會遺失。在這種情況下,建議您只在使用者有未儲存的變更時新增 beforeunload 事件監聽器,然後在儲存未儲存的變更後立即移除事件監聽器。

錯誤做法
window.addEventListener('beforeunload', (event) => {
  if (pageHasUnsavedChanges()) {
    event.preventDefault();
    return event.returnValue = 'Are you sure you want to exit?';
  }
});
上述程式碼會無條件新增 beforeunload 事件監聽器。
正確做法
function beforeUnloadListener(event) {
  event.preventDefault();
  return event.returnValue = 'Are you sure you want to exit?';
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  window.addEventListener('beforeunload', beforeUnloadListener);
});

// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
  window.removeEventListener('beforeunload', beforeUnloadListener);
});
上述程式碼只會在必要時新增 beforeunload 事件監聽器 (如無需要,則會移除)。

盡量減少使用 Cache-Control: no-store

Cache-Control: no-store 是 HTTP 標頭網路伺服器,可指示瀏覽器不要將回應儲存在任何 HTTP 快取中。這個類型只能用於含有使用者機密資訊的資源,例如需要登入的網頁。

包含每個輸入群組 (.fieldset-item) 的 fieldset 元素使用 gap: 1px 建立元素之間的髮線邊框。沒有棘手的邊界解決方案!

實心差距
.grid {
  display: grid;
  gap: 1px;
  background: var(--bg-surface-1);

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
邊框技巧
.grid {
  display: grid;

  & > .fieldset-item {
    background: var(--bg-surface-2);

    &:not(:last-child) {
      border-bottom: 1px solid var(--bg-surface-1);
    }
  }
}

自然格線包裝

最複雜的版面配置最後是巨集版面配置,也就是 <main><form> 之間的邏輯版面配置系統。

輸入
<input
  type="checkbox"
  id="text-notifications"
  name="text-notifications"
>
標籤
<label for="text-notifications">
  <h3>Text Messages</h3>
  <small>Get notified about all text messages sent to your device</small>
</label>

包含每個輸入群組 (.fieldset-item) 的 fieldset 元素使用 gap: 1px 建立元素之間的髮線邊框。沒有棘手的邊界解決方案!

實心差距
.grid {
  display: grid;
  gap: 1px;
  background: var(--bg-surface-1);

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
邊框技巧
.grid {
  display: grid;

  & > .fieldset-item {
    background: var(--bg-surface-2);

    &:not(:last-child) {
      border-bottom: 1px solid var(--bg-surface-1);
    }
  }
}

分頁 <header> 版面配置

下一個版面配置與以下幾乎相同:我使用 Flex 建立垂直排序。

HTML
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
CSS
header {
  display: flex;
  flex-direction: column;
}

.snap-indicator 應隨連結群組水平移動,而這個標頭版面配置有助於設定該階段。這裡沒有絕對位置元素!

Gentle Flex 是「僅聚焦」核心的策略。由於 place-content: centerplace-content: center 不同,置中時不會變更子項方塊大小,因此相當柔和且輕柔。盡可能平穩地對項目進行堆疊、置中和間距

.gentle-flex {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1ch;
}
優點
  • 只處理對齊、方向和分佈情形
  • 從同一個頁面集中管理編輯和維護作業
  • 差距可保證 n 個子項之間的間距等於
缺點
  • 大部分程式碼

適合微距和微型版面配置。

使用方法

gap 可接受任何 CSS 長度百分比做為值。

.gap-example {
  display: grid;
  gap: 10px;
  gap: 2ch;
  gap: 5%;
  gap: 1em;
  gap: 3vmax;
}


能夠傳遞 1 段長度,這將會用於列和欄。

簡寫
.grid {
  display: grid;
  gap: 10px;
}
一次設定「全部」列和欄
已展開
.grid {
  display: grid;
  row-gap: 10px;
  column-gap: 10px;
}


有 2 個長度限制可傳遞 2 個長度,用於列和欄。

簡寫
.grid {
  display: grid;
  gap: 10px 5%;
}
一次設定個別列和欄
已展開
.grid {
  display: grid;
  row-gap: 10px;
  column-gap: 5%;
}