ASP.NET Core Blazor CSS 隔离

作者:Dave Brock

本文介绍 CSS 隔离如何将 CSS 范围限定到 Razor 组件,以简化 CSS 并避免与其他组件或库发生冲突。

将 CSS 样式隔离到各个页面、视图和组件以减少或避免:

  • 依赖难以维护的全局样式。
  • 嵌套内容中的样式冲突。

启用 CSS 隔离

若要定义组件特定的样式,请在相同文件夹中创建一个 .razor.css 文件,该文件与组件的 .razor 文件的名称相匹配。 .razor.css 文件是限定范围的 CSS 文件。

对于 Example.razor 文件中的 Example 组件,请随组件一起创建一个名为 Example.razor.css 的文件。 Example.razor.css 文件必须驻留在与 Example 组件 (Example.razor) 相同的文件夹中。 文件的“Example”基名称不区分大小写。

Pages/Example.razor:

@page "/example"

<h1>Scoped CSS Example</h1>

Pages/Example.razor.css:

h1 { 
    color: brown;
    font-family: Tahoma, Geneva, Verdana, sans-serif;
}

Example.razor.css 中定义的样式仅应用于 Example 组件的呈现输出。 CSS 隔离适用于匹配的 Razor 文件中的 HTML 元素。 在应用的其他位置定义的任何 h1 CSS 声明都不会与 Example 组件的样式冲突。

注意

为了保证发生捆绑时的样式隔离,不支持在 Razor 代码块中导入 CSS。

CSS 隔离捆绑

CSS 隔离在生成时发生。 Blazor 会重写 CSS 选择器以匹配组件呈现的标记。 重写的 CSS 样式被作为静态资产捆绑和生成。 在 <head> 标记(<head> 内容的位置)中引用表样式。 默认情况下,以下 <link> 元素将添加到从 Blazor 项目模板创建的应用,其中占位符 {ASSEMBLY NAME} 是项目的程序集名称:

<link href="{ASSEMBLY NAME}.styles.css" rel="stylesheet">

以下示例来自托管的 Blazor WebAssemblyClient 应用。 应用的程序集名称为 BlazorSample.Client,通过“托管”选项(使用 .NET CLI 或利用 Visual Studio 的“ASP.NET Core 托管”复选框的 -ho|--hosted 选项)创建项目时,Blazor WebAssembly 项目模板会添加 <link>

<link href="BlazorSample.Client.styles.css" rel="stylesheet">

在捆绑的文件中,每个组件都与范围标识符关联。 对于每个具有样式的组件,HTML 属性追加有格式 b-{STRING},其中 {STRING} 占位符是框架生成的十个字符的字符串。 标识符对每个应用都是唯一的。 在呈现的 Counter 组件中,Blazor 将范围标识符追加到 h1 元素:

<h1 b-3xxtam6d07>

{ASSEMBLY NAME}.styles.css 文件使用范围标识符将样式声明及其组件分为一组。 下面的示例提供了前面 <h1> 元素的样式:

/* /Pages/Counter.razor.rz.scp.css */
h1[b-3xxtam6d07] {
    color: brown;
}

在生成时,会使用约定 obj/{CONFIGURATION}/{TARGET FRAMEWORK}/scopedcss/projectbundle/{ASSEMBLY NAME}.bundle.scp.css 创建项目捆绑包,其中占位符为:

  • {CONFIGURATION}:应用的生成配置(例如 DebugRelease)。
  • {TARGET FRAMEWORK}:目标框架(例如 net6.0)。
  • {ASSEMBLY NAME}:应用的程序集名称(例如 BlazorSample)。

子组件支持

默认情况下,CSS 隔离仅应用于与 {COMPONENT NAME}.razor.css 格式关联的组件,其中占位符 {COMPONENT NAME} 通常是组件名称。 若要对子组件应用更改,请对父组件的 .razor.css 文件中的任何后代元素使用 ::deeppseudo-element::deep pseudo-element 会选择属于元素生成范围标识符后代的元素。

下面的示例演示了名为 Parent 的父组件和名为 Child 的子组件。

Pages/Parent.razor:

@page "/parent"

<div>
    <h1>Parent component</h1>

    <Child />
</div>

Shared/Child.razor:

<h1>Child Component</h1>

使用 ::deep pseudo-element 在 Parent.razor.css 中更新 h1 声明,以表示 h1 样式声明必须应用于父组件及其子组件。

Pages/Parent.razor.css:

::deep h1 { 
    color: red;
}

h1 样式现在将应用于 ParentChild 组件,你无需为子组件创建单独的限定范围 CSS 文件。

::deep pseudo-element 仅适用于后代元素。 以下标记会按预期将 h1 样式应用于组件。 父组件的范围标识符应用于 div 元素,这样浏览器便知道从父组件继承样式。

Pages/Parent.razor:

<div>
    <h1>Parent</h1>

    <Child />
</div>

但是,排除 div 元素将删除后代关系。 在下面的示例中,样式不应用于子组件。

Pages/Parent.razor:

<h1>Parent</h1>

<Child />

::deep pseudo-element 会影响范围特性应用于规则的位置。 在限定范围的 CSS 文件中定义 CSS 规则时,范围默认应用于最右侧的元素。 例如:div > a 转换为 div > a[b-{STRING}],其中 {STRING} 占位符是框架生成的由十个字符组成的字符串(例如,b-3xxtam6d07)。 如果希望规则应用于其他选择器,则 ::deep pseudo-element 允许你执行此操作。 例如,div ::deep > a 转换为 div[b-{STRING}] > a(例如,div[b-3xxtam6d07] > a)。

::deep pseudo-element 附加到任何 HTML 元素的功能使你能够创建限定范围的 CSS 样式,这些样式会影响当你可以确定呈现的 HTML 标记的结构时其他组件呈现的元素。 对于呈现另一个组件内的超链接标记 (<a>) 的组件,确保将组件包装在 div(或任何其他元素)中,并使用规则 ::deep > a 创建仅在父组件呈现时应用于该组件的样式。

重要

限定范围的 CSS 仅适用于 HTML 元素,不适用于 Razor 组件或标记帮助程序,包括应用了标记帮助程序的元素,例如 <input asp-for="..." />

CSS 预处理器支持

利用 CSS 预处理器的变量、嵌套、模块、混合和继承等功能,可有效改进 CSS 开发。 虽然 CSS 隔离并不原生支持 CSS 预处理器(如 Sass 或 Less),但只要在生成过程中 Blazor 重写 CSS 选择器的步骤之前进行预处理器编译,就可以无缝集成 CSS 预处理器。 例如,使用 Visual Studio 将现有预处理器编译配置为 Visual Studio 任务运行程序资源管理器中的“生成前”任务。

许多第三方 NuGet 包(如 Delegate.SassBuilder)都可以在生成过程开始时编译 SASS/SCSS 文件,再进行 CSS 隔离,而无需其他配置。

CSS 隔离配置

CSS 隔离开箱即用,也可在某些高级场景(例如依赖于现有工具或工作流)下进行配置。

自定义范围标识符格式

默认情况下,范围标识符使用格式 b-{STRING},其中 {STRING} 占位符是框架生成的十个字符的字符串。 若要自定义范围标识符格式,请将项目文件更新为所需模式:

<ItemGroup>
  <None Update="Pages/Example.razor.css" CssScope="custom-scope-identifier" />
</ItemGroup>

在上面的示例中,为 Example.razor.css 生成的 CSS 将其范围标识符从 b-{STRING} 更改为了 custom-scope-identifier

使用范围标识符来实现与限定范围的 CSS 文件的继承。 在下面的项目文件示例中,BaseComponent.razor.css 文件包含跨组件的通用样式。 DerivedComponent.razor.css 文件继承了这些样式。

<ItemGroup>
  <None Update="Pages/BaseComponent.razor.css" CssScope="custom-scope-identifier" />
  <None Update="Pages/DerivedComponent.razor.css" CssScope="custom-scope-identifier" />
</ItemGroup>

使用通配符 (*) 运算符跨多个文件共享范围标识符:

<ItemGroup>
  <None Update="Pages/*.razor.css" CssScope="custom-scope-identifier" />
</ItemGroup>

更改静态 Web 资产的基路径

scoped.styles.css 文件在应用的根目录生成。 在项目文件中,请使用 <StaticWebAssetBasePath> 属性来更改默认路径。 下面的示例将 scoped.styles.css 文件以及应用的其余资产放在 _content 路径:

<PropertyGroup>
  <StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>

禁用自动捆绑

若要禁用 Blazor 在运行时发布和加载限定范围的文件,请使用 DisableScopedCssBundling 属性。 使用此属性时,意味着将由其他工具或进程从 obj 目录中捕获隔离的 CSS 文件,并在运行时发布和加载这些文件:

<PropertyGroup>
  <DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>

禁用 CSS 隔离

通过在应用的项目文件中将 <ScopedCssEnabled> 属性设置为 false 来禁用项目的 CSS 隔离:

<ScopedCssEnabled>false</ScopedCssEnabled>

Razor 类库 (RCL) 支持

NuGet 包或 Razor 类库 (RCL) 中组件的独立样式会自动捆绑:

  • 应用使用 CSS 导入来引用 RCL 的捆绑样式。 对于名为 ClassLib 的类库和具有 BlazorSample.styles.css 样式表的 Blazor 应用,RCL 的样式表会导入到应用样式表的顶部:

    @import '_content/ClassLib/ClassLib.bundle.scp.css';
    
  • RCL 的捆绑样式不会作为使用该样式的应用的静态 Web 资产发布。

有关 RCL 的详细信息,请参阅以下文章:

其他资源

CSS 隔离通过防止对全局样式的依赖来简化应用的 CSS 占用情况,帮助避免组件和库的样式冲突。

启用 CSS 隔离

若要定义组件特定的样式,请在相同文件夹中创建一个 .razor.css 文件,该文件与组件的 .razor 文件的名称相匹配。 .razor.css 文件是限定范围的 CSS 文件。

对于 Example.razor 文件中的 Example 组件,请随组件一起创建一个名为 Example.razor.css 的文件。 Example.razor.css 文件必须驻留在与 Example 组件 (Example.razor) 相同的文件夹中。 文件的“Example”基名称不区分大小写。

Pages/Example.razor:

@page "/example"

<h1>Scoped CSS Example</h1>

Pages/Example.razor.css:

h1 { 
    color: brown;
    font-family: Tahoma, Geneva, Verdana, sans-serif;
}

Example.razor.css 中定义的样式仅应用于 Example 组件的呈现输出。 CSS 隔离适用于匹配的 Razor 文件中的 HTML 元素。 在应用的其他位置定义的任何 h1 CSS 声明都不会与 Example 组件的样式冲突。

注意

为了保证发生捆绑时的样式隔离,不支持在 Razor 代码块中导入 CSS。

CSS 隔离捆绑

CSS 隔离在生成时发生。 Blazor 会重写 CSS 选择器以匹配组件呈现的标记。 重写的 CSS 样式被作为静态资产捆绑和生成。 在 wwwroot/index.html<head> 标记 (Blazor WebAssembly) 或 Pages/_Host.cshtml (Blazor Server) 中引用表样式。 默认情况下,以下 <link> 元素将添加到从 Blazor 项目模板创建的应用,其中占位符 {ASSEMBLY NAME} 是项目的程序集名称:

<link href="{ASSEMBLY NAME}.styles.css" rel="stylesheet">

以下示例来自托管的 Blazor WebAssemblyClient 应用。 应用的程序集名称为 BlazorSample.Client,通过“托管”选项(使用 .NET CLI 或利用 Visual Studio 的“ASP.NET Core 托管”复选框的 -ho|--hosted 选项)创建项目时,Blazor WebAssembly 项目模板会添加 <link>

<link href="BlazorSample.Client.styles.css" rel="stylesheet">

在捆绑的文件中,每个组件都与范围标识符关联。 对于每个样式的组件,HTML 属性都会追加 b-<10-character-string> 格式。 标识符是唯一的,每个应用各不相同。 在呈现的 Counter 组件中,Blazor 将范围标识符追加到 h1 元素:

<h1 b-3xxtam6d07>

{ASSEMBLY NAME}.styles.css 文件使用范围标识符将样式声明及其组件分为一组。 下面的示例提供了前面 <h1> 元素的样式:

/* /Pages/Counter.razor.rz.scp.css */
h1[b-3xxtam6d07] {
    color: brown;
}

在生成时,会使用约定 obj/{CONFIGURATION}/{TARGET FRAMEWORK}/scopedcss/projectbundle/{ASSEMBLY NAME}.bundle.scp.css 创建项目捆绑包,其中占位符为:

  • {CONFIGURATION}:应用的生成配置(例如 DebugRelease)。
  • {TARGET FRAMEWORK}:目标框架(例如 net6.0)。
  • {ASSEMBLY NAME}:应用的程序集名称(例如 BlazorSample)。

子组件支持

默认情况下,CSS 隔离仅应用于与 {COMPONENT NAME}.razor.css 格式关联的组件,其中占位符 {COMPONENT NAME} 通常是组件名称。 若要对子组件应用更改,请对父组件的 .razor.css 文件中的任何后代元素使用 ::deeppseudo-element::deep pseudo-element 会选择属于元素生成范围标识符后代的元素。

下面的示例演示了名为 Parent 的父组件和名为 Child 的子组件。

Pages/Parent.razor:

@page "/parent"

<div>
    <h1>Parent component</h1>

    <Child />
</div>

Shared/Child.razor:

<h1>Child Component</h1>

使用 ::deep pseudo-element 在 Parent.razor.css 中更新 h1 声明,以表示 h1 样式声明必须应用于父组件及其子组件。

Pages/Parent.razor.css:

::deep h1 { 
    color: red;
}

h1 样式现在将应用于 ParentChild 组件,你无需为子组件创建单独的限定范围 CSS 文件。

::deep pseudo-element 仅适用于后代元素。 以下标记会按预期将 h1 样式应用于组件。 父组件的范围标识符应用于 div 元素,这样浏览器便知道从父组件继承样式。

Pages/Parent.razor:

<div>
    <h1>Parent</h1>

    <Child />
</div>

但是,排除 div 元素将删除后代关系。 在下面的示例中,样式不应用于子组件。

Pages/Parent.razor:

<h1>Parent</h1>

<Child />

::deep pseudo-element 会影响范围特性应用于规则的位置。 在限定范围的 CSS 文件中定义 CSS 规则时,范围默认应用于最右侧的元素。 例如:div > a 转换为 div > a[b-{STRING}],其中 {STRING} 占位符是框架生成的由十个字符组成的字符串(例如,b-3xxtam6d07)。 如果希望规则应用于其他选择器,则 ::deep pseudo-element 允许你执行此操作。 例如,div ::deep > a 转换为 div[b-{STRING}] > a(例如,div[b-3xxtam6d07] > a)。

::deep pseudo-element 附加到任何 HTML 元素的功能使你能够创建限定范围的 CSS 样式,这些样式会影响当你可以确定呈现的 HTML 标记的结构时其他组件呈现的元素。 对于呈现另一个组件内的超链接标记 (<a>) 的组件,确保将组件包装在 div(或任何其他元素)中,并使用规则 ::deep > a 创建仅在父组件呈现时应用于该组件的样式。

重要

限定范围的 CSS 仅适用于 HTML 元素,不适用于 Razor 组件或标记帮助程序,包括应用了标记帮助程序的元素,例如 <input asp-for="..." />

CSS 预处理器支持

利用 CSS 预处理器的变量、嵌套、模块、混合和继承等功能,可有效改进 CSS 开发。 虽然 CSS 隔离并不原生支持 CSS 预处理器(如 Sass 或 Less),但只要在生成过程中 Blazor 重写 CSS 选择器的步骤之前进行预处理器编译,就可以无缝集成 CSS 预处理器。 例如,使用 Visual Studio 将现有预处理器编译配置为 Visual Studio 任务运行程序资源管理器中的“生成前”任务。

许多第三方 NuGet 包(如 Delegate.SassBuilder)都可以在生成过程开始时编译 SASS/SCSS 文件,再进行 CSS 隔离,而无需其他配置。

CSS 隔离配置

CSS 隔离开箱即用,也可在某些高级场景(例如依赖于现有工具或工作流)下进行配置。

自定义范围标识符格式

默认情况下,范围标识符使用 b-<10-character-string> 格式。 若要自定义范围标识符格式,请将项目文件更新为所需模式:

<ItemGroup>
  <None Update="Pages/Example.razor.css" CssScope="my-custom-scope-identifier" />
</ItemGroup>

在上面的示例中,为 Example.razor.css 生成的 CSS 将其范围标识符从 b-<10-character-string> 更改为了 my-custom-scope-identifier

使用范围标识符来实现与限定范围的 CSS 文件的继承。 在下面的项目文件示例中,BaseComponent.razor.css 文件包含跨组件的通用样式。 DerivedComponent.razor.css 文件继承了这些样式。

<ItemGroup>
  <None Update="Pages/BaseComponent.razor.css" CssScope="my-custom-scope-identifier" />
  <None Update="Pages/DerivedComponent.razor.css" CssScope="my-custom-scope-identifier" />
</ItemGroup>

使用通配符 (*) 运算符跨多个文件共享范围标识符:

<ItemGroup>
  <None Update="Pages/*.razor.css" CssScope="my-custom-scope-identifier" />
</ItemGroup>

更改静态 Web 资产的基路径

scoped.styles.css 文件在应用的根目录生成。 在项目文件中,请使用 <StaticWebAssetBasePath> 属性来更改默认路径。 下面的示例将 scoped.styles.css 文件以及应用的其余资产放在 _content 路径:

<PropertyGroup>
  <StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>

禁用自动捆绑

若要禁用 Blazor 在运行时发布和加载限定范围的文件,请使用 DisableScopedCssBundling 属性。 使用此属性时,意味着将由其他工具或进程从 obj 目录中捕获隔离的 CSS 文件,并在运行时发布和加载这些文件:

<PropertyGroup>
  <DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>

禁用 CSS 隔离

通过在应用的项目文件中将 <ScopedCssEnabled> 属性设置为 false 来禁用项目的 CSS 隔离:

<ScopedCssEnabled>false</ScopedCssEnabled>

Razor 类库 (RCL) 支持

NuGet 包或 Razor 类库 (RCL) 中组件的独立样式会自动捆绑:

  • 应用使用 CSS 导入来引用 RCL 的捆绑样式。 对于名为 ClassLib 的类库和具有 BlazorSample.styles.css 样式表的 Blazor 应用,RCL 的样式表会导入到应用样式表的顶部:

    @import '_content/ClassLib/ClassLib.bundle.scp.css';
    
  • RCL 的捆绑样式不会作为使用该样式的应用的静态 Web 资产发布。

有关 RCL 的详细信息,请参阅以下文章: