比较图片说明

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 示例中进行测试,并使用 DevTools 探索性能。

使用相同的标记,我们可以将 padding-top: 56.25% 替换为 aspect-ratio: 16 / 9,并将 aspect-ratio 设置为指定的 width / height 比率。

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

使用 aspect-ratio 而非 padding-top 会更清晰,并且不会对内边距属性进行全面改造,以执行超出其常规范围的操作。

是的,没错,我使用 reduce 来链接 Promise 序列。我很聪明。但这种有点很智能的编码还是不要为好。

不过,如果使用异步函数改写以上代码,又容易让代码变得过于顺序

不推荐 - 过于循序
async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
代码简洁得多,但我的第二次获取要等到第一次获取读取完毕才能开始,以此类推。其执行效率要比并行执行获取的 Promise 示例低得多。幸运的是,还有一种理想的中庸之道。
推荐的编码方式 - 可读性强、并行效率高
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 位替换成了标准单调乏味但可读性强的 for 循环。

编写 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;
}

现在,您可以像访问任何其他 CSS 自定义属性一样,通过 var(--colorPrimary) 访问 --colorPrimary。不过,这里的区别在于,--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 标头,Web 服务器可以在响应中设置此标头,以指示浏览器不要将响应存储在任何 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 应随一组链接水平移动,此标题布局有助于设置相应阶段。此处没有绝对定位元素!

轻柔弹性是一种更纯粹的仅限居中策略。它柔和细腻,因为与 place-content: center 不同,在居中期间,子级框的大小不会发生变化。尽可能轻柔地将所有内容堆叠、居中和间隔。

.gentle-flex {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1ch;
}
优点
  • 仅处理对齐、方向和分布
  • 在一个位置集中进行修改和维护
  • Gap 可确保 n 个子项之间保持等距
缺点
  • 代码行数最多

非常适合宏观和微观布局。

用法

gap 接受任何 CSS 长度百分比作为值。

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


可以为 Gap 传递 1 个长度,该长度将同时用于行和列。

简写
.grid {
  display: grid;
  gap: 10px;
}
同时设置行和列
已展开
.grid {
  display: grid;
  row-gap: 10px;
  column-gap: 10px;
}


Gap 可以传递 2 个长度,分别用于行和列。

简写
.grid {
  display: grid;
  gap: 10px 5%;
}
同时单独设置行和列
已展开
.grid {
  display: grid;
  row-gap: 10px;
  column-gap: 5%;
}