元主题颜色和技巧

Avatar of Manuel Matuzovic
Manuel Matuzovic

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费积分!

从 15 版开始,Safari 在 macOS 和 iOS 上都支持theme-color <meta> 标签。这是一个令人兴奋的消息,因为现在第一个桌面浏览器支持此<meta> 标签,并且它也支持media 属性和prefers-color-scheme 媒体特性。

我从未真正注意过theme-color 元标签,但现在是了解其功能和局限性并尝试发现一些有趣用例的好时机。

注意!Safari 在 Safari Technology Preview (127) 中删除了对theme-color 元标签的支持。这只是暂时的,从 128 版开始,它再次支持它。

功能和限制

过去几年我一直这样使用theme-color 元标签:只是content 属性的一个普通的十六进制代码。

<meta name="theme-color" content="#319197">

根据我今年早些时候进行的测试,这在 Chrome、Brave 和三星互联网(Android 上)、Chrome 中安装的 PWA 以及现在 Safari Technology Preview 中均有效。

所有支持的浏览器都很好地支持十六进制颜色。

CSS 颜色支持

我想到的第一个问题之一是“我们也可以使用颜色关键字、hsl()rgb() 吗?”根据 HTML 规范,属性的值可以是任何 CSS 颜色。我创建了这个 theme-color 测试 CodePen 来验证这一点。

<meta name="theme-color" content="hsl(24.3, 97.4%, 54.3%)">
Blank webpage with orange header.
theme-color 元标签支持任何形式的 CSS 颜色:关键字、rgb()hsl() 或十六进制代码。
Blank webpage with a hot pink header. There are controls to the right of the webpage for browser testing.
查看 Android Galaxy S20 上的 Chrome 90

所有支持的浏览器也支持hsl()rgb()。这很棒,因为它允许我们使用 JavaScript 做一些很酷的事情。我们稍后会讨论这个问题,但首先让我们看看一些限制。

透明度

十六进制代码、rbg()hsl() 和关键字都得到了良好且一致的支持,但包含透明度的颜色:则没有那么好。实际上,它们在大多数浏览器中都受支持,但结果并不一致,有时甚至出乎意料。

transparent 是一个 CSS 颜色,在大多数浏览器中,它在theme-color 元标签中使用时会按预期工作。所有常规移动浏览器都不会更改颜色并显示默认标签栏,但 macOS 上的 Safari 和 macOS 上的 Chrome Canary PWA 会将标签栏变为黑色。Android 上的 PWA 回退到manifest.json 中定义的theme-color,我们稍后会讨论。

Examples of the same white webpage with either white or dark headers with the browser vendor labeled above each one.
带有透明theme-color 元标签的浏览器

所有浏览器都解释hsla()rgba(),但它们将 alpha 值设置为 1。唯一的例外是 macOS 上的 Safari;它解释了透明度,但透明颜色似乎有一个黑色基线。这会导致浅橙色看起来像深橙色。

Same browser comparison but all with orange headers, except Safari which is a darker brown.
hsla() 应用于theme-color 元标签

新的颜色函数

Safari 15 是第一个支持lab()lch()hwb() 颜色函数的浏览器。如果您在 CSS 中使用这些函数,它们可以工作,但如果您在theme-color 元标签中使用它们,则不行。

所有三个声明在 Safari 15 中都运行良好

body {
  background-color: hwb(27 10% 28%);
  background-color: lch(67.5345% 42.5 258.2);
  background-color: lab(62.2345% -34.9638 47.7721);
}

如果您在theme-color 元标签中使用任何新的颜色函数,Safari 不会解释它们,而是回退到它自己的颜色选择算法。Safari 可能会使用您的<body> 的背景颜色作为theme-color,这意味着您可能在没有显式定义theme-color 的情况下获得预期结果。

<meta name="theme-color" content="lab(29.2345% 39.3825 20.0664)">
Green webpage with green header.

请注意,在撰写本文时 Safari 15 是唯一支持这些新颜色函数的浏览器。

currentColor

如果支持 CSS 颜色,那么currentColor 也应该可以工作,对吧?不,不幸的是,在任何浏览器中都不行。这可能是一个不常见的用例,但我希望我们可以将theme-color 设置为<body><html> 元素的当前颜色。

<style>
  body {
    color: blue;
  }
</style>

<meta name="theme-color" content="currentColor">

我在 WebKit 错误跟踪器中找到了一张标题为 <meta name="theme-color" content="..."> 也应支持 CSS currentcolor。” 的工单。如果有人处理这张工单,支持可能会在将来发生变化。

禁止的颜色

当我测试 CSS 颜色关键字时,我使用了颜色red,但它不起作用。首先,我认为不支持关键字,但bluehotpinkgreen 运行良好。事实证明,Safari 不支持 一小部分颜色,这些颜色会妨碍使用界面。red 不起作用,因为它在视觉上与标签栏中关闭按钮的背景颜色过于接近。此限制特定于 Safari,在所有其他支持的浏览器中,任何颜色似乎都可以正常工作。

Wbite webpage with a color picker set to red. The header of the browser is white.
如果将theme-color 设置为red,Safari 会使用它认为合适的任何颜色。

自定义属性

我不太了解浏览器和自定义属性的内部机制,以及是否可以在<head> 中访问自定义属性,但我还是试了一下。不幸的是,它在任何浏览器中都不起作用。

<style>
  :root {
    --theme: blue;
  }
</style>

<meta name="theme-color" content="var(--theme)">

这几乎是我想知道的关于theme-color 元标签基本支持的所有内容。接下来,让我们看看如何以及如何不为标签栏实现暗模式。

暗模式

Safari 15 是第一个支持theme-color 元标签上的media 属性和prefers-color-scheme 媒体特性的桌面浏览器。从 93 版开始,Chrome 也支持它,但仅限于已安装的渐进式 Web 应用。

根据 web.dev 上的 Web 应用清单页面,如果您定义了多个theme-color 元标签,浏览器会选择第一个匹配的标签。

<meta name="theme-color" content="#872e4e" media="(prefers-color-scheme: dark)">

我渴望了解在不支持media 属性的浏览器中会发生什么。我创建了一个 演示页面来测试暗模式,其中包含上述元标签,还允许您将站点安装为 PWA。webmanifest.json 包含另一个主题颜色的颜色定义。

{
  "name": "My PWA",
  "icons": [
    {
      "src": "https://via.placeholder.com/144/00ff00",
      "sizes": "144x144",
      "type": "image/png"
    }
  ],
  "start_url": "/theme-color-darkmode.html",
  "display": "standalone",
  "background_color": "hsl(24.3, 97.4%, 54.3%)",
  "theme_color": "hsl(24.3, 97.4%, 54.3%)"
}

以下是支持的浏览器在亮模式下显示标签栏的方式。无论浏览器是否支持媒体属性,它都会解释第一个元标签,这一点无关紧要。

以下是同一页面上的标签栏在深色模式下的外观。这些结果比较有趣,因为它们略有差异。Canary PWA 和 Safari 支持并显示深色。所有移动浏览器都使用其默认的深色标签栏样式,除了三星互联网,因为它不支持prefers-color-scheme媒体特性,所以使用浅色样式。(TIL:这应该在不久的将来发生变化。)

我做了一个最后的测试。我想看看如果我只为深色模式定义主题颜色,但在浅色模式下访问页面会发生什么。

<meta name="theme-color" content="#872e4e" media="(prefers-color-scheme: dark)">

这些结果让我最惊讶,因为我预计所有移动浏览器都会忽略媒体属性,而不管怎样都只使用元标签中的深色,但普通 Chrome Canary 完全忽略了整个元标签,即使它不支持media属性。正如预期的那样,两个 Canary PWA 都回退到清单文件中定义的颜色。

另一件有趣的事情是,Safari 即使我没有为浅色模式定义主题颜色,也会显示一个theme-color。这是因为,如果你没有提供theme-color,Safari 会自己选择一个颜色。在这种情况下,它使用页面的背景颜色,但它也可能使用<header>元素的背景颜色,例如。

如果你想为浅色和深色模式定义主题颜色,最好的方法是定义这两种颜色,并使用第一个元标签作为不支持媒体特性的浏览器的后备。

<meta name="theme-color" content="#319197" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#872e4e" media="(prefers-color-scheme: dark)">

Safari 已经证明theme-color在桌面浏览器上也能很好地工作。我相信设计师和开发人员会找到许多创造性的方法来使用这个元标签,特别是考虑到可以通过 JavaScript 更改其值。我收集并创建了一些有趣的演示,以激发你的灵感。

演示和用例

主题化

poolsuite.net 为网站提供不同的主题,并相应地更改主题颜色。

Max Böck 也会在他的网站上更改theme-color,当你更改主题时。

页面主题化

大多数网站不提供自定义主题,但你仍然可以为你的页面增添一些特色。Dave 在他的博文中为链接和图标使用不同的关键颜色,现在也用在标签栏中。

渐变

如果你在页面上使用渐变,可以通过使渐变跨越整个浏览器来突出你的样式。theme-color元标签不支持渐变,但你可以对元标签和页面背景渐变的起始颜色使用相同的颜色。

<meta name="theme-color" content="rgb(0, 235, 255)">

<style>
  body {
    background: linear-gradient(rgb(0, 235, 255), #08124a);
  }
</style>
表单验证

我构建了这个表单概念验证,它在表单验证时更改theme-color。它以蓝色标签栏开始,如果提交的数据无效则变为红色,如果有效则变为绿色。

const email = document.querySelector('input')
const themeColor = document.querySelector('meta[name="theme-color"]')
const msg = document.querySelector('[aria-live]')
let color = '#FA0000'
let message = 'Error message'

document.querySelector('button').addEventListener('click', (e) => {
  e.preventDefault()

  email.reportValidity()
  email.setAttribute('aria-invalid', true)

  if (email.validity.valid) {
    color = '#00FF00'
    message = "Success message!"
    email.setAttribute('aria-invalid', false)
  }

  msg.textContent = message
  themeColor.setAttribute('content', color)
});
迪斯科模式

我并不是说你应该这样做,但你可以通过结合setIntervalhsl()颜色将你的网站置于💃迪斯科模式🕺。

/*
Inspired by https://twitter.com/argyleink/status/1408184587885309952
*/

const motion = window.matchMedia("(prefers-reduced-motion: no-preference)");

// Check if users don't have a preference for reduced motion
if (motion.matches) {
  let scheme = document.querySelector('meta[name="theme-color"]')
  let hue = 0
  let color

  setInterval(() => {
    color = `hsl(${hue+=5} 50% 30%)`
    document.body.style.background = color;
    scheme.setAttribute('content', color)
  }, 50)
滚动

Stuart 有一个好主意,他建议在滚动时更改主题颜色。我构建了这个快速原型,同样使用hsl()颜色。

请仅在不会对性能产生负面影响的情况下执行此操作。

Max 构建了一个演示,其中他根据视口中当前部分的背景颜色使用 Intersection Observer 更改theme-color

const setThemeColor = (color) => {
  const meta = document.querySelector('meta[name="theme-color"]')
  if (meta) {
    meta.setAttribute('content', color)
  }
}

if ("IntersectionObserver" in window) {
  const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        const { isIntersecting, target } = entry
        if (isIntersecting) {
          const color = window.getComputedStyle(target).getPropertyValue("background-color");
          setThemeColor(color)
        }
      })
  }, {
    root: document.getElementById('viewport'),
    rootMargin: "1px 0px -100% 0px",
    treshold: 0.1
  })
  
  document.querySelectorAll('.section').forEach(section => {
    observer.observe(section)
  })
}
提取颜色

另一个有趣的想法是从你的标题图像中自动提取主导颜色或平均颜色,并将其用作theme-color

<script type="module">
  import fastAverageColor from "https://cdn.skypack.dev/[email protected]";
  const fac = new fastAverageColor();
    
  fac.getColorAsync(document.querySelector('img'))
    .then(color => {
      document.querySelector('meta[name="theme-color"]').setAttribute('content', color.rgba)
    })
    .catch(e => {
      console.log(e);
    });
</script> 
  
<img src="/amy-humphries-2M_sDJ_agvs-unsplash.jpg" alt="A sea star on blue sand." />

这只是一些想法,但我已经喜欢上它的发展方向,我相信你会想出更多创造性的方法来使用theme-color元标签。

资源