So sánh & So sánh phụ đề

Thuộc tính lang chỉ có thể liên kết với một ngôn ngữ. Điều này có nghĩa là thuộc tính <html> chỉ có thể có một ngôn ngữ, ngay cả khi có nhiều ngôn ngữ trên trang. Đặt lang thành ngôn ngữ chính của trang.

Không nên
<html lang="ar,en,fr,pt">...</html>
Không hỗ trợ nhiều ngôn ngữ.
Nên
<html lang="ar">...</html>
Chỉ đặt ngôn ngữ chính của trang. Trong trường hợp này, ngôn ngữ là tiếng Ả Rập.

Tương tự như các nút, đường liên kết chủ yếu lấy tên hỗ trợ tiếp cận từ nội dung văn bản. Một mẹo hay khi tạo đường liên kết là đặt đoạn văn bản có ý nghĩa nhất vào chính đường liên kết đó, thay vì các từ đệm như "Tại đây" hoặc "Đọc thêm".

Không đủ thông tin mô tả
Check out our guide to web performance <a href="/guide">here</a>.
Nội dung hữu ích!
Check out <a href="/guide">our guide to web performance</a>.

Kiểm tra xem ảnh động có kích hoạt bố cục hay không

Ảnh động di chuyển một phần tử bằng cách sử dụng một thành phần khác ngoài transform có thể sẽ bị chậm. Trong ví dụ sau, tôi đã đạt được kết quả hình ảnh tương tự khi tạo ảnh động topleft và sử dụng transform.

Không nên
.box {
  position: absolute;
  top: 10px;
  left: 10px;
  animation: move 3s ease infinite;
}

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

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

Bạn có thể kiểm thử điều này trong hai ví dụ sau về Glitch và khám phá hiệu suất bằng DevTools.

Với cùng một mã đánh dấu, chúng ta có thể thay thế: padding-top: 56.25% bằng aspect-ratio: 16 / 9, đặt aspect-ratio thành tỷ lệ width / height đã chỉ định.

Sử dụng padding-top
.container {
  width: 100%;
  padding-top: 56.25%;
}
Sử dụng aspect-ratio
.container {
  width: 100%;
  aspect-ratio: 16 / 9;
}

Việc sử dụng aspect-ratio thay vì padding-top rõ ràng hơn nhiều và không làm thay đổi thuộc tính khoảng đệm để thực hiện một việc gì đó nằm ngoài phạm vi thông thường.

Vâng, đúng vậy, tôi đang sử dụng reduce để tạo chuỗi các lời hứa. Tôi rất thông minh. Tuy nhiên, đây là một cách lập trình quá thông minh mà bạn nên tránh.

Tuy nhiên, khi chuyển đổi mã trên thành một hàm không đồng bộ, bạn có thể sẽ quá tuần tự:

Không nên dùng – quá tuần tự
async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
Nhìn gọn gàng hơn nhiều, nhưng lần tìm nạp thứ hai sẽ không bắt đầu cho đến khi lần tìm nạp đầu tiên đã được đọc hoàn toàn, v.v. Cách này chậm hơn nhiều so với ví dụ về lời hứa thực hiện các lệnh tìm nạp song song. Rất may, có một giải pháp trung gian lý tưởng.
Nên dùng – đẹp và song song
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);
  }
}
Trong ví dụ này, các URL được tìm nạp và đọc song song, nhưng bit reduce "thông minh" được thay thế bằng một vòng lặp for-loop tiêu chuẩn, nhàm chán và dễ đọc.

Viết thuộc tính tuỳ chỉnh Houdini

Sau đây là ví dụ về cách thiết lập thuộc tính tuỳ chỉnh (ví dụ: biến CSS), nhưng giờ đây, thuộc tính này có cú pháp (loại), giá trị ban đầu (phương thức dự phòng) và boolean kế thừa (có kế thừa giá trị từ thuộc tính mẹ hay không?). Cách hiện tại để thực hiện việc này là thông qua CSS.registerProperty() trong JavaScript, nhưng trong Chromium 85 trở lên, cú pháp @property sẽ được hỗ trợ trong các tệp CSS:

Tệp JavaScript riêng biệt (Chromium 78)
CSS.registerProperty({
  name: '--colorPrimary',
  syntax: '',
  initialValue: 'magenta',
  inherits: false
});
Có trong tệp CSS (Chromium 85)
@property --colorPrimary {
  syntax: '';
  initial-value: magenta;
  inherits: false;
}

Giờ đây, bạn có thể truy cập vào --colorPrimary như mọi thuộc tính tuỳ chỉnh CSS khác thông qua var(--colorPrimary). Tuy nhiên, điểm khác biệt ở đây là --colorPrimary không chỉ được đọc dưới dạng chuỗi. Nó có dữ liệu!

CSS backdrop-filter áp dụng một hoặc nhiều hiệu ứng cho một phần tử mờ hoặc trong suốt. Để hiểu điều đó, hãy xem xét các hình ảnh bên dưới.

Không có độ trong suốt trên nền trước
Một tam giác chồng lên một hình tròn. Không thể nhìn thấy vòng tròn qua tam giác.
.frosty-glass-pane {
  backdrop-filter: blur(2px);
}
Độ trong suốt của nền trước
Một tam giác chồng lên một hình tròn. Hình tam giác có độ mờ, cho phép nhìn thấy hình tròn qua đó.
.frosty-glass-pane {
  opacity: .9;
  backdrop-filter: blur(2px);
}

Hình ảnh bên trái cho thấy cách các phần tử chồng chéo sẽ được kết xuất nếu không sử dụng hoặc không hỗ trợ backdrop-filter. Hình ảnh ở bên phải áp dụng hiệu ứng làm mờ bằng backdrop-filter. Lưu ý rằng lớp này sử dụng opacity ngoài backdrop-filter. Nếu không có opacity, bạn sẽ không thể áp dụng hiệu ứng làm mờ. Hầu như không cần phải nói rằng nếu bạn đặt opacity thành 1 (độ mờ hoàn toàn), thì nền sẽ không bị ảnh hưởng.

Tuy nhiên, không giống như sự kiện unload, có những cách sử dụng hợp pháp cho beforeunload. Ví dụ: khi bạn muốn cảnh báo người dùng rằng họ có các thay đổi chưa lưu và sẽ mất nếu rời khỏi trang. Trong trường hợp này, bạn chỉ nên thêm trình nghe beforeunload khi người dùng có các thay đổi chưa lưu, sau đó xoá các trình nghe đó ngay sau khi lưu các thay đổi chưa lưu.

Không nên
window.addEventListener('beforeunload', (event) => {
  if (pageHasUnsavedChanges()) {
    event.preventDefault();
    return event.returnValue = 'Are you sure you want to exit?';
  }
});
Mã ở trên thêm trình nghe beforeunload một cách vô điều kiện.
Nên
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);
});
Mã ở trên chỉ thêm trình nghe beforeunload khi cần (và xoá trình nghe đó khi không cần).

Hạn chế tối đa việc sử dụng Cache-Control: no-store

Cache-Control: no-store là một tiêu đề HTTP mà máy chủ web có thể đặt trên các phản hồi hướng dẫn trình duyệt không lưu trữ phản hồi trong bất kỳ bộ nhớ đệm HTTP nào. Bạn nên sử dụng phương thức này cho các tài nguyên chứa thông tin nhạy cảm của người dùng, chẳng hạn như các trang sau khi đăng nhập.

Phần tử fieldset chứa từng nhóm đầu vào (.fieldset-item) đang sử dụng gap: 1px để tạo đường viền mảnh giữa các phần tử. Không có giải pháp đường viền phức tạp!

Lấp đầy khoảng trống
.grid {
  display: grid;
  gap: 1px;
  background: var(--bg-surface-1);

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
Thủ thuật đường viền
.grid {
  display: grid;

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

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

Tự xuống dòng theo lưới

Bố cục phức tạp nhất cuối cùng là bố cục vĩ mô, hệ thống bố cục logic giữa <main><form>.

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

Phần tử fieldset chứa từng nhóm đầu vào (.fieldset-item) đang sử dụng gap: 1px để tạo đường viền mảnh giữa các phần tử. Không có giải pháp đường viền phức tạp!

Lấp đầy khoảng trống
.grid {
  display: grid;
  gap: 1px;
  background: var(--bg-surface-1);

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
Thủ thuật đường viền
.grid {
  display: grid;

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

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

Bố cục <header> của thẻ

Bố cục tiếp theo gần giống như vậy: Tôi sử dụng flex để tạo thứ tự dọc.

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 sẽ di chuyển theo chiều ngang với nhóm đường liên kết và bố cục tiêu đề này sẽ giúp thiết lập giai đoạn đó. Không có phần tử có vị trí tuyệt đối nào ở đây!

Gentle Flex là chiến lược chỉ căn giữa đúng nghĩa hơn. Cách này mềm mại và nhẹ nhàng, vì không giống như place-content: center, không có kích thước hộp nào của phần tử con cháu thay đổi trong quá trình căn giữa. Tất cả các mục được xếp chồng, căn giữa và giãn cách một cách nhẹ nhàng nhất có thể.

.gentle-flex {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1ch;
}
Ưu điểm
  • Chỉ xử lý việc căn chỉnh, hướng và phân phối
  • Tất cả nội dung chỉnh sửa và bảo trì đều ở cùng một nơi
  • Gap đảm bảo khoảng cách bằng nhau giữa n phần tử con
Nhược điểm
  • Hầu hết các dòng mã

Phù hợp với cả bố cục vĩ mô và vi mô.

Cách sử dụng

gap chấp nhận mọi độ dài hoặc tỷ lệ phần trăm CSS làm giá trị.

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


Bạn có thể truyền 1 độ dài cho Gap. Độ dài này sẽ được dùng cho cả hàng và cột.

Chữ viết tắt
.grid {
  display: grid;
  gap: 10px;
}
Đặt cả hàng và cột cùng nhau cùng một lúc
Đã mở rộng
.grid {
  display: grid;
  row-gap: 10px;
  column-gap: 10px;
}


Bạn có thể truyền 2 độ dài cho Gap, các độ dài này sẽ được dùng cho hàng và cột.

Chữ viết tắt
.grid {
  display: grid;
  gap: 10px 5%;
}
Đặt cả hàng và cột riêng biệt cùng một lúc
Đã mở rộng
.grid {
  display: grid;
  row-gap: 10px;
  column-gap: 5%;
}