使用Angular Material学习“适用于任何应用”的组件开发

首先

这篇文章的目标是针对那些已经基本掌握了Angular功能,但却无法制作出出色的用户界面的人群,致力于提供初级和中级的桥梁。

本文所讲述的主题分为两个部分 – 设计出炫酷的导航栏,以及制作“忙碌转动”的加载动画。

这些是许多应用程序中常见的组件,但正因为如此,其中有一些方面很难欺骗,并且我自己在探索最佳实践的过程中不断尝试和犯错。

本文的前半是相对简单的内容,后半则涉及稍微有难度的内容,我努力着为广大读者提供尽可能广泛的乐趣。由于都涉及实用的主题,希望您能够读完,并感到满意。

目标读者

    • Angularの基本的な機能をある程度知っている

 

    • Angular CLIのコマンドがちょっと使える

 

    • Angular v17の構文をキャッチアップしたい

 

    • Angular Materialのカスタマイズ方法を知りたい

 

    ちょっとかっこいいコンポーネントの作り方を知りたい

软件开发环境

各种工具的版本假定如下所示。

Node.js: 20.10.0 (LTS)

npm: 10.2.4

Angular CLI: 17.0.5

OS: Windows 11

免责事项

本文仅为提供信息而存在。
作者对基于内容的实施和运营不承担任何责任,包括因此而导致的任何损失。

准备运动

创建项目(工作空间)

让我们在命令提示符中输入以下内容,创建一个名为sample的项目。

> ng new sample --routing --standalone --style scss --ssr false --skip-tests

在ng new命令中,有几个选项可以使用,但在这里我们指定了以下选项。有关其他选项,请参阅Angular CLI文档。

オプション説明--routingルーティング(ページ遷移)を有効化します。--standaloneNgModuleに依存しない「スタンドアロンコンポーネント」によるアプリケーションのテンプレートを生成します。--styleアプリケーションで利用するスタイルファイルの種類を指定します。cssscsssasslessのいずれかが指定できます。今回はscssを指定しました。--ssrサーバサイドレンダリングを有効化するかどうかを指定できます。今回はサーバサイドレンダリングは有効化しません。--skip-testsユニットテストコードが生成されないようにします。

当执行先前的命令时,将会生成以下的模板。

可能的中文翻译选项:
对于习惯于往常的Angular的人来说,可能会感到不适应,因为生成的文件中不再包含app.module.ts和app-routing.module.ts等NgModule。这是因为通过推出Angular v15的“独立组件”,NgModule不再是必需的。以前,每次添加外部模块引用或组件时都需要编辑NgModule,导致多个开发成员频繁编辑同一文件,容易出现竞争和合并冲突。这些问题已经通过独立组件得到了相当大的改善。

如果项目创建成功,请将其移动到示例文件夹中。

> cd sample

安裝Angular Material

接下来,我们将安装Angular Material1。

请确保当前目录为sample,并输入以下命令。

> ng add @angular/material

在安装过程中会出现一些问题,但是如果你不清楚的话,可以选择默认回答2,没有问题。

但是,对于“请选择一个预设的主题名称,或者选择“自定义”以自定义主题”的问题,我们建议您像下图一样选择“自定义”。

安装成功后,会显示“Packages installed successfully”(如下图红色区域),然后会自动更新一些文件。

主题的定制化

选择Custom选项后,您可以自定义应用程序的主题(颜色)。

让我们这次尝试使用外部工具Material Design调色板生成器来创建主题。

请访问 http://mcg.mbitson.com/,选择您喜欢的配色,创建称为”调色板”的配色模式。

基于每个调色板,选择一种基本颜色,会自动创建基于该颜色的明暗变化的多样化选项。在这里,我们以primary、accent和warn为例,创建了三个调色板,并分别选择了#04127C、#A0D8AD和#BE375A作为它们的基色。

完成调色板后,将其应用到Angular项目中。

点击工具的右上角的 “↓” 按钮后,将会显示如下图所示的 “Output Formats” 对话框,然后从侧边菜单中选择 “ANGULAR JS 2 (MATERIAL 2)”,最后点击对话框右上角的 “COPY” 按钮。

现在,调色板的代码已经以SCSS格式复制到剪贴板中了。

接下来,在Angular项目的assets文件夹下创建一个名为palette.scss的新文件,将复制的调色板全部粘贴并保存覆盖。

以下是复制并粘贴的 “palette.scss” 的示例图(在这个编辑器中,为了能够完整显示,我进行了分割显示,但事实上,它是一个整体文件)。

最后,为了将创建的调色板应用到Angular项目中,我们需要按照以下方式编辑styles.scss文件。

  
  // Custom Theming for Angular Material
  // For more information: https://material.angular.io/guide/theming
  @use '@angular/material' as mat;
  // Plus imports for other components in your app.
+ @use "./assets/palette" as palette;
  
  // 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/
- $sample-primary: mat.define-palette(mat.$indigo-palette);
+ $sample-primary: mat.define-palette(palette.$md-primary);

- $sample-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
+ $sample-accent: mat.define-palette(palette.$md-accent);
  
  // The warn palette is optional (defaults to red).
- $sample-warn: mat.define-palette(mat.$red-palette);
+ $sample-warn: mat.define-palette(palette.$md-warn);
  
  // Create the theme object. A theme consists of configurations for individual
  // theming systems such as "color" or "typography".
  $sample-theme: mat.define-light-theme((
    color: (
      primary: $sample-primary,
      accent: $sample-accent,
      warn: $sample-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 mat.all-component-themes($sample-theme);
  
  /* You can add global styles to this file, and also import other style files */
  
  html, body { height: 100%; }
  body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }

准备运动已经完成,辛苦了。

设计一个漂亮的导航栏

接下来,我们将以之前介绍的Angular Material作为实践示例,来构建一个导航栏。

在展示完整形式之前,任何人都可以轻松地创建这种互动体验。

在Angular Material中,有一个名为”Schematics”的自动代码生成功能,可以帮助我们建立一个相当完整的用户界面,而几乎不需要编写太多的代码。关于Schematics的详细信息可以参考Angular Material的文档。

生成导航栏代码的命令如下所示。

> ng generate @angular/material:navigation navigation

执行上述命令后,将在navigation文件夹下自动生成三个文件。

从这里开始,我们将对现有的源代码进行修改,以便在屏幕上显示我们创建的导航。

我们首先需要从 app.component.ts 中引用导航,可以像下面这样添加导入:

  import { Component } from '@angular/core';
  import { CommonModule } from '@angular/common';
  import { RouterOutlet } from '@angular/router';
+ import { NavigationComponent } from './navigation/navigation.component';
  
  @Component({
    selector: 'app-root',
    standalone: true,
-   imports: [CommonModule, RouterOutlet],
+   imports: [CommonModule, RouterOutlet, NavigationComponent],
    templateUrl: './app.component.html',
    styleUrl: './app.component.scss'
  })
  export class AppComponent {
-   title = 'sample';
  }

再者,将app.component.html替换为以下内容3。这样,导航就被放置在了app.component.html的最上方。

<app-navigation>
  <router-outlet></router-outlet>
</app-navigation>

接下来,打开navigation.component.html文件,并进行以下修改4。

  <mat-sidenav-container class="sidenav-container">
    <mat-sidenav #drawer class="sidenav" fixedInViewport
        [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
        [mode]="(isHandset$ | async) ? 'over' : 'side'"
        [opened]="(isHandset$ | async) === false">
      <mat-toolbar>Menu</mat-toolbar>
      <mat-nav-list>
        <a mat-list-item href="#">Link 1</a>
        <a mat-list-item href="#">Link 2</a>
        <a mat-list-item href="#">Link 3</a>
      </mat-nav-list>
    </mat-sidenav>
    <mat-sidenav-content>
      <mat-toolbar color="primary">
        @if (isHandset$ | async) {
          <button
            type="button"
            aria-label="Toggle sidenav"
            mat-icon-button
            (click)="drawer.toggle()">
            <mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
          </button>
        }
        <span>sample</span>
      </mat-toolbar>
      <!-- Add Content Here -->
+     <div class="main-container">
+       <ng-content></ng-content>
+     </div>
    </mat-sidenav-content>
  </mat-sidenav-container>

到目前为止,最基本的导航栏已经完成了。

在Angular v17中,传统的*ngIf结构指令被@if语法取代。类似地,*ngSwitch被替换为@switch,*ngFor被替换为@for(传统的结构指令仍然可用)。
需要注意的是,由于这个规范更改的影响,@、{和}等字符在模板中具有特殊意义,因此无法直接使用这些字符,必须进行转义。@转义为@,{转义为{,}转义为}。

确认行动

为了确认到目前为止的工作内容,让我们在浏览器中检查一下页面的外观。

输入以下命令到命令提示符中,Angular项目将被构建并启动浏览器以进行验证。

> ng serve --open

执行结果将会是下面这样的感觉。

这个本身还可以,但老实说,如果要集成到销售系统中,外观有点简陋。我们将通过专业技术将其改进为更美观的用户界面。

问题1:导航栏被滚动条遮挡。

当我们意识到应用程序的内容真正变得丰富时,我们会发现由Schematics生成的导航栏与滚动条相比相形见绌(见下图红框)。

希望页面顶部固定位置的导航栏在滚动时可以超越页面内容,因此我们想要调整滚动条的上端位置,以避免与下面所示的滚动条重叠。

在这种情况下,首先要对navigation.component.scss进行以下编辑。

  .sidenav-container {
    height: 100%;
  }
  
  .sidenav {
    width: 200px;
  }
  
  .sidenav .mat-toolbar {
    background: inherit;
  }
  
  .mat-toolbar.mat-primary {
    position: sticky;
    top: 0;
    z-index: 1;
  }
  
+ mat-sidenav-content {
+   height: 100%;
+   display: grid;
+   grid-template-rows: max-content 1fr;
+ }
  
+ .main-container {
+   overflow: auto;
+ }

此外,为了视觉上显示导航栏比其他组件更加突出,我们可以轻松地添加一些阴影。

在Angular Material中,定义了CSS类mat-elevation-z0~mat-elevation-z24用于添加阴影效果,通过将其应用于导航栏可以产生立体的视觉效果(在这里我们选择指定mat-elevation-z8以突出效果)。

  <mat-sidenav-container class="sidenav-container">
    <mat-sidenav #drawer class="sidenav" fixedInViewport
        [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
        [mode]="(isHandset$ | async) ? 'over' : 'side'"
        [opened]="(isHandset$ | async) === false">
      <mat-toolbar>Menu</mat-toolbar>
      <mat-nav-list>
        <a mat-list-item href="#">Link 1</a>
        <a mat-list-item href="#">Link 2</a>
        <a mat-list-item href="#">Link 3</a>
      </mat-nav-list>
    </mat-sidenav>
    <mat-sidenav-content>
-     <mat-toolbar color="primary">
+     <mat-toolbar color="primary" class="mat-elevation-z8">
        @if (isHandset$ | async) {
          <button
            type="button"
            aria-label="Toggle sidenav"
            mat-icon-button
            (click)="drawer.toggle()">
            <mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
          </button>
        }
        <span>sample</span>
      </mat-toolbar>
      <!-- Add Content Here -->
      <div class="main-container">
        <ng-content></ng-content>
      </div>
    </mat-sidenav-content>
  </mat-sidenav-container>

执行结果如下所示。导航栏的存在感更加突出。

問題2: 无法关闭侧边菜单。

使用Schematics自动生成导航栏时,侧边菜单(如下图红框所示)也会一同构建。

然而,这个侧边菜单有一些特色,不一定非常方便使用。

例如,如果在常见的个人电脑浏览器中打开应用程序,侧边菜单会被强制显示,并且没有关闭的方法。

然而,在通常情况下,可能会有一些需要将菜单隐藏起来,并根据需要进行互动显示或隐藏的情况。

因此,在本节中,我们将修改导航栏以实现自由展开和折叠的侧边菜单。只需要修改模板中的三个地方就可以实现这一功能。

每个修改的内容都大致如下。

    1. 将启动时的初始状态设置为菜单隐藏

 

    1. 在菜单的右上方放置一个×按钮

 

    1. → 这样就可以关闭菜单

 

    1. 修改显示菜单的三条线按钮的显示条件

 

    → 其实原本为了在智能手机上打开菜单而编码了按钮,现在只需修改条件使其在电脑上也能使用。
  <mat-sidenav-container class="sidenav-container">
    <mat-sidenav #drawer class="sidenav" fixedInViewport
        [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
        [mode]="(isHandset$ | async) ? 'over' : 'side'"
-       [opened]="(isHandset$ | async) === false">
+       [opened]="false">

-     <mat-toolbar>Menu</mat-toolbar>
+     <mat-toolbar>Menu
+       <span style="flex: 1 1 auto;"></span>
+         <button mat-icon-button (click)="drawer.close()">
+         <mat-icon>close</mat-icon>
+       </button>
+     </mat-toolbar>
    
      <mat-nav-list>
        <a mat-list-item href="#">Link 1</a>
        <a mat-list-item href="#">Link 2</a>
        <a mat-list-item href="#">Link 3</a>
      </mat-nav-list>
    </mat-sidenav>
    <mat-sidenav-content>
      <mat-toolbar color="primary" class="mat-elevation-z8">

-       @if (isHandset$ | async) {
+       @if (!drawer.opened) {
          <button
            type="button"
            aria-label="Toggle sidenav"
            mat-icon-button
            (click)="drawer.toggle()">
            <mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
          </button>
        }
        <span>sample</span>
      </mat-toolbar>
      <!-- Add Content Here -->
      <div class="main-container">
        <ng-content></ng-content>
      </div>
    </mat-sidenav-content>
  </mat-sidenav-container>

通过这个修正,导航菜单的左侧将显示一个用于打开侧边菜单的三条横线按钮。

另外,现在在侧边菜单的右上方添加了一个“X”按钮来关闭菜单。

通过这样做,折叠菜单可以充分利用应用程序的宽度空间。

問題3: 导航系统太普通了。

这是一个个人喜好的问题,但是例如在引入系统的企业中加入他们的主题颜色或标志,仅仅这样就能显著增加独特性。

要进行这个改造,需要进行一些准备工作。首先,准备一个合适的图片(例如系统的标志)并将其放置在assets文件夹下。文件夹结构如下所示。

接下来,我们要为导航栏设置渐变的背景色。在 navigation.component.scss 文件中,我们需要添加一个新的类。

  .sidenav-container {
    height: 100%;
  }
  
  .sidenav {
    width: 200px;
  }
  
  .sidenav .mat-toolbar {
    background: inherit;
  }
  
  .mat-toolbar.mat-primary {
    position: sticky;
    top: 0;
    z-index: 1;
  }
  
  mat-sidenav-content {
    height: 100%;
    display: grid;
    grid-template-rows: max-content 1fr;
  }
  
  .main-container {
    overflow: auto;
  }
  
+ .toolbar-background {
+   background: linear-gradient(82deg, #04127C 35%, #65B3A4 90%, #A0D8AD 100%);
+ }

最后,我们将修改模板。在导航栏的样式中,设置之前提到的CSS类,并放置系统的标志。

  <mat-sidenav-container class="sidenav-container">
    <mat-sidenav #drawer class="sidenav" fixedInViewport
        [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
        [mode]="(isHandset$ | async) ? 'over' : 'side'"
        [opened]="false">
      <mat-toolbar>Menu
        <span style="flex: 1 1 auto;"></span>
          <button mat-icon-button (click)="drawer.close()">
          <mat-icon>close</mat-icon>
        </button>
      </mat-toolbar>
      <mat-nav-list>
        <a mat-list-item href="#">Link 1</a>
        <a mat-list-item href="#">Link 2</a>
        <a mat-list-item href="#">Link 3</a>
      </mat-nav-list>
    </mat-sidenav>
    <mat-sidenav-content>
-     <mat-toolbar color="primary" class="mat-elevation-z8">
+     <mat-toolbar color="primary" class="mat-elevation-z8 toolbar-background">
+       <mat-toolbar-row>
          @if (!drawer.opened) {
            <button
              type="button"
              aria-label="Toggle sidenav"
              mat-icon-button
              (click)="drawer.toggle()">
              <mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
            </button>
          }
-         <span>sample</span>
+         <img src="/assets/logo.png" style="height: 1rem;">
+         <span style="flex: 1 1 auto;"></span>
+         <button mat-button class="mat-headline-4">
+           <mat-icon>account_circle</mat-icon>
+           日電 太郎
+         </button>
+       </mat-toolbar-row>
      </mat-toolbar>
      <!-- Add Content Here -->
      <div class="main-container">
        <ng-content></ng-content>
      </div>
    </mat-sidenav-content>
  </mat-sidenav-container>

执行到这一步的内容,结果如下。外观看起来相当不错。关于导航栏,目前暂且就这样吧。

只需稍微改动一点点,就能展现出如此独特性,因此在制作模型等时,尝试运用这个方法可能会让客户感到高兴。

生产装载部件

在这里,我们之前只是对自动生成的代码进行了一些小小的修改,接下来我们要进入正式的阶段了,即”本気ガチ篇”。我们将开始创建用于在数据加载期间让用户感到愉悦的UI组件,俗称”ぐるぐる”。

在需要进行耗时的搜索处理等操作时,如果能够巧妙地利用,可以减轻用户的感知压力,因此在关键时刻提前准备好非常有用。

这个部件看起来似乎很简单,但实际上是借助Angular CDK的遮罩效果和动画模块精心制作的复杂组件。

在设计中,我们不仅关注了外观的表现,还特别注重了产品的易用性和通用性作为组件的方面。

那么,让我们立即来看一下制作方法。

> ng generate component loading
> ng generate service loading/loading

执行上述命令后,将在loading文件夹下生成组件和服务的集合。 这些将成为组件的主体。

下一步,我们需要进行必要的设置才能进行叠加。

请在styles.scss文件的“/* 您可以在此文件中添加全局样式,并导入其他样式文件 */”注释的下一行添加以下内容:

@import '@angular/cdk/overlay-prebuilt.css';

接下来,让我们来写一个控制覆盖组件显示/隐藏的服务loading.service。

实现show()和hide()这两个方法作为服务的公开方法,并在每个方法被调用时执行更改旋转状态的逻辑。

由于Guruguru中有动画显示,所以不会立即切换显示/隐藏,而是在出现时有一个持续100毫秒的“柔和过渡”,在消失时有一个持续1000毫秒的“柔和过渡”。为了防止在这个短暂的时间内连续调用show()和hide()时发生奇怪的事情,在内部进行了相当繁琐的控制。

import { Overlay } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { Injectable, inject } from '@angular/core';
import { LoadingComponent } from './loading.component';

@Injectable({ providedIn: 'root' })
export class LoadingService {
  // 画面全体を覆うようにオーバレイを作成
  private readonly overlay = inject(Overlay);
  private readonly overlayRef = this.overlay.create({ height: '100%', width: '100%' });
  private readonly duration = 1000;
  
  private _visible: boolean = false;
  private _timeoutId?: any;

  // 可視状態フラグ
  get visible() {
    return this._visible;
  }

  // ぐるぐるをオーバレイ表示する
  show() {
    if (!this._timeoutId) {
      clearTimeout(this._timeoutId);
      this.overlayRef.detach();
      this._timeoutId = undefined;
    }
    this._visible = true;
    this.overlayRef.attach(new ComponentPortal(LoadingComponent));
  }

  // ぐるぐるを非表示にする
  hide() {
    this._visible = false;
    this._timeoutId = this._timeoutId || setTimeout(() => {
      this.overlayRef.detach();
      this._timeoutId = undefined;
    }, this.duration);
  }
}

接下来,我们来编写用于显示「轮转」效果的SCSS代码。我们会将组件的宽度和高度都设置为100%,以覆盖整个显示区域,并将「轮转」组件居中放置在其中。

另外,为了突显旋转的感觉,通过让背景变模糊来制造出远近感。

:host {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: none;
}

.main-container {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: none;

  /* 背景をぼかす */
  backdrop-filter: saturate(25%) blur(5px);
  background-color: rgba(255, 255, 255, 0.3);
  
  /* コンポーネントを上下左右中心に配置する */
  display: flex;
  justify-content: center;
  align-items: center;
  user-select: none;
}

接下来是组件的实现。在这里导入的MatProgressSpinnerModule是”转圈圈”的真正身份。

同样,在“转转”显示/隐藏切换的时机触发一个细微的淡入/淡出的动画也在此定义。

在用户的视角下,由于具有淡入淡出效果,使突然感减轻,所以推荐不仅仅限于此的适度动画。

import { Component, inject } from '@angular/core';
import { animate, style, transition, trigger } from "@angular/animations";
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { LoadingService } from './loading.service';

@Component({
  selector: 'app-loading',
  standalone: true,
  imports: [MatProgressSpinnerModule],
  templateUrl: './loading.component.html',
  styleUrl: './loading.component.scss',
  animations: [
    trigger('foreTrigger', [
      transition(':enter', [
        style({ opacity: 0 }),
        animate('100ms', style({ opacity: 1 })),
      ]),
      transition(':leave', [
        style({ opacity: '*' }),
        animate('1000ms', style({ opacity: 0 })),
      ]),
    ])
  ]
})
export class LoadingComponent {
  readonly loading = inject(LoadingService);
}

最后,是模板的标记语言。我们使用@if来切换显示/隐藏,并将动画触发器绑定到该切换的时机上,以实现淡入淡出效果。

@if(loading.visible) {
  <div @foreTrigger class="main-container">
    <div>
      <mat-spinner strokeWidth="15"></mat-spinner>
    </div>
  </div>
}

到目前为止,我们已经完成了旋转部件。虽然部件内部结构错综复杂,但相应地,使用方法非常简单。

转动零件的使用方法

让我们现在开始看一下如何正确使用这些旋转零件。

主要是以组件为例简单解释如下:

    1. 可以使用DI将LoadingService注入(无论是通过inject()函数还是构造函数参数)

调用LoadingService#show()会显示一个旋转的图形

调用LoadingService#hide()会隐藏旋转的图形

只是这样而已。为了保险起见,下面给出一个伪代码示例。非常简单对吧。

import { Component } from '@angular/core';
import { LoadingService } from './loading/loading.service';
import { inject } from '@angular/core';

@Component({ /* 略 */ })
export class AppComponent {

  // LoadingServiceをインジェクト
  private readonly loading = inject(LoadingService);

    /* 中略 */
  
    // ぐるぐるを表示させたいとき
    this.loading.show();

    // ぐるぐるを非表示にしたいとき
    this.loading.hide();
}

总结

你觉得怎么样呢?

在这次活动中,我们使用Angular的新功能和Angular Material,介绍了如何开发可以在任何应用中都能发挥作用的组件。

希望让更多的人了解变得更加强大的Angular版本17以及仅需稍加定制便能大幅改进的Angular Material的魅力。

我希望继续从前端到后端为大家介绍有趣的小知识。

非常感谢您阅读到最后。

Angular Material是Angular官方的用户界面组件库。通过引入这个库,可以轻松构建一致的用户界面。↩按下回车键而不输入任何内容时,将自动返回默认回答。↩

请删除app.component.html中原有的所有源代码,并用本文中示例的源代码进行替换。↩

由于接下来的说明,现在先指定一个叫做main-container的CSS类,但请暂时不用关心它。↩

然而,如果滥用的话反而会让人讨厌,所以要注意不要使用过度。↩

当在实际的项目中使用时,如果过度模糊化会显得很做作,而且还会降低前端性能,所以要适度调整。↩

bannerAds