获取Angular Material主题颜色方案/调色板以应用于其他元素

38

我正在构建一个应用程序,但我想保持一致的颜色方案,可以通过设置进行更改,因此我正在使用Angular-Material,但我不确定如何在不直接提供使用color="primary"颜色的元素上获取颜色方案,因此我必须尝试找出如何获取我的Material主题使用的颜色/颜色方案。 我希望它在主题更改时改变,例如,我的导航栏将适应主题更改,因为它被设置为

<mat-toolbar color="primary" class="fixed-navbar mat-elevation-z10">

但是Material的网格元素没有使用相同的参数,因此我只能尝试在近似颜色中进行样式设置,或者干脆不匹配它(并且它不会根据主题更改调整),如下所示:

enter image description here

我想让它与主题mat的颜色匹配,这里是主题颜色(并且根据导航栏设置中选择的选项进行更改)。

@import '~@angular/material/theming';

@include mat-core();   
    
$candy-app-primary: mat-palette($mat-red);
$candy-app-accent:  mat-palette($mat-deep-orange, A200, A100, A400);
$candy-app-warn:    mat-palette($mat-red);
$candy-app-theme: mat-dark-theme($candy-app-primary, $candy-app-accent, $candy-app-warn);

// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
.default {
  @include angular-material-theme($candy-app-theme);
}
.light {
  $light-primary: mat-palette($mat-blue, 200,300, 900);
  $light-accent:  mat-palette($mat-light-blue, 600, 100, 800);
  $light-warn:    mat-palette($mat-red, 600);
  $light-theme: mat-dark-theme($light-primary, $light-accent, $light-warn);
  @include angular-material-theme($light-theme);
}
@include angular-material-theme($candy-app-theme);
8个回答

45

我个人将它们放在CSS4变量中,这样我就可以像这样直接使用它们而无需导入:

background: var(--color-primary)

以下是如何设置CSS4变量:

@import '~@angular/material/theming';
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat-core();

// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$app-primary: mat-palette($mat-blue);
$app-accent:  mat-palette($mat-orange);
$app-warn:    mat-palette($mat-red);
$app-success: mat-palette($mat-light-green);

// Create the theme object (a Sass map containing all of the palettes).
$app-theme: mat-light-theme($app-primary, $app-accent, $app-warn);

// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include angular-material-theme($app-theme);

$primary: map-get($app-theme, primary);
$accent: map-get($app-theme, accent);

:root {
  --color-primary: #{mat-color($app-primary)};
  --color-accent: #{mat-color($app-accent)};
  --color-warn: #{mat-color($app-warn)};
  --color-success: #{mat-color($app-success)};
}

现在可以在CSS文件中直接使用颜色,无需导入。

background: var(--color-primary)

1
像这种CSS4的方法。它避免了在每个组件的所有scss文件中包含mat-core()吗? - JoeCool-Avismón
1
@JosepAlacid 是的,它确实这样做。 - Ced
1
太棒了。谢谢。我确认这在普通的.css文件上有效...正是我需要的。 - Dan Ortega
1
太棒了,喜欢它..!! - kushal Baldev

43

我找到了一个很棒的解决方法!!! 我非常兴奋地展示这个方法,因为我已经烦恼了很长时间如何实现它。 首先,将所有的CSS文件更改为SCSS;

对于现有项目

  • 在控制台中运行ng set defaults.styleExt=scss

(ng set似乎已经被淘汰,但您可以查看此链接以获得修复方案,感谢用户@wlyles:get/set已被弃用,改用config命令

  • 将所有现有的.css文件重命名为.scss
  • 手动更改.angular-cli.jsonstyles的文件扩展名,从.css更改为.scss
  • 如果您没有使用像WebStorm Refactor这样的工具进行重命名,那么请手动将所有的styleUrls.css更改为.scss

对于未来的项目

  • 只需在新项目中使用ng new your-project-name --style=scss

  • 对于所有新项目使用scss,请使用ng set defaults.styleExt=scss --global

现在您需要在应用程序根目录中拥有一个theme.scss文件,如下所示: where themes are

现在在你的style.scss文件中添加以下内容(如您所见,我引用了background-color,但您可以将其更改为任何元素以根据自己的喜好设置网站主题):

编辑: 您不需要将此自定义@mixin元素放在您的styles.scss中,您可以将其放在任何一个您的*name*.component.scss中,然后以相同的方式导入和包含它!

@import '~@angular/material/theming';

// Define a custom mixin that takes in the current theme
@mixin theme-color-grabber($theme) {
  // Parse the theme and create variables for each color in the pallete
  $primary: map-get($theme, primary);
  $accent: map-get($theme, accent);
  $warn: map-get($theme, warn);
  // Create theme specfic styles
  .primaryColorBG {
    background-color: mat-color($primary);
  }
  .accentColorBG {
    background-color: mat-color($accent);
  }
  .warnColorBG {
    background-color: mat-color($warn);
  }
}

现在前往您用于主题化Material 2项目的theme.scss文件,如果需要帮助进行主题化,请查看此链接:Material 2 Github - 主题化指南

现在打开您的theme.scss并导入您的style.scss。由于我的theme.scss位于/src/app/theme.scss文件夹的根目录中,因此我必须先退出它才能引用我的/src/styles.scss全局样式文件,代码如下:

@import '../styles';

那么我们必须在所有主题中实际包含我们创建的新自定义@mixin(如果您像我一样有多个主题,因此根据当前选择的主题更改颜色)。

将其包含在实际的 angular-material-theme 包含之前,如下所示:

@include theme-color-grabber($theme);
@include angular-material-theme($theme);

如果你和我一样有任何主题,请将它添加到相同的位置,方法如下:

.light {
  $light-primary: mat-palette($mat-blue, 200,300, 900);
  $light-accent:  mat-palette($mat-light-blue, 600, 100, 800);
  $light-warn:    mat-palette($mat-red, 600);
  $light-theme: mat-dark-theme($light-primary, $light-accent, $light-warn);
  @include theme-color-grabber($light-theme);
  @include angular-material-theme($light-theme);

}

你可以看到我在include上方添加了theme-color-grabber,它是否在主题的上方或下方并不重要,因为它获取的是主题颜色,这才是关键。

我的整个themes.scss文件如下:

@import '~@angular/material/theming';
//We import our custom scss component here
@import '../styles';

@include mat-core();

$theme-primary: mat-palette($mat-red);
$theme-accent:  mat-palette($mat-deep-orange, A200, A100, A400);
$theme-warn:    mat-palette($mat-red);
$theme: mat-dark-theme($theme-primary, $theme-accent, $theme-warn);
//
@include theme-color-grabber($theme);
@include angular-material-theme($theme);
.light {
  $light-primary: mat-palette($mat-blue, 200,300, 900);
  $light-accent:  mat-palette($mat-light-blue, 600, 100, 800);
  $light-warn:    mat-palette($mat-red, 600);
  $light-theme: mat-dark-theme($light-primary, $light-accent, $light-warn);
  @include theme-color-grabber($light-theme);
  @include angular-material-theme($light-theme);

}

最后,我们现在可以在任何地方使用我们的主题颜色作为背景!例如,我可以通过将其类设置为相应的类名来为mat-grid-tile设置“primary”颜色(它不像其他元素(如mat-toolbar)那样采用color =''参数):

编辑:在每个组件的scss文件中,您需要import '<path-to>/theme.scss'以使主题应用于该组件。不要在styles.scss中导入theme.scss,因为这会创建一个导入循环!

<mat-grid-list cols="4" rows="4" rowHeight="100px">
  <mat-grid-tile
    colspan="4"
    rowspan="5"
  class="primaryColorBG">
    <div fxLayout="column" fxLayoutAlign="center center">
      <h1 class="title-font">Callum</h1>
      <h1 class="title-font">Tech</h1>
    </div>
    <p>
      Ambitious and ready to take on the world of Information Technology,<br>
      my love for programming and all things I.T. has not wavered since I first got access<br>
      to my fathers computer at age 9.
    </p>
  </mat-grid-tile>

</mat-grid-list>

最终我们的结果会是这样的!:

红色主题激活 Red theme

蓝色主题激活 enter image description here


3
太棒了,这是可能的! - P.S.
3
太好了,我非常高兴!之前一直让我感到很烦恼,但现在我可以让所有的东西都正常运作了;D - Cacoon
1
我正在创建一个导入循环。所以你说,当你在styles.scss中导入时,主题颜色将可以在任何地方使用。但是我必须在styles.scss中导入我的主题,当我将styles.scss导入到我的主题中时,我遇到了一个导入循环。那么这怎么能行呢? - Florian Leitgeb
1
我所说的“任何地方”是指为每个可视化组件生成的任何单独的scss文件,您不需要将“Theme.scss”导入到style.scss中。 - Cacoon
1
感谢这个出色的指南!但需要注意的是,ng set已经被弃用了。这个问题应该可以解决任何问题。 - wlyles
显示剩余4条评论

5

更新:

这个解决方案的新版本已经发布在这里:

https://github.com/mirismaili/angular-material-dynamic-themes

如果你只需要所问问题的答案,可能最好参考下面的第一个版本的答案。此外,我建议阅读上述存储库文档的这一部分:使用材料主题处理其他元素。但是,如果您想要在下面的视频中看到其他功能,我推荐这种新方法。

Video

感谢StackBlitz


存档答案:

enter image description here

在这里查看 StackBlitz


最重要的部分:

在你的 styles.scss 中(如果有的话,也可以是 themes.scss):

@import '~@angular/material/theming';

@include mat-core();

@mixin define-css-classes($theme) {
    @include angular-material-theme($theme);

    $primary: map-get($theme, primary);
    $accent: map-get($theme, accent);
    $warn: map-get($theme, warn);
    $background: map-get($theme, background);
    $foreground: map-get($theme, foreground);

    // CSS THEME-DEPENDENT-STYLES ARE HERE:
    .theme-dependent-colors {
        background: mat-color($primary);
        color: mat-color($accent);
    }
}

/**
* Define your custom themes in this map. 
* The `key` of each member is the name of CSS class for that theme. 
* To better understand the schema of the map, see `@each` loop below and especially pay attention to `map-has-key()` functions.
*/ 
$app-themes: (
        indigo-pink : (primary-base: $mat-indigo, accent-base: $mat-pink),
        deeppurple-amber: (primary-base: $mat-deep-purple, accent-base: $mat-amber),
        pink-bluegrey : (primary-base: $mat-pink, accent-base: $mat-blue-gray, is-dark: true),
        purple-green : (primary-base: $mat-purple, accent-base: $mat-green, is-dark: true),
);

@each $css-class, $theme in $app-themes {
    $primary: if(map-has-key($theme, primary), map-get($theme, primary), mat-palette(map-get($theme, primary-base)));

    $accent: if(map-has-key($theme, accent), map-get($theme, accent), mat-palette(map-get($theme, accent-base)));

    $warn: if(map-has-key($theme, warn), map-get($theme, warn), mat-palette(
            if(map-has-key($theme, warn-base), map-get($theme, warn-base), $mat-red)
    ));

    .#{$css-class} {
        @include define-css-classes(mat-light-theme($primary, $accent, $warn));
    }

    .#{$css-class}-dark {
        @include define-css-classes(mat-dark-theme($primary, $accent, $warn));
    }

    .theme-primary.#{$css-class} {
        background-color: mat-color($primary);
    }

    ...
}

动态主题更改,使用 TypeScript 中的 setTheme()(请参见此处此处):

import {Component, HostBinding} from '@angular/core';
import {OverlayContainer} from "@angular/cdk/overlay";

const THEME_DARKNESS_SUFFIX = `-dark`;

export class AppComponent {
    @HostBinding('class') activeThemeCssClass: string;
    isThemeDark = false;
    activeTheme: string;

    setTheme(theme: string, darkness: boolean = null) {
        if (darkness === null)
            darkness = this.isThemeDark;
        else if (this.isThemeDark === darkness) {
            if (this.activeTheme === theme) return;
        } else
            this.isThemeDark = darkness;

        this.activeTheme = theme;

        const cssClass = darkness === true ? theme + THEME_DARKNESS_SUFFIX : theme;

        const classList = this.overlayContainer.getContainerElement().classList;
        if (classList.contains(this.activeThemeCssClass))
            classList.replace(this.activeThemeCssClass, cssClass);
        else
            classList.add(cssClass);

        this.activeThemeCssClass = cssClass;
    }

    constructor(overlayContainer: OverlayContainer) {
        this.setThemeClass('indigo-pink', false); // Default theme
    }
}

stackblitz中查看其他内容。


注意:将8个动态材料主题添加到应用程序中(4个亮色+4个深色)会增加构建的styles.css文件大小,我的情况下增加了 ~420 kB(与一个静态材料主题相比)。


哇,这太棒了! - JoeCool-Avismón
2
我必须再次询问:您是否真的需要经历所有这些步骤才能在自定义HTML标记中使用您的材料主题颜色?只有一些“特殊”的材料组件提供了一个“color=”参数,让您将它们的颜色设置为主题(primary、accent等)中的值吗?谢谢。 - Jens Mander
@JensMander;你有读过这个章节吗:使用材料主题为其他元素?如果是的话,但没有找到你想要的答案,请告诉我。 - Mir-Ismaili
2
“再次”是指“尽管讨论似乎已经结束了,但我想添加一个问题,我相信每个人都可以轻松回答,除了我。” @ Mir-Ismaili:感谢提供链接。所以为了能够使用主题颜色,需要花费那么多麻烦。对我来说,这很疯狂和奇怪。如此复杂的框架,然后你必须像30年前那样做事情;-)顺便说一句:问题不在于您需要做什么太困难。问题在于您认为如果出现意外复杂的情况,你认为自己做错了。我简直不能相信。 - Jens Mander

4

有人想在 Angular Material 12+ 中实现 mixin 吗?

theme.scss:

    @use 'sass:map';
    @use '@angular/material' as mat;

    /// Gets the CSS property and it's computed value for both light and dark themes.
    /// @param {String} $property The css property to set. ex: background, color, background-color, border-color etc.
    /// @param {String} $color Theme color. Accepted values are: primary, accent, or warn.
    /// @param {String | Number} $hue The hue from the palette to use. If this is a value between 0 and 1, it will be treated as opacity. Ex values: 500, 500-contrast, darker, darker-contrast
    /// @returns {CssProperty} CSS property with it's computed value for the both light and dark themes.
    @mixin get-theme-color-property($property: null, $color: null, $hue: null) {
      // Get the color config from the theme.
      $light-color-config: mat.get-color-config($light-theme);
      // Get the required color palette from the color-config.
      $light-color-palette: map.get($light-color-config, $color);
    
      // Get the color config from the theme.
      $dark-color-config: mat.get-color-config($dark-theme);
      // Get the required color palette from the color-config.
      $dark-color-palette: map.get($dark-color-config, $color);
      @if $hue != null {
        // Finally get the desired color with the specified hue.
        $light-color: mat.get-color-from-palette($light-color-palette, $hue);
    
        // Finally get the desired color with the specified hue.
        $dark-color: mat.get-color-from-palette($dark-color-palette, $hue);
        & {
          #{$property}: $light-color;
        }
    
        .dark-theme & {
          #{$property}: $dark-color;
        }
      } @else {
        // Finally get the desired color with the specified hue.
        $light-color: mat.get-color-from-palette($light-color-palette);
    
        // Finally get the desired color with the specified hue.
        $dark-color: mat.get-color-from-palette($dark-color-palette);
        & {
          #{$property}: $light-color;
        }
    
        .dark-theme & {
          #{$property}: $dark-color;
        }
      }
    }

用法:

@use '/path/to/theme' as theme;
.example {
  padding: 10px 20px;
  @include theme.get-theme-color-property(background, primary);
  @include theme.get-theme-color-property(color, primary, default-contrast); // or 'lighter-contrast' or 'darker-contrast'
  color: #fff;
}

<mat-toolbar class="example">

3

我是一个完全的新手,也许这种做法不符合良好的实践,对于这个案例没有用处,或者是一种资源消耗的解决方案,但在myangularthemefile.scss中,我创建了以下类:

@import '~@angular/material/theming';
@include mat-core();

...

.matcolorprimary{
    color: mat-color($mitjans-primary)
}

.matcoloraccent {
    color: mat-color($mitjans-accent);
}

.matcolorwarn {
    color: mat-color($mitjans-warn);
}

我将它们添加到组件模板中所需的html元素中。

我认为为背景颜色创建相同的结构会很容易...

对于小项目,这是否可以作为在每个Shadow DOM组件样式表中包含Sass构件的替代方案?


0

如果您想根据主题进行颜色更改,可以在定义主题的每个类中嵌套不同的变量来简单地实现。例如,在我的应用程序中,通过将类分配给应用程序的外部容器来设置主题。因此,在每个“主题类”中嵌套,我可以将相同的变量分配为不同的值。因此,当分配给整个应用程序的“主题类”更改时,我的变量的值也会随之更改:

.app-light-theme {
  @include angular-material-theme($theme);
  * {
     --my-variable-color: yellow;
    }
}

.app-dark-theme {
  @include angular-material-theme($altTheme);
  * {
    --my-variable-color: purple;
  }
}

然后在我的应用程序中的任何CSS文件中,我都可以使用var()函数来使用我的变量:

background-color: var(--my-variable-color);

颜色将根据应用程序设置的主题而改变。

您可以通过在每个主题中设置--primary--accent--warn变量来使用主题的主要、强调和警告颜色。


0
多年来,我一直在为此苦苦挣扎。
感谢之前的答案https://dev59.com/w1YN5IYBdhLWcg3w4bhS#54259083,这里提供了一个Angular 14的实现方法,稍微有些不同,可以直接应用于我的主题(还有深色模式):
@use '@angular/material' as mat;
// ...custom color palettes if necessary...

$light-primary: mat.define-palette($coolGray, 700);
$light-accent: mat.define-palette($indigo, 500);
$light-warn: mat.define-palette($rose, 500);

$theme-light: mat.define-light-theme($light-primary, $light-accent, $light-warn);

$dark-primary: mat.define-palette($fuchsia, 500);
$dark-accent: mat.define-palette($sky, 500);
$dark-warn: mat.define-palette($rose, 500);

$theme-dark: mat.define-dark-theme($dark-primary, $dark-accent, $dark-warn);

.theme-light {
  @include mat.all-component-themes($theme-light);
  --color-primary: #{mat.get-color-from-palette($light-primary)};
  --color-accent: #{mat.get-color-from-palette($light-accent)};
  --color-warn: #{mat.get-color-from-palette($light-warn)};
}

.theme-dark {
  @include mat.all-component-themes($theme-dark);
  --color-primary: #{mat.get-color-from-palette($dark-primary, 500)};
  --color-accent: #{mat.get-color-from-palette($dark-accent, 500)};
  --color-warn: #{mat.get-color-from-palette($dark-warn, 500)};
}

确保主题已应用
 <body class="theme-light" ...

然后你可以在任何scss文件中使用它们。在我的情况下,我使用它们来定义一些有用的类,例如:

.color {
  &--primary {
    background-color: var(--color-primary) !important;
  }

  &--accent {
    background-color: var(--color-accent) !important;
  }

  &--warn {
    background-color: var(--color-warn) !important;
  }
}

.bg-color {
  &--primary {
    background-color: var(--color-primary) !important;
  }

  &--accent {
    background-color: var(--color-accent) !important;
  }

  &--warn {
    background-color: var(--color-warn) !important;
  }
}


0
1. 将应用程序样式规则设置为SASS:
在运行时更新自定义组件主题需要使用@mixin,因此您的应用程序样式规则应为SASS(而不是CSS)。您可以在此处阅读有关如何使用SASS配置Angular-Cli的信息:https://scotch.io/tutorials/using-sass-with-the-angular-cli 2. 为自定义组件定义@mixin
对于每个使用主题颜色的组件,在其.scss文件中创建一个@mixin。对于该组件,请提取所有颜色定义。并将它们移动到@mixin中,如下所示:
// --- file: my-component_1.scss ---
@import '~@angular/material/theming'; // we need to add this so we could use Material functions
@mixin set-theme-component-1($theme)
{
  // Extract whichever individual palettes you need from the theme.
  $primary-palette: map-get($theme, primary);
  $accent-palette:  map-get($theme, accent);
  $warn-palette:    map-get($theme, warn);

  .component-container
  {
    background-color: mat-color($primary-palette); // use the mat-color function to extract the color from the palette
    border-color: mat-color($warn-palette);
  }
}

// Style rules that aren't theme/color related (size, font, etc)
.component-container
{
  width: 100%;
  ...
}
  1. 定义主题文件:
    如果您还没有定义主题文件,您需要定义一个主题文件并调用我们在该文件中定义的@mixin,如下所示:
// --- file: app.theme.scss ---
@import '~@angular/material/theming';
@include mat-core(); // include this only once in your code!!!

// each custom component that uses theme colors will be imported here - we need there @mixin
@import './some-path/some-folder/my-component_1'; // no need to specify .scss suffix

@mixin set-theme($theme) // define a new @mixin that will be invoked each time the theme is changed
{
  @include set-theme-component-1($theme); // invoke the mixin we defined for component_1
  // repeat this for each component that uses theme colors
}

// define your themes:
.theme-light
{
  $light-primary: mat-palette($mat-indigo);
  $light-accent:  mat-palette($mat-pink, A200, A100, A400);
  $light-warn:    mat-palette($mat-red);
  $light-theme:   mat-light-theme($light-primary, $light-accent, $light-warn);

  @include angular-material-theme($light-theme);
  @include set-theme($light-theme); // once the theme was set, invoke the mixin
}

.theme-dark
{
  $dark-primary:  mat-palette($mat-teal, A400);
  $dark-accent:   mat-palette($mat-grey, 800);
  $dark-warn:     mat-palette($mat-red, 700);
  $dark-theme:    mat-dark-theme($dark-primary, $dark-accent, $dark-warn);

  @include angular-material-theme($dark-theme);
  @include set-theme($dark-theme); // once the theme was set, invoke the mixin
}

就是这样 :-)

为了使实时主题工作,您需要实现一些额外的步骤(它们与自定义组件无关,因此我将快速介绍它们)。
- 创建一个ThemeService来保存当前主题。该服务需要更新OverlayContainer(Angular Material使用此容器作为弹出窗口和下拉列表等模态框的背景)。
- 在DOM中的一个根元素中添加主题类(theme-dark或其他任何类)。
- 在DOM中的一个根元素中添加mat-app-background类。这将根据主题更改背景颜色和字体颜色。
- 为了更好的软件设计 - 如果您有很多主题,则拆分主题文件可能是维护目的的好主意。

您可以在这里继续阅读: https://material.angular.io/guide/theming

或者查看他们的 Github 项目:https://github.com/angular/material2/blob/master/src/lib/core/theming/_theming.scss


这个解决方案可行,但是你应该意识到,在全局 styles.scss 中导入组件的 .scss 文件会使所有组件的样式类在应用程序中全局可用:如果你对类名不够小心,这可能会非常混乱。 - Alessandro Prete

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