为类和媒体查询定义暗模式,无需重复 CSS 自定义属性声明,并允许用户在颜色模式之间切换

Chr*_*per 8 javascript css media-queries

我有:

\n
body { background: white; }\n
Run Code Online (Sandbox Code Playgroud)\n

为了显示暗模式,我使用.dark类:

\n
.dark body { background: black; }\n
Run Code Online (Sandbox Code Playgroud)\n

为了检测用户是否将其操作系统设置为使用深色主题,我们有prefers-color-scheme

\n
@media (prefers-color-scheme: dark) {\n  body { background: black; }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后我们就有了 DRY(Don\xe2\x80\x99t Repeat Yourself)编程的想法。我们是否可以在不重复CSS属性声明的情况下定义深色模式,并在此过程中允许用户通过JS在颜色模式之间进行切换?

\n

对于上面的示例,.dark类和媒体查询是彼此的副本。

\n

到目前为止我做了什么

\n

在 CSS 中跳过prefers-color-scheme并使用:

\n
body { background: white; }\n.dark body { background: black; }\n
Run Code Online (Sandbox Code Playgroud)\n

然后通过JS,检测他们的设置并调整

\n
if (window.matchMedia && window.matchMedia(\'(prefers-color-scheme: dark)\').matches) {\n     document.getElementsByTagName(\'html\')[0].classList.add(\'dark\');\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这种方法的问题是它没有prefers-color-scheme在 CSS 中使用。

\n

虽然我可以补充:

\n
@media (prefers-color-scheme: dark) {\n  body { background: black; }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

它不允许我通过 JS切换配色方案,因为我无法prefers-color-scheme: dark为在操作系统首选项中设置了深色的用户取消。

\n

2022年解决这个问题的方法是什么?

\n

Noa*_*oam 9

我们来分析一下。

我们绝对不能将深色模式样式放在媒体查询中,因为无法通过现场选择来应用它。因此,我们希望样式位于某个类 ( dark) 下,没有媒体查询(以便可以通过 JS 切换该类),但媒体查询应该以某种方式提取相同的样式,即使没有该类,并且不重复样式。light如果用户选择的话,我们可能还需要一个类来覆盖媒体查询。

这里完美的解决方案是废弃的@apply at-rule,它可以允许在单个变量下重用一堆样式。但这已经被放弃了。

重用一堆样式的标准建议是在 CSS 预处理器上使用 mixin(因此不会在项目的源代码上重复,但会在编译后的 CSS 上重复)。如果所有属性都应用于一个<body>或几个元素,那么我相信这就是正确的方法。然而,您要求一个没有预处理器的解决方案。

另一种方法是在 JS 中而不是在 CSS 本身中使用媒体查询,并相应地切换类。此方法甚至可以处理复杂的主题,并将属性应用于许多元素,但在 JS 生效之前,它可能会出现“无样式内容的闪烁” 。无论如何,您已经提出了这个问题,并要求提供一个将媒体查询保留在 CSS 本身中的解决方案。

您还要求不要使用重复的 CSS 变量。现在,我不确定是否可以不重复任何内容,但我们可以将重复减少为两个“切换”变量,而不管受影响的属性数量有多少。

var root = document.documentElement,
  theme = window.getComputedStyle(root)
  .getPropertyValue('--light') === ' ' ? 'dark' : 'light';

document.getElementById('toggle-theme')
  .addEventListener('click', toggleTheme);

function toggleTheme() {
  root.classList.remove(theme);
  theme = (theme === 'dark') ? 'light' : 'dark';
  root.classList.add(theme);
}
Run Code Online (Sandbox Code Playgroud)
/*
  "initial"ized variables are like undefined variables and will resolve to
  their fallback value.
  Whitespaced variables (the space is required) will serve as an empty value
  in chaining.
*/

@media (prefers-color-scheme: dark) {
   :root {
    --light: ;
    --dark: initial;
  }
}

@media (prefers-color-scheme: light) {
   :root {
    --dark: ;
    --light: initial;
  }
}

:root.dark {
  --light: ;
  --dark: initial;
}

:root.light {
  --dark: ;
  --light: initial;
}

#content {
  background-color: var(--dark, darkblue) var(--light, lightblue);
  color: var(--dark, white) var(--light, black);
  border: 5px solid var(--dark, blue) var(--light, yellow);
}
Run Code Online (Sandbox Code Playgroud)
<div id="content">
  Hello, world!
  <button id="toggle-theme">Toggle theme</button>
</div>
Run Code Online (Sandbox Code Playgroud)

最后,我可以想到另一种可能的 hack,尽管我真的不喜欢它。这个想法是添加两个<div>s,其唯一目的是根据首选/选定的主题将样式继承到另一个。

var root = document.documentElement,
  theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';

document.getElementById('toggle-theme')
  .addEventListener('click', toggleTheme);

function toggleTheme() {
  root.classList.remove(theme);
  theme = (theme === 'dark') ? 'light' : 'dark';
  root.classList.add(theme);
}
Run Code Online (Sandbox Code Playgroud)
.dark-styles {
  background: darkblue;
  color: white;
  border-color: blue;
}

.light-styles {
  background: lightblue;
  color: black;
  border-color: yellow;
}

:root.dark .light-styles {
  all: inherit;
}

@media (prefers-color-scheme:dark) {
   :root:not(.light) .light-styles {
    all: inherit;
  }
}

#content {
  border: 5px solid;
  border-color: inherit;
}
Run Code Online (Sandbox Code Playgroud)
<div class="dark-styles">
  <div class="light-styles">
    <div id="content">
      Hello, world!
      <button id="toggle-theme">Toggle theme</button>
    </div>
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)


Bap*_*tou 5

下面是一个使用CSS 变量prefers-color-scheme媒体查询和单选按钮的简单解决方案。此处,页面检测操作系统深色/浅色主题,并允许用户通过单击单选按钮而不使用 JavaScript 来更改主题。如果您确实需要 JavaScript,您还可以在这些单选按钮上触发单击事件。

/* Defines theme variables */

:root {
    --theme-light-color: #222222;
    --theme-light-background: #FFFFFF;
    --theme-dark-color: #DDDDDD;
    --theme-dark-background: #222222;
}

/* Defines body content styles */

body {
    margin: 0;
}

body>main {
    min-height: 100vh;
    color: var(--theme-main-color);
    background: var(--theme-main-background);
}

label[for="theme-light"] {
    color: var(--theme-light-color);
    background: var(--theme-light-background);
    cursor: pointer;
}

label[for="theme-dark"] {
    color: var(--theme-dark-color);
    background: var(--theme-dark-background);
    cursor: pointer;
}

/* IF theme = light THEN */

#theme-light:checked~main {
    --theme-main-color: var(--theme-light-color);
    --theme-main-background: var(--theme-light-background);
}

@media (prefers-color-scheme: light){
    :root {
        --theme-main-color: var(--theme-light-color);
        --theme-main-background: var(--theme-light-background);
    }
}

/* IF theme = dark THEN */

#theme-dark:checked~main {
    --theme-main-color: var(--theme-dark-color);
    --theme-main-background: var(--theme-dark-background);
}

@media (prefers-color-scheme: dark){
    :root {
        --theme-main-color: var(--theme-dark-color);
        --theme-main-background: var(--theme-dark-background);
    }
}
Run Code Online (Sandbox Code Playgroud)
<!DOCTYPE html>
<html lang="en">
    <body>
        <!-- THEME SWITCH -->
        <input type="radio" id="theme-light" name="theme" hidden>
        <input type="radio" id="theme-dark" name="theme" hidden>
        
        <!-- BODY CONTENT -->
        <main>
            <label for="theme-light">Light</label>
            <label for="theme-dark">Dark</label>
            <p>Hello World!</p>
        </main>
    </body>
</html>
Run Code Online (Sandbox Code Playgroud)