如何使用 JavaScript 在 Chrome v78+ 上检测#enable-force-dark 标志?

Owe*_*van 5 javascript google-chrome google-chrome-extension

我最近为我的网站设计并实现了一种深色模式,该模式使用自定义深色来匹配浅色(默认)配色方案,而且我最近还意识到 Chrome 78 有一个名为#enable-force-dark. 启用后(用户必须这样做),Chrome 会自动尝试将网站转换为深色主题。它与操作系统的偏好分开,这意味着用户可以在系统范围内使用浅色模式,但启用此标志后,Chrome 仍将进行转换。

我正在使用以下代码来检测用户的设备是否像其他线程所建议的那样更喜欢暗模式。请注意,我正在使用 javascript 检测它,因为有一个按钮可以来回切换它,最终得到比使用@media查询更好的解决方案。

if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
    // my dark mode code goes here
}
Run Code Online (Sandbox Code Playgroud)

这个if语句是在一个 jQuery$(window).load函数下的,它完美地工作。

我需要的是能够检测到 Chrome 的新#enable-force-dark标志,逆转 Chrome 所做的更改,并启用我的,因为 Chrome 转换不完整,它搞砸了我的自定义样式。我知道这个功能没有被广泛使用,但我想面向未来。

这有可能吗?我不需要要求用户禁用该标志,但如果有必要我会。谢谢!

nin*_*cko 11

这是一个非答案,但 Chrome 的 #enable-force-dark(在默认模式下,没有子选项)似乎执行简单的颜色映射。单击下面的代码片段以动态生成此映射以供您观看。(一定要向下滚动有趣部分的嵌入框架。)

body {
  background-color:white;
}
Run Code Online (Sandbox Code Playgroud)
activate about:flags #enable-force-dark before using this snippet<br/>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>

//document.domain = 'https://stacksnippets.net';

function gray(s) {
  return s/(S-1)*100 + '%';
}

var S = 32; // renders a table of size S*S cells
for(let BODY_BGCOLOR of ['white']) {//, 'black']) {
  document.body.innerHTML += `(body background-color = ${BODY_BGCOLOR})<br/>`;

  for(let CASE of ['bg','text','bg gray','text gray','bg gray + text gray']) {

/*
    var iframe = $(`<iframe src="${location.href.replace(/(:\/\/[^\/]*\/).*$/, '$1')}"></iframe>`).appendTo(document.body);
    var d = iframe[0].contentWindow.document;
    d.open(); d.close();
    $('body', d).append('test');
 */
    $('<table>').css({display:'inline-block', border:'5px solid blue', backgroundColor:'black'}).append(
      $(`<caption>${CASE}</caption>`)
    ).append(

      Array(S).fill().map((_,l)=> 
        $('<tr>').append(

          Array(S).fill().map((_,s)=> {


            gradient = `hsl(0,${s/(S-1)*100}%,${l/(S-1)*100}%)`;

            if (CASE=='bg') {
              var backgroundColor = gradient; 
              var color = gradient;
              var text = '_';

            } else if (CASE=='text') {
              var backgroundColor = '#444'; // you can change this to convince yourself color and background-color are 
                                            // independent and do not depend on each other
              var color = gradient;
              var text = 'X';
            } else if (CASE=='bg gray') {
              var backgroundColor = `hsl(0,0%,${gray(l)})`;
              var color = '';
              var text = '_';
            } else if (CASE=='text gray') {
              var backgroundColor = 'black';
              var color = `hsl(0,0%,${gray(s)})`;
              var text = 'X';
            } else if (CASE=='bg gray + text gray') {
              var backgroundColor = `hsl(0,0%,${gray(l)})`;
              var color = `hsl(0,0%,${gray(s)})`;
              var text = '?';
            }


            return $('<td>').css({backgroundColor, color, fontSize:'0.5em'}).text(text);
          })
        )
      )
    ).appendTo(document.body)


  }
  $('<br/>').appendTo(document.body);
}
</script>

you can also right-click body tag and dynamically change its background-color, then highlight and unhighlight text, to convince yourself that color and background-color are usually independent
Run Code Online (Sandbox Code Playgroud)

从 Chrome 79 开始,以下任何事情似乎都是独立发生的:

  • 任何带有background-color> #ccc(如此粗略#cdcdcd或更高)的东西都会background-color反转其亮度(在尊重色调的同时反转)
  • 任何带有color< 的东西#999,粗略地,将其color亮度反转(在尊重色调的同时反转)

不幸的是,似乎“力”确实意味着。您可以访问https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme并查看“结果”面板并右键单击以在 DOM 检查器中查看结果,并摆弄并动态更改颜色(即使使用鼠标单击颜色);似乎您期望发生的事情(Chrome 注意到prefers-color-scheme: dark,如果是这样,则不会反转执行强制颜色映射),不会发生;这真的是力量

据我所知,您甚至无法轻松检测到这一点(也许有人可以在这里插话),因为window.getComputedStyle(some_element)将(大概是正确的)返回 CSS-spec 计算样式,而不是显示给用户的实际样式。

从数学上讲,很明显,除非此选项更改(您可以提交功能请求),否则我们无法“撤消”此操作以允许我们使用我们自己的样式表,或者以其他方式彻底解决它。这是一个证明:如果您查看上面生成的调色板,退后一步,您会注意到它减少了,因此您无法再使用某些颜色。具体来说,文本将始终显示为浅色(无深色文本),而背景将始终显示为深色(无浅色背景)。尝试想出一种在此模式下制作深色文本或浅色背景的方法;乍一看似乎是不可能的。因此,没有希望使用传统background-color并且color没有“奇怪的东西”发生。(你甚至不能使用 SVG 文本;

但是,并非完全不可能!我能想到三个想法:

  • 如果你真的需要它,我猜你可以使用一个<canvas>似乎没有受到影响的元素......但这真的很难看(无法选择文本等)。几乎肯定不会这样做。

  • 不强制背景颜色:

    • background-color:gradient(...)好像不受影响!因此,只要您真的想要浅色背景(任何粗略#ccc或更高的颜色),就可以通过使用渐变来强制使用任何您想要的背景颜色。这可能效率低下并导致额外的 GPU 工作(或者可能不会;您需要对其进行测试)。

    • 您可以尝试background-image:url(data:...)使用 data-url;没试过

  • 取消强制文本颜色:

    • 这个很狡猾,但我只是想出了一个可怕的kludge:找到一系列像常量函数一样的CSS过滤器......或者(由于颜色值被剪裁了),只是撞击颜色值对着夹轨,然后将其用作常量,然后在函数空间中应用任何必要的转换。就像有人给你一个 Photoshop/GIMP/等。软件并告诉您“这是一张漂亮的风景图像......只使用过滤器,使整个图像纯#0000ff 蓝色!(或某些选择的颜色)”......所以......让我们这样做

这里的总体思路如下:

.blue-text {

  filter: 
   brightness(0%)      /* multiple all colors by 0 to get black; white may not work */
   contrast(50%)       /* get gray from black */
   sepia(100%)         /* get non-gray from gray */
   contrast(250%)      /* \   may not be necessary, but here we */
   brightness(500%)    /*  |   get fully satured from less-than- */
   saturate(500%)      /* /    -fully saturated                  */
   /*hue-rotate(???deg) we have pure red now, but if we didn't, we'd do this*/
   hue-rotate(240deg)  /* rotate from pure red to whatever you want */
   saturate(200%);     /* for some reason, hue-rotate is not keeping saturation? */
                       /* unsaturate your color here, adjust lightness, etc. */

}
Run Code Online (Sandbox Code Playgroud)

所以......而不是每次我们想要强制一种颜色时都粘贴那种恐怖......我们有几个选择。意识到只有在您希望使用深色文本时才需要使用它(因此您不需要为浅色执行此操作)。我能想到的方法有两种。第一种方法是,如果您只是快速模拟某些东西,或者您是前端设计师或做主题;这是“纯 CSS”方式。使用下面的方法,您可以filter:...在任何需要使用深色文本的地方使用color:...,无论是在 CSS 声明中,还是在内联中。定义了一些预设,但不需要使用:

.blue-text {

  filter: 
   brightness(0%)      /* multiple all colors by 0 to get black; white may not work */
   contrast(50%)       /* get gray from black */
   sepia(100%)         /* get non-gray from gray */
   contrast(250%)      /* \   may not be necessary, but here we */
   brightness(500%)    /*  |   get fully satured from less-than- */
   saturate(500%)      /* /    -fully saturated                  */
   /*hue-rotate(???deg) we have pure red now, but if we didn't, we'd do this*/
   hue-rotate(240deg)  /* rotate from pure red to whatever you want */
   saturate(200%);     /* for some reason, hue-rotate is not keeping saturation? */
                       /* unsaturate your color here, adjust lightness, etc. */

}
Run Code Online (Sandbox Code Playgroud)

第二种方法是编写一个 javascript 库,它会扫描整个 DOM,调用getComputedStyle(HTMLElement)并自动计算适当的过滤器,然后将其作为 CSS 样式注入。如果 Chrome 没有优化为在页面上同时处理数百个元素上的单个 CSS 过滤器,这可能会很慢。

此外,如果 Chrome 无法在页面上同时使用数百个 CSS 过滤器(也许您每次使用过滤器时都需要重新编译 GPU 着色器!!),这两种方法中的任何一种都可能会变慢或不起作用。

所以...让我们写那个库...


让我们写一个#enable-force-dark 库

所以上面的主要问题是我们想要向前兼容。也就是说,我们不想通过插入这些奇怪的 CSS 样式来创建会破坏网络的错误代码,这些样式会影响 GPU 并通常使事情变得可怕。我们想要向前兼容

这样做的一个办法是,如果有人提交与眨眼团队功能请求,询问是否最好的方法是通过眨眼引擎源代码检测出来,看看(它是开源的),或询问是否会有一些navigator.prefersColorScheme在未来。否则,不可能以一种比做这样的事情更丑陋的方式向前兼容(如果是 2025 年并且此代码仍在运行,请发送弹出通知以更新它......在实践中你永远不想这样做,但这只是说明了一种担忧,即如果您无法将其关闭,则您需要有一个永久的第三方依赖项,您信任该依赖项来更新内容,例如<script src="http://hypothetical-trusted-third-party.com/always-up-to-date-forcedark-polyfill">,这将花费无限量的金钱并且一个安全漏洞)。

我们接下来干吗?

我想我注意到 force-dark 的工作方式有一个非常奇怪的微妙之处。在上面,我们注意到反演函数独立于colorbackground-color(即它们的因素,即变化background-color不会影响color或反之亦然)......情况并非总是如此。

如果我们有一个<iframe>AND background-color,iframe 的 BODY TAG的属性严格小于 < ,就会发生一件非常奇怪的事情#333333。在这种情况下(截至撰写本文时),Chrome 使用它来“检测”页面是否具有“暗色”的“意图”,如果是,则关闭强制文本​​的暗模式(但不会关闭强制背景颜色的暗模式)。伪代码似乎是:

function internalSecretChromeFunction(...) {
  websiteIsDarkish = (document.body.style.backgroundColor < #333333);
  if (websiteIsDarkish)
    enableColorInversionForText();
  enableColorInversionForBackgroundColor();
}
Run Code Online (Sandbox Code Playgroud)

这给我们带来了完整的 SVG 文本,我之前说过,它也被视为普通文本。因此,我们可以使用画布元素ctx.drawImage函数来复制 SVG,它似乎是直接从内存中复制图像而不是重新绘制它(可能是安全问题?),但我们可以使用它来构建 #enable-force-dark 检测器作为跟着!首先注意可能状态的矩阵:

  • 不是( #force-dark)
    • 深色文字很暗
    • 浅色文字很浅
  • #force-dark && body.bgcolor<#333
    • 深色文字很暗
    • 浅色文字很浅
  • #force-dark && body.bgcolor>=#333
    • 深色文本被反转(深色->浅色)
    • 浅色文本被反转(浅色->深色)

如何进行?我们可以在几毫秒内异步确定这一点:

  • 创建一个包含一个元素和一个元素的<iframe>with<body style="background-white;"><svg><canvas>
  • 将 SVG 复制到画布上 drawImage
  • 查询像素颜色,看是否反转;在 iframe 之外返回这个值

但是,这似乎依赖于可能是错误的行为(实际上是三个错误)...... 1)如果 body.background-color 为深色,则背景颜色不会反转,2)prefers-color-scheme:dark media不导出,3) SVG 图像被复制而不是从头开始重新生成

[在几周内编辑传入...希望...有人应该真正联系眨眼团队]


当然这个功能可能会改变,所以这个答案有一天可能不再有效。

  • 天哪,我喜欢寻找这样的答案 (2认同)
  • 哇,这真是一个地狱般的回应。我完全忘记了这个问题,继续前进,没有打开 StackOverflow,将我的电子邮件设置为不打扰等等,所以我从未见过这个;你是对的,这不是答案,但它提供了宝贵的见解,我对你投入的工作量印象深刻。我最终放弃了尝试检测 Chrome 标志,因为似乎有极少数用户正在使用它们(尤其是这个),而且它不值得我付出努力(无论是否可能),但我很欣赏这一点!干杯! (2认同)

axb*_*xby 7

检测 Chrome #enable-force-dark/“自动深色主题”是否启用

本文提到了一个不同的 chrome 标志,并将其称为“自动深色主题”,但其技术似乎可以检测是否#enable-force-dark为我启用: https: //developer.chrome.com/blog/auto-dark-theme/ #检测自动黑暗主题

要检测用户是否处于自动黑暗主题中,请创建一个元素并将background-color颜色canvas方案设置为浅色。如果计算出的背景颜色不是白色 ( rgb(255, 255, 255)),则自动深色主题将应用于页面。

HTML:

<div id="detection"
     style="display: none; background-color: canvas; color-scheme: light">
</div>
Run Code Online (Sandbox Code Playgroud)

JS:

const detectionDiv = document.querySelector('#detection');
// If the computed style is not white then the page is in Auto Dark Theme.
const isAutoDark = getComputedStyle(detectionDiv).backgroundColor != 'rgb(255, 255, 255)';
Run Code Online (Sandbox Code Playgroud)

该消息来源还表示,检查window.matchMedia("(prefers-color-scheme: dark)")应该有效,但这与我在自己的浏览器和其他在线帖子中观察到的情况不符。即使#enable-force-dark启用了此检查,此检查也会返回 false:

const userPrefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)")
Run Code Online (Sandbox Code Playgroud)

因此,我检查两者,如果其中之一表明偏好深色,则应用我的深色样式表:

// Check if user prefers dark mode
const prefersColorSchemeDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;

// Check if the browser is automatically darkening elements
// Only necessary to check if the matchMedia query does not work.
// https://developer.chrome.com/blog/auto-dark-theme/#detecting-auto-dark-theme
const detectionDiv = document.querySelector('#detection');
// If the computed style is not white then the page is in Auto Dark Theme.
const isAutoDark = getComputedStyle(detectionDiv).backgroundColor != 'rgb(255, 255, 255)';

if (prefersColorSchemeDark || isAutoDark) {
     // apply dark stylesheet
}
Run Code Online (Sandbox Code Playgroud)

有关调用的更多信息,请参阅此 stackoverflow 帖子window.matchMedia('(prefers-color-scheme: dark)')

这对我在 linux 上的 chromium 版本 98.0.4758.102 和 Android Chrome 版本 99.0.4844.58 上有效。

#enable-force-dark改变颜色比#cdcdcd

另一个令我困扰的怪癖是,在黑暗模式下,在黑暗背景上应该明亮的某些东西在启用时呈现为黑暗,例如https://mathjax.org/#demo#enable-force-dark中的分数条和平方根线

我发现将body样式设置为color: #cdcdcd导致大部分文本为白色,但当它在#cecece和之间时#ffffff,chrome 使其变暗:MathJax.org 演示显示启用了 chrome enable-force-dark 导致大部分文本为白色,但带有深色分数条和平方根线

因此,设置body style="color:#cdcdcd"使 chrome 渲染所有文本,非背景内容根据需要渲染浅色和深色背景。