Angular 加载指示器与遮罩层 | ng-hub-ui-loading
Angular 加载组件,提供 spinner、dots、bars、pulse、ring 五种指示器,支持 inline、overlay 与 fullscreen 三种模式,并附带用于全应用阻塞遮罩的服务。
最后更新 2026年9月16日
概览
团队为何搜索这个库
当某个区块、某个容器或整个应用需要表明正在处理,又不希望每个视图各自手写一套指示器和遮罩时,请使用 ng-hub-ui-loading。
安装
npm install ng-hub-ui-loading跳转到
适用于
- 路由与数据加载
- 表单提交遮罩
- 全应用阻塞式操作
- 带品牌标识的启动画面
- 导航栏下的页面进度条
关于 loading
ng-hub-ui-loading 是一个独立的 Angular 组件,以三种模式呈现不确定的加载状态:`inline` 作为文档流中的区块,`overlay` 绝对定位覆盖其父容器,`fullscreen` 固定于视口。它内置五种纯 CSS 指示器——spinner、dots、bars、pulse 与 ring——并提供三档尺寸、可选的提示文案,以及可选的品牌图片,图片支持 `none`、`spin`、`pulse` 三种动效。`HubLoadingService` 以编程方式驱动全局全屏遮罩,并对并发调用方进行引用计数,因此并行的请求不会互相撤掉遮罩。颜色、尺寸与时长都是 `--hub-loading-*` 自定义属性,另有 `hub-loading-theme()` Sass mixin 用于整体重塑样式;组件默认具备无障碍能力,带有 `role=status`、`aria-live=polite` 与 `aria-busy`,并遵循 `prefers-reduced-motion`。 与之并列,`hub-loading-bar` 用来告诉读者页面本身正在路上——导航栏下的那条细带,可以画在文档流中,挂在已定位的导航栏上,或按 `--hub-loading-bar-offset` 固定到 viewport。`HubLoadingBarService` 同样对调用方计数,留出一段宽限期,让很快完成的工作不会让进度条闪一下,并朝着自己永远到不了的终点推进:只有 `complete()` 可以显示 100%。`provideHubLoadingBarRouter()` 与 `hubLoadingBarInterceptor` 把它接到路由和 HTTP 上,而两者互不相识。
功能指南
内联块
`inline` 之所以是默认模式,是因为一块尚且为空的区域本就该先把内容将要占据的空间留出来:该块参与正常的文档流,因此真实内容到达时不会发生跳动。`message` 渲染在指示器下方,标签之间投影的任何内容再渲染在消息下方——那是放置取消操作,或提示“本次操作比平时更久”的位置。宿主元素带有 `role="status"`、`aria-live="polite"` 与 `aria-busy="true"`,因此屏幕阅读器会在消息出现时读到它,而不会打断正在朗读的内容。
示例:
内联块基础
内联块的三种实用形态:仅指示器、指示器加消息,以及消息下方投影一个取消操作。
The block is announced as role="status" with aria-live="polite", so a screen reader reads the message when it appears without interrupting whatever is being read.
代码
导入:
模板:
组件:
指示器与强调色
库内置五种纯 CSS 指示器——`spinner`、`dots`、`bars`、`pulse` 与 `ring`——因此不会引入任何图片资源或图标字体。三档尺寸预设了 `--hub-loading-size`,而当布局需要这三档之外的尺寸时,该令牌本身仍可单独覆盖。`color` 并非普通的 CSS 颜色:它会经过 `resolveHubAccent()`,把 `primary` 这样的裸名称解析为跟随主题的设计系统令牌,而 hex、`oklch()` 字面量或 `var(...)` 引用则原样使用。
示例:
变体、尺寸与强调色
内置的五种指示器、三档尺寸,以及强调色可以采取的三种写法——语义名称、CSS 颜色字面量与 `var(...)` 引用。
Variants
spinnerdotsbarspulseringSizes
size="sm"size="md"size="lg" Each step is a preset for the --hub-loading-size token. Override that token directly when a layout needs a size the three steps do not cover.
Accents
color="primary"color="success"color="danger"color="#7c3aed"color="var(--hub-sys-color-warning)" A bare name resolves to var(--hub-sys-color-<name>) and tracks the active theme. A hex, an oklch() or a var(...) is used verbatim.
代码
导入:
模板:
组件:
Overlay 与全屏
`overlay` 覆盖的是某一块区域,而不是整个页面。它采用绝对定位,因此由最近的已定位祖先决定被覆盖的范围——请给该容器加上 `position: relative`,否则 overlay 会一路向上逃逸到碰巧被定位的那个祖先。这样限定范围可以让页面其余部分仍能滚动与操作。`fullscreen` 固定于 viewport,正是 `HubLoadingService` 挂载到 `document.body` 上的那一层;它位于所有组件子树之外,因此不会被祖先的 `overflow` 或层叠上下文裁切。除非关闭 `backdrop`,两种模式都会绘制半透明遮罩。
示例:
容器 overlay
借助卡片自身的 `position: relative` 把 overlay 限制在这张卡片内,页面其余部分仍可滚动。切换 `backdrop`,即可看出后台刷新与「使屏幕内容失效的操作」之间的差别。
Order #4821
3 items · delivered 14 Aug 2026
- Subtotal
- 212.00 €
- Shipping
- 9.90 €
- VAT (21%)
- 26.70 €
- Total
- 248.60 €
Turn the backdrop off to keep the content readable underneath — useful when the work is a background refresh rather than something that invalidates what is on screen.
代码
导入:
模板:
组件:
全屏服务
从 TypeScript 驱动的 `HubLoadingService`。两个相互重叠的请求说明了引用计数为何重要:第一次 `hide()` 不会撤下 overlay,因为第二个调用方仍持有引用,而 `update()` 会在进行途中改写消息。
hideAll() after 5 seconds no matter what. Nothing yet.
代码
导入:
模板:
组件:
品牌化与主题化
`image` 会用你自己的标识替换内置指示器,并以 `none`、`spin` 或 `pulse` 赋予动效:`spin` 只在径向对称的标识上才好看,而 `pulse` 能让文字标识保持端正易读。所有视觉相关的设定都在 `--hub-loading-*` 令牌里,声明于 `:where(.hub-loading)`,因此任何祖先都能设置它们——强调色、尺寸、速度、线条粗细、间距、图像尺寸、文本颜色,以及遮罩的色调与模糊。Sass 混入 `hub-loading-theme()` 一次调用即可设定整套令牌,而 `provideHubLoading()` 无需改动任何模板就能为整个应用重设默认值。
示例:
品牌图像
以品牌标识替换内置指示器,并分别配以三种图像动效,让 `spin`、`pulse` 与 `none` 之间的取舍一目了然。
imageAnimation="none"imageAnimation="spin"imageAnimation="pulse" Any URL works — an asset path, an imported file or, as here, an inline data URI. Size the mark with --hub-loading-image-size rather than size, which drives the built-in indicators.
代码
导入:
模板:
组件:
CSS 变量主题化
通过设置在包裹元素上的 `--hub-loading-*` 令牌进行实时主题化,包括仅在 `overlay` 与 `fullscreen` 模式下才起作用的遮罩色调与模糊。
Overlay preview
The backdrop tint and blur only apply in overlay and fullscreen modes. The tint is mixed from --hub-sys-surface-page, so it veils this text with the theme's own surface instead of a fixed colour — drop it to zero and the card stays fully legible underneath.
The same overrides written once, in Sass, are in the CSS tab — that is what hub-loading-theme() is for.
代码
导入:
模板:
组件:
页面进度条
`hub-loading-bar` 是那条细带,用来告诉读者页面本身正在路上——就是导航栏下面的那一条。三个决定让它可信。它对调用方计数:一次导航加上页面发出的三个请求就是四个引用,进度条随最后一个结束,而不是第一个。它在画出任何东西之前先等待:在宽限期内完成的工作根本不会出现进度条,因为一条 40 毫秒的路由闪一下进度条,读起来像故障而不像快。它的自动推进随着填充而减速,并且停在终点之前——这里没有人知道真实的百分比,所以只有 `complete()` 可以显示 100%,因为只有 `complete()` 知道那是真的。用 `mode="overlay"` 放进一个已定位的导航栏,或者当导航栏本身固定时,用 `mode="fixed"` 配合 `--hub-loading-bar-offset`。
示例:
页面进度条
挂在模拟导航栏上的进度条。三条路由展示三种行为:普通的会推进,慢的能看出推进在减速,40 毫秒的那条则根本不画进度条。
Orders
This page arrived without you watching a spinner.
代码
导入:
模板:
组件:
已知与未知的进度
绑定 `progress` 会把进度条交给调用方并公开 `aria-valuenow`;`indeterminate` 改为扫动并隐去数值。第三种状态 `null` 则把它收起来。
Known percentage
[progress]="null"A real value, so the bar announces it. Reaching 100% is the caller's call, not the trickle's.
No percentage at all
indeterminateA sweep instead of a fill, for a stream or a job that reports no stages. No value is announced, because there is none.
Both bars are mode="inline", so each reserves its own row and nothing on the card moves when a bar appears or goes away.
代码
导入:
模板:
组件:
路由与 HTTP 接入
进度条为什么要对调用方计数:一次导航加三个请求就是四个引用,进度条随最后一个结束。代码展示了 `provideHubLoadingBarRouter()`、拦截器与排除方式。
Nothing yet.
代码
导入:
模板:
组件:
从右向左扫动
同一条 `indeterminate` 进度条,分别置于 `dir="ltr"` 与 `dir="rtl"` 之下。扫动方向由 `--hub-loading-bar-sweep-direction` 决定,会随文字方向翻转,无需为每种语言绑定或复制任何东西。
Loading results…
dir="ltr"Sweeps left to right, with the text.
جارٍ تحميل النتائج…
dir="rtl"يمسح من اليمين إلى اليسار، مع اتجاه النص.
The flip comes from [dir='rtl'] .hub-loading-bar, .hub-loading-bar[dir='rtl'], so the attribute counts wherever it sits — on <html>, on the region that changes direction, or on the <hub-loading-bar> element itself. The determinate fill needs no such treatment: it grows along the inline axis, which already reverses on its own.
代码
导入:
模板:
组件:
核心特性
最近更改
Version 22.2.1 - 9/16/26, 12:00 AM
changed: Repository, issue and README links follow the move to the hub-env organization. Issues for every Hub UI package are now gathered in hub-env/hub-ui, and the repository and bugs fields of the manifest point at the new addresses. No code, types or styles change.
Version 22.2.0 - 9/8/26, 12:00 AM
changed: <hub-loading> and <hub-loading-bar> keep their stylesheets to themselves. Both shipped with ViewEncapsulation.None, and neither reason recorded for it held any more. Retheming from a global stylesheet is answered by the tokens, not by the encapsulation mode: they are declared on the host at zero specificity, a custom property inherits down to every element inside, and a rule written against .hub-loading still reaches the host because the class is on the host element. And the overlay HubLoadingService mounts on document.body is a real component instance created through createComponent(), so it carries its own stylesheet with it. See BREAKING_CHANGES.md.
changed: The bar's RTL rule is written as :host-context([dir='rtl']). It was a hand-written pair of selectors, one for the attribute on an ancestor and one for the attribute on the bar itself, because dir is inherited and both spellings are legitimate. :host-context() is exactly that pair, and under emulated encapsulation Angular now compiles it, which it never did while the component was unencapsulated. Same behaviour, one selector.
added: ng-hub-ui-ds is declared as an optional peer dependency (>=22.0.0). Both stylesheets have always resolved their defaults through the --hub-sys-* / --hub-ref-* ladder and the manifest said nothing about it, so a consumer reading the package on npm could not tell that installing the token package is what hands the indicator and the bar the family palette and its dark mode. It stays optional: every token carries a literal fallback and the library renders without it.
Version 22.1.1 - 9/6/26, 12:00 AM
added: Both READMEs now document the BEM classes of the loading bar and add a Right-to-left section explaining that the indeterminate sweep reverses under dir="rtl" so it travels with the text. Both have worked since 22.1.0 with nothing in the documentation to find them by, which is the same as not having them.
changed: rxjs is now declared as a peer dependency, because hubLoadingBarInterceptor imports finalize from it on the published path and a strict installer such as pnpm with hoisting turned off had no reason to resolve it. Nothing changes under npm or yarn, where rxjs arrives hoisted as a peer of @angular/core.
fixed: The Angular badge and the opening line of both READMEs say 21+, the range package.json has always declared. Claiming 22+ turned away an application on Angular 21 that the library supports.
fixed: HubLoadingService.isLoading is described as what it computes, the reference counter rather than the mounted overlay. The README contradicted itself, since its own SSR section already explained that on the server the counter runs while the mount is skipped.
fixed: FUNCTIONALITIES.md marks the ariaLabel of hub-loading as covered, which the Inline playground control has made true since the page shipped, and adds the RTL row of the bar so the file stops omitting behaviour the stylesheet has.
fixed: Calling HubLoadingService.hide(), HubLoadingBarService.complete() or HubLoadingBarService.inc() from inside an effect() no longer subscribes that effect to the internal state of the service. Each of those paths read a tracked signal to decide what to do next, the counter, whether the bar is on screen, the current fill, so the effect woke up on state it does not own: a show() from anywhere else re-ran it and retired a reference it never registered, and inc() re-entered itself on every step. The untracked read that already guarded the bar counter now covers the three places that were left out.
Version 22.1.0 - 9/5/26, 12:00 AM
added: hub-loading-bar: the thin page-progress strip that sits under the navbar, in three placements — inline in the flow, overlay against a positioned ancestor, or fixed to the viewport at --hub-loading-bar-offset.
added: HubLoadingBarService, with reference-counted start() / complete(), an anti-flicker grace period so fast work never paints a bar at all — set delay to 0 to opt out and reveal synchronously — and a trickle that decelerates as it fills and stops short of the end, since only complete() may show 100%.
added: provideHubLoadingBarRouter(), which runs the bar for the length of a navigation, including one a guard rejects, and hubLoadingBarInterceptor with withoutHubLoadingBar() to keep polls and heartbeats out of the count.
added: Determinate mode through the progress input, which publishes aria-valuenow, and an indeterminate sweep. Both withhold the value while the number is invented, which is how ARIA marks a progressbar of unknown position.
added: provideHubLoadingBar(), the HUB_LOADING_BAR_CONFIG token, the exported hubLoadingBarTrickle() curve, thirteen --hub-loading-bar-* tokens and the hub-loading-bar-theme() Sass mixin.
Version 22.0.0 - 8/24/26, 12:00 AM
added: Initial release of hub-loading: one component covering the inline block, the container overlay and the fullscreen layer, with role="status", aria-live="polite" and aria-busy set on the host.
added: Five pure-CSS indicators — spinner, dots, bars, pulse and ring — in three size steps, with no image or font dependency.
added: Accent resolution through resolveHubAccent() from ng-hub-ui-utils: a bare name becomes a --hub-sys-color-* token and follows the theme, while a hex, an oklch() or a var(...) is used verbatim.
added: Optional image input with none, spin and pulse animations, replacing the built-in indicator with a brand mark.
added: HubLoadingService for the fullscreen overlay, with a reference counter so concurrent callers cannot unblock the screen out from under each other, plus update() to re-dress the live overlay and hideAll() to force the count to zero. SSR-safe: a no-op without a DOM.
added: provideHubLoading() and the HUB_LOADING_CONFIG token, supplying application-wide defaults to both the component and the service.
added: Theming through the --hub-loading-* token ladder and the hub-loading-theme() Sass mixin, shipped at ng-hub-ui-loading/styles.
相关库
常见问题
如何在 Angular 中显示加载动画或遮罩?
安装 ng-hub-ui-loading,并在处理进行时渲染 `<hub-loading>`。它默认是文档流中的内联块;把 `mode` 设为 `overlay` 可覆盖所在容器,设为 `fullscreen` 则遮住整个视口。若要覆盖整个应用,注入 `HubLoadingService`,在操作前后调用 `show()` 和 `hide()`。
该用加载动画、骨架屏还是进度条?
已经知道即将出现的内容形状时,选 ng-hub-ui-skeleton,占位会预留空间并避免布局偏移。等待时长不确定或必须挡住屏幕时——表单正在提交、报表正在生成——用 ng-hub-ui-loading。能报告真实进度时,改用 ng-hub-ui-metrics 绘制确定的进度条、仪表和圆环。
如何修改 Angular 加载指示器的颜色?
设置 `color` 输入属性,它接受语义名称、十六进制字面量、`oklch()` 值,或指向你自己 token 的 `var(...)` 引用。若要永久修改,每一种颜色、尺寸和时长都是 `--hub-loading-*` 自定义属性,可在自己的样式表里重新定义,Sass 混入 `hub-loading-theme()` 则能在一处重设整个组件的样式。当加载状态需要体现品牌时,`image` 输入属性会用 logo 取代指示器。
Angular 加载组件是否无障碍且支持 SSR?
是的。宿主元素是一个实时区域——`role=status`、`aria-live=polite` 和 `aria-busy`——并通过 `ariaLabel` 输入属性指定播报文本,所有动画在 `prefers-reduced-motion` 下都会关闭。服务端没有可挂载的 DOM,因此 `HubLoadingService` 只保留计数器,服务端渲染依然安全。
如何在 Angular 中给导航栏下方加一条页面加载条?
在设置了 `position: relative` 的导航栏内渲染 `<hub-loading-bar mode="overlay" placement="bottom">`;若导航栏本身是固定定位,则用 `mode="fixed"` 配合 `--hub-loading-bar-offset`。然后加上 `provideHubLoadingBarRouter()`,让加载条贯穿每次导航(包括被守卫拒绝的那次),再加上 `hubLoadingBarInterceptor`,让它同样覆盖页面到达时发出的请求。加载条对调用方做引用计数,因此两者互不知情也能协同工作,而 `withoutHubLoadingBar()` 可以把后台轮询排除在计数之外。