在React中使用Less + CSS Modules切换主题

9
在我的项目中,我使用带有Less的CSS模块化,这意味着我可以兼顾两者优点。
我的“src”文件夹大致如下:
components/
    [all components]
theme/
    themes/
        lightTheme.less
        darkTheme.less
    palette.less

palette.less:

@import './themes/lightTheme.less';

然后,在每个想要使用主题颜色的组件中,我都会这样做:

component.module.less:

@import '../../theme/palette.less';

.element {
    background-color: @primary;
}

这种结构使我能够编辑palette.less并导入我想要使用的主题。但问题是我希望用户能够自行选择他们喜欢的主题。主题应该在运行时可切换,这意味着我需要同时拥有这两个主题。


我设想的完美解决方案如下:

app.less

body {
    @theme: @light-theme;

    &.dark-theme {
        @theme: @dark-theme;
    }
}

然后在每个组件中导入 @theme 变量,并从中读取属性(即 @theme[primary])。

不幸的是,Less 变量的作用域并不像这样工作。


我对任何使用 Less 模块的解决方案持开放态度。

谢谢!


@Ranjithv 这只是一些伪代码,用来演示我所想的。 - Itay Ganor
1
你应该看一下 CSS 变量 https://developer.mozilla.org/zh-CN/docs/Web/CSS/Using_CSS_custom_properties - Morpheus
最佳解决方案是使用 css in js,例如 styled-components、emotion等,然后使用 ThemeProvider,可以轻松地通过编程方式切换多个主题。 - gadi tzkhori
1
嗨@gaditzkhori,我们不想使用styled-components,因为这会使我们的CSS变得非常混乱(那些.styled.js文件,除了它们绝对不是组件之外,其他都是组件...) - Itay Ganor
你正在使用webpack吗? - Mohamed Ramrami
显示剩余4条评论
4个回答

3

我知道你可能一直在寻找使用Less / CSS模块的解决方案,但很可能你的情况可以仅通过使用CSS变量来解决(正如Morpheus在你的问题下评论的那样)。

它是如何工作的?

你需要确保所有的样式都不使用硬编码的值,即不要写成:

.awesome-div {
  background-color: #fefefe;
}

您将会拥有:

:root {
  --awesome-color: #fefefe;
}

.awesome-div {
  background-color: var(--awesome-color);
}

切换浅色和深色主题

在这种方法中,有两种更改主题的方式:

  • 使用原生JS代码在React中更新:root CSS元素,请查看此codepen获取更多信息;
  • 只需加载一个包含所有新的:root变量的组件,在其component.css文件中进行设置;

在React(以及原生CSS)中,您可以轻松地拥有多个组件/元素在其.css文件中声明自己的:root

此外,任何新的:root都将覆盖先前:root的冲突值。例如,如果在app.css文件中我们有:root { --color: red; },并且在加载另一个组件时,例如组件A,其中在component_a.css中我们覆盖了相同的变量,例如:root { --color: blue; },则在我们的浏览器中呈现的将是来自组件A的变量。

按照这个逻辑,您可以拥有一个虚拟组件,它什么也不做,但是在此组件.js文件中,您导入主题的.css文件,例如:

import './light.css'; // suppose this is the light-theme dummy component

在您的应用程序中切换主题时,您只需从场景中删除虚拟组件并调用其他组件即可。

我对Codepen的经验不太丰富,无法在那里提供包含导入/模块的示例,但我希望上面的解释可以让您了解我的意思。以下是我打算演示的简要伪代码:


loadTheme() {
  if (this.state.theme === 'dark') return <LightTheme />;
  if (this.state.theme === 'user-3232') return <UserTheme />;
  return <DarkTheme />;
}

render() {
  return (
    <App>
      {this.loadTheme()}
      <OtherContent>
    </App>
  );
}


CSS变量是一个很酷的解决方案,但它消除了我进行值操作的选项,比如淡化颜色。 - Itay Ganor
你可以在.less文件中声明你的CSS变量,并使用值操作。 - Mohamed Ramrami
你也可以使用CSS变量来模拟颜色渐变;我在实际生产中经常这样做,例如声明 --fade-me: 200, 215, 225 然后将此变量用于如下所示的代码:border-color: rgba(var(--fade-me), 0.2)。虽然这不像使用CSS预处理器那么直接,但它使我能够保持相对有组织的CSS代码库,而不是像我预期的那样混乱。不过,如果在您的项目中过于依赖Less,那么我建议您继续使用它,我会把我的答案留在这里,因为一些使用纯CSS的人可能会从中受益。顺便说一句,希望您能在这里找到解决方案。 - LuDanin
1
我不明白“导入主题”的示例是如何工作的。导入主题将在构建时导入CSS,最后一个将获胜。 - user3053247

1

你需要使用 CSS 变量,一个简单的选择是使用 react-theme-change 库。

示例:

themes.ts

import ReactThemeChange from 'react-theme-change';

const base = {
    btn_radius: '50%',
};

const themes = {
    dark: {
        bg_0: 'rgba(21, 14, 65, 1)',
        title: 'rgba(255, 255, 255, 1)',
    },
    light: {
        bg_0: 'rgba(239, 242, 247, 1)',
        title: 'rgba(42, 49, 60, 1)',
    },
    gray: {
        bg_0: 'rgba(35, 42, 63, 1)',
        title: 'rgba(255, 255, 255, 1)',
    },
};
const useThemeChange = ReactThemeChange({
    base,
    themes,
    defaultTheme: 'light',
});

export default useThemeChange;    

样式

button {
  width: 50px;
  height: 50px;
  border-radius: var(--btn_radius);
  cursor: pointer;
}

.demo {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 12px;
  align-items: center;
  background-color: var(--bg_0);
  color: var(--title);
}

.themeName {
  font-size: 40px;
}

.btns {
  display: flex;
  gap: 8px;
}

开关语句

import React from 'react';

import useThemeChange from './themes';
import './App.scss';

function App() {
    const {theme, setTheme} = useThemeChange();

    return (
        <div className="demo">
            <div className="themeName">current theme: {theme.name}</div>
            <div className="btns">
                <button onClick={() => setTheme('dark')}>dark</button>
                <button onClick={() => setTheme('light')}>light</button>
            </div>
        </div>
    );
}

export default App;

0

1
正如我在评论中所说,我们不想使用styled-components,因为它会使我们的CSS变得非常混乱(那些.styled.js文件,除了它们绝对不是组件之外,其他所有东西都是组件...) - Itay Ganor
如果您不想使用 .styled.js 文件,那么您并不需要使用它们。 此外,如果您喜欢的话,可以创建一个包含所有样式的 styled 组件。然后将您的应用程序包装在此组件周围,并将 CSS 保留在同一文件中。 - xurei

0
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import navStyle from './navbar.module.css';
import './assets/css/global.css';


export default function Navbar() {
  const [theme, setTheme] = useState('light');

  const handleTheme = () => {
    var root = document.querySelector(':root');
    var rootStyle = getComputedStyle(root);

    if(rootStyle.getPropertyValue('--Forground--') === 'black'){
      root.style.setProperty('--Forground--', 'white');
      root.style.setProperty('--Background--', 'black');
      setTheme('dark');
    }else{
      root.style.setProperty('--Forground--', 'black');
      root.style.setProperty('--Background--', 'white');
      setTheme('light');
    }
  }

  return (
    <>
      <div className={navStyle.navbarDesign}>
        <div className={navStyle.logo}>
          <span className={navStyle.world}>World</span>
          <span className={navStyle.coder}>Coder</span>
          <span className={navStyle.master}>Master</span></div>
        <div className={navStyle.theme}>
          <i onClick={handleTheme} className={theme === 'light' ? `fa fa-sun ${navStyle.sunThemeIcon}` : `fa fa-moon ${navStyle.moonThemeIcon}`}></i>
        </div>
        <ul className={navStyle.listItems}>
          <li><Link className={navStyle.link} to="/">Home</Link></li>
          <li><Link className={navStyle.link} to="/service">Service</Link></li>
          <li><Link className={navStyle.link} to="/blog">Blog</Link></li>
          <li><Link className={navStyle.link} to="/about">About</Link></li>
          <li><Link className={navStyle.link} to="/">Login</Link></li>
        </ul>
        <div className={navStyle.searchGroup}>
          <input type="search" name="" id="" className={navStyle.navbarSearch} />
          <button className={navStyle.navbarSearchBtn}>Search</button>
        </div>
      </div>
    </>
  )
}

1
目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接