- 作者:老汪软件技巧
- 发表时间:2024-12-01 00:05
- 浏览量:160
1. Deferrable views • Angular
defer是 Angular 17 中引入的一种新的模块加载方式。它可以在组件模板中使用,延迟加载该模板中的某些内容。这包括组件、指令和管道,以及任何相关的 CSS。
你可以将模板的一部分包裹在一个@defer控制块中,并且指定加载条件。
@defer (when cond) {
<large-component />
}
defer 可以用来减少应用加载的初始包大小,或延迟加载那些可能直到以后也不会加载的重量级组件。这将会带来更快的初始加载速度。
为了使@defer控制块内的依赖项能够延迟加载,它们需要满足两个条件:
必须是standalone的。非 standalone 的依赖项无法延迟加载,即使在@defer控制块内也会被急性加载。不能在 defer 块之外的同一文件中直接引用要延迟加载的内容,包括 ViewChild 查询,举个例子,假设你已经在组件A内使用 defer 定义了B组件要延迟加载,那就不能在组件 A 内再引用组件 B了,这会导致代码在访问 B 的引用时被提前加载)。
另外 defer 块中使用的组件、指令和管道的传递依赖项可以是 standalone 或基于 NgModule 的,都会被延迟加载。
那什么是传递依赖项呢?
其实它指的是这些依赖项不是由组件、指令或管道直接导入的,但这些组件、指令或管道的依赖项需要这些依赖项。例如,如果组件 A 依赖于服务 B,而服务 B 依赖于服务 C,则服务 C 是组件 A 的传递依赖项。让我们考虑一个例子来说明这一点:
说明
Standalone 与基于 NgModule
指令 B 和管道 C 可以是 standalone 的,也可以在 NgModule 中声明。
服务 D 和服务 E 也可以是 standalone 的,也可以在 NgModule 中提供。
当组件 A 被延迟时,Angular 还将延迟加载指令 B、管道 C、服务 D 和服务 E。
无论这些依赖项是 standalone 的还是 NgModule 的一部分,此延迟都适用。
总结一下这句话的意思是:
当使用 defer 块在 Angular 中延迟加载组件、指令和管道时,它们的所有依赖项(直接依赖和传递依赖)也将被延迟。无论这些依赖项是 standalone 的还是 NgModule 的一部分,这都适用。defer 会确保整个依赖关系图都被延迟加载,仅在需要时加载必要的内容,从而优化应用程序的性能。
注意: 尽量将任何延迟加载的组件放置在对用户不可见的位置。一旦依赖项加载完毕,布局可能会发生变化,导致布局抖动、重排重绘。
defer包裹的内容什么时候被加载呢触发器被触发时加载 预加载 条件表达式满足时加载自定义条件预加载
除此之外,defer还支持配置、,分别提供未加载和加载中显示的内容。在加载失败时你还可以配置 提供异常时显示的内容。
下面我们会逐个介绍这些配置。
@placeholder
@placeholder是一个可选的块,用于声明触发加载之前要显示的内容,一旦加载完成,占位符内容将被主内容替换,像是为 input 标签设置 placeholder。你可以在占位符部分使用任何内容,包括纯 HTML、组件、指令和管道,但是占位符块的依赖项是急性加载的。
@defer {
<large-component />
} @placeholder (minimum 500ms) {
<p>Placeholder contentp>
}
minimum 参数指定此占位符应显示的最小时间,可以毫秒(ms)或秒(s)为单位。
@loading
@loading是一个可选的块,允许你定义在触发加载后,加载过程中显示的视图,一般用于显示一个加载动画。一旦触发加载,@loading的内容将替换@placeholder的内容。其依赖项被急性加载,与@placeholder类似。
@defer {
<large-component />
} @loading (after 100ms; minimum 1s) {
<img alt="loading..." src="loading.gif" />
}
@error
@error是一个可选的控制块,允许你声明在延迟加载失败时显示的内容。与@placeholder和@loading类似,@error控制块的依赖项也会被急性加载。
@defer {
<large-component />
} @error {
<p>Failed to load the componentp>
}
Trigger
@defer支持两种触发器:on和when。
on支持如下几种情况:
在浏览器空闲时(使用requestIdleCallbackAPI 检测)触发延迟加载。这也是 @defer 的默认行为。
在指定内容进入视口时(使用IntersectionObserverAPI)触发延迟加载。默认情况下,只要该占位符是单个根元素节点,它将作为进入视口的被观察元素。
@defer (on viewport) {
<calendar-cmp />
} @placeholder {
<div>Calendar placeholderdiv>
}
或者,你可以在与@defer控制块相同的模板中指定一个模板引用变量,作为被观察进入视口的元素。此变量作为参数传递给视口触发器。
<div #greeting>Hello!div>
@defer (on viewport(greeting)) {
<greetings-cmp />
}
将在用户通过click或keydown事件与指定元素交互时触发延迟块。
@defer (on interaction) {
<calendar-cmp />
} @placeholder {
<div>Calendar placeholderdiv>
}
或者与 viewport 类似,或者你可以指定一个模板引用变量,作为触发交互的元素。
<button type="button" #greeting>Hello!button>
@defer (on interaction(greeting)) {
<calendar-cmp />
} @placeholder {
<div>Calendar placeholderdiv>
}
在鼠标悬停于触发区域时触发延迟加载, 与上面两个类似,你可以指定一个模板引用变量,作为触发交互的元素,这里就不再提供示例了。
@defer (on hover) {
<calendar-cmp />
} @placeholder {
<div>Calendar placeholderdiv>
}
立即触发延迟加载,这意味着客户端完成渲染后,延迟块将立即开始获取。
@defer (on immediate) {
<calendar-cmp />
} @placeholder {
<div>Calendar placeholderdiv>
}
将在指定的持续时间后触发,可以以ms或s为单位指定。
@defer (on timer(500ms)) {
<calendar-cmp />
} @placeholder {
<div>Calendar placeholderdiv>
}
when
when作为一个返回布尔值的表达式来指定条件。当此表达式变为真值时,占位符将被延迟加载的内容替换, 表达式可以是异步的。
但如果 condition 的初始值为 true, 后续when条件切换回false,延迟控制块不会恢复到占位符。因为替换是一次性操作。如果控制块内的内容应有条件地渲染,可以在控制块内使用if。
@defer (when cond) {
<calendar-cmp />
}
你也可以在一个语句中同时使用when和on,只要任一条件满足,替换就会被触发, 多个on触发器也是一样,同时使用时,判断条件始终是 OR 。
@defer (on viewport; on hover; when cond) {
<calendar-cmp />
} @placeholder {
<img src="placeholder.png" />
}
prefetching
@defer允许指定何时应该触发依赖项的预加载。你可以使用prefetch关键字。prefetch语法与之前的延迟条件类似,接受when和/或on来声明触发器。
prefetch when和prefetch on控制何时预先获取资源。这允许你定义更高级的行为,例如在用户实际看到之前,或者与延迟块交互之前开始预取资源。在下面的示例中,当浏览器变为空闲时开始预取,而块内容在交互时渲染。
@defer (on interaction; prefetch on idle) {
<calendar-cmp />
} @placeholder {
<img src="placeholder.png" />
}
2. Image Optimization • Angular
NgOptimizedImage支持通过配置自动生成srcset属性,同时支持配置延迟加载或者急性加载,显著提升LCP指标(Largest Contentful Paint (LCP) | Articles | web.dev),并且提供占位符等功能,提升用户体验。
注意:尽管NgOptimizedImage指令在 Angular 版本 15 中成为了一个稳定特性,但它已被回迁,并在 13.4.0 和 14.3.0 版本中也作为一个稳定特性提供。
什么是 srcset 呢?
srcset属性一般用于
标签,它提供一组图像资源,以便浏览器根据设备的显示特性(如屏幕分辨率和视口大小)选择最合适的图像,从而提升性能和用户体验。
<img
src="small.jpg"
srcset="
small.jpg 480w,
medium.jpg 800w,
large.jpg 1200w,
xlarge.jpg 1600w"
sizes="(max-width: 600px) 480px, (max-width: 900px) 800px, (max-width: 1200px) 1200px, 1600px"
alt="Example image"
>
在这个示例中:
srcset的工作原理
浏览器根据视口宽度和sizes属性计算出需要的图像尺寸。浏览器从srcset属性中选择最接近计算尺寸的图像资源进行加载。较为完整的使用 NgOptimizedImage 的例子
import { Component } from '@angular/core';
import {
IMAGE_CONFIG, xianggang
IMAGE_LOADER,
ImageLoaderConfig,
NgOptimizedImage,
} from '@angular/common';
@Component({
selector: 'app-optimize-img',
standalone: true,
imports: [NgOptimizedImage],
templateUrl: './optimize-img.component.html',
styleUrl: './optimize-img.component.scss',
providers: [

{
provide: IMAGE_LOADER,
useValue: (config: ImageLoaderConfig) => {
if (config.width === undefined) {
return config.src;
}
return `${config.width}-${config.src}`;
},
},
{
provide: IMAGE_CONFIG,
useValue: {
// placeholder img width
placeholderResolution: 40,
},
},
],
})
export class OptimizeImgComponent {}
<img
class="image-class"
ngSrc="SpongeBob.png"
width="3840"
height="2160"
priority
ngSrcset="600w,800w,1200w,1920w,3840w"
sizes="(max-width: 800px) 70vw, (max-width: 1200px) 100vw"
placeholder
/>
除此之外我们还需要提供不同尺寸的图片
img标签最终会被编译为下面这样
<img
ngsrc="SpongeBob.png"
width="3840"
height="2160"
priority=""
ngsrcset="400w,600w,900w,1200w,1920w,3840w"
sizes="(max-width: 800px) 70vw, (max-width: 1200px) 100vw"
placeholder=""
class="image-class"
loading="eager"
fetchpriority="high" ng-img="true"
src="SpongeBob.png"
srcset="400-SpongeBob.png 400w, 600-SpongeBob.png 600w, 900-SpongeBob.png 900w, 1200-SpongeBob.png 1200w, 1920-SpongeBob.png 1920w, 3840-SpongeBob.png 3840w"
>
我们可以看一下效果,浏览器自动根据视口大小,获取了不同尺寸的图片。
上面的示例中,总共做了如下配置:
分别解释下这些配置的作用
width,height
如果不预先提供宽高,浏览器不会为图片预留大小,当图片被加载完后,会导致布局发生变化,进行重排,所以NgOptimizedImage 要求必须为图片指定高度和宽度。
对于响应式图片(你已经根据视口大小调整了大小的图片),width和height属性应该设置为图片的原始大小。另外还需要,size 的用法我们下面再说。
对于固定大小的图片,width和height属性应该设置为你所需渲染的大小。但是要注意宽高比应该始终与图片文件的原始宽高比一致,不然会产生形变,angular也会在开发模式的控制台中发出警告。
如果你有一个具有已知宽高的父容器,并且希望将图片放到该容器中,那也可以不设置宽高,添加 fill 属性即可,不过为了为了正确渲染fill图片,其父元素必须使用position: "relative"、position: "fixed"或position: "absolute"来设置样式。
另外根据图片的样式,添加width和height属性可能会导致图片的渲染方式不同。如果你的图片样式正在以扭曲的纵横比渲染图片,NgOptimizedImage会在控制台发出警告(仅在开发环境下)。
可以通过将height: auto或width: auto添加到图片的样式中来解决此问题。
priority
将图片标记为priority后,该指令会自动应用以下优化:
在开发环境下,如果 LCP 元素是一个没有priority属性的图像, Angular 会在控制台中抛出一个 Error, 提示你这可能会严重影响加载性能。
常见的LCP元素
ngSrcset sizes
这两个属性通常会一起使用, NgOptimizedImage 会根据ngSrcset 和 Sizes 自动生成 srcset 属性。
ngSrcset 指定需要根据视口宽度自适应的尺寸有哪些,Sizes指定一个百分比,那么ngSrcset 是怎么配合 sizes 使用的呢?我们先来看一个例子:
假设需求是在视口宽度为1366px时,使用一张宽高为1366×768的图片,在视口宽度为 1920px时,使用宽高为 1920×1080的图片,那么我们需要配置
<img
...
srcset="./xxxpath/1366-example.png 1366w, ./xxxpath/1920-example.png 1920w"
sizes="100vw"
>
当我们使用 NgOptimizedImage 指令时
通过 ngSrc提供一个默认的图片源,配置 sizes 为 100vw用 ngSrcset 声明我们需要的尺寸。
<img
...
ngSrc="example.png"
sizes="100vw"
ngSrcset="1366w,1920w"
>
提供一个自定义的 loader,去生成图片的URL
providers: [
{
provide: IMAGE_LOADER,
useValue: (config: ImageLoaderConfig) => {
// width from ngSrcset config
if (config.width === undefined) {
// return default src
return config.src;
}
return `./xxxpath/${config.width}-${config.src}`;
},
}
],
// 这里贴出 ImageLoaderConfig的属性
export declare interface ImageLoaderConfig {
/**
* Image file name to be added to the image request URL.
*/
src: string;
/**
* Width of the requested image (to be used when generating srcset).
*/
width?: number;
/**
* Whether the loader should generate a URL for a small image placeholder instead of a full-sized
* image.
*/
isPlaceholder?: boolean;
/**
* Additional user-provided parameters for use by the ImageLoader.
*/
loaderParams?: {
[key: string]: any;
};
}
然后该指令就会生成一模一样的配置,如果你想要遵循一份标准的尺寸配置,那么可以不指定 ngSrcset,只保留 sizes 配置,angular 默认会帮你以 [16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840] 进行设置,从而生成 srcset。
如果你想要替换这些默认值,除了手动设置 ngSrcset 以外,还可以用IMAGE_CONFIG 来实现:
providers: [
{
provide: IMAGE_CONFIG,
useValue: {
breakpoints: [16, 48, 96, 128, 384, 640, 750, 828, 1080, 1200, 1920]
}
},
],
怎么理解 sizes 中配置的 100vw 呢
这里的 100vw 相当于 100%,举个例子,你配置了 ngSrcset ="600w, 900w, 1000w,1200w", sizes = "80vw", 那么当用户的视口宽度为 1000px时,浏览器会尝试去找最接近 1000 * 80% 宽度的照片, 最终使用 900-xxxx.png,而不是对应视口宽度的 1000-xxxx.png。
你可能注意到我们最开始的示例代码中,配置了 sizes="(max-width: 800px) 70vw, (max-width: 1200px) 100vw",是的,sizes 也支持媒体查询。
placeholder
NgOptimizedImage 可以为你的图像提供一个自动低分辨率占位符, 只需给你的图像添加placeholder属性即可
<img ngSrc="SpongeBob.png" width="400" height="200" placeholder>
添加此属性将自动请求图像的较小版本,使用你指定的图像 loader 。这个小图像将以 CSS 模糊的background-image样式应用,而你的图像加载时。如果没有提供图像 loader,则无法生成占位符图像,并将抛出错误。
图例中请求40-SpongeBob.png 就是因为我提供的 loader中返回的 url是 ${config.width}-${config.src}
生成的占位符的默认大小为 30px 宽,这个值会变成参数传递到你提供的 loader中,你可以通过在IMAGE_CONFIG提供者中指定一个值来使其更大或更小,如下所示
{
provide: IMAGE_CONFIG,
useValue: {
placeholderResolution: 40,
},
},
默认情况下,NgOptimizedImage 对图像占位符应用 CSS 模糊效果。要呈现一个没有模糊效果的占位符,可以配置placeholderConfig参数
<img ngSrc="cat.jpg" width="400" height="200" placeholder [placeholderConfig]="{blur: false}">
其它知识点要禁用单个图片的 srcset 生成,你可以在图片上添加disableOptimizedSrcset属性。默认情况下,NgOptimizedImage为所有未标记priority的图片设置loading=lazy。你可以通过设置loading属性来为非优先图片禁用此行为。此属性会接受值:eager、auto和lazy。
<img ngSrc="cat.jpg" width="400" height="200" loading="eager">
NgOptimizedImage指令中支持的另一个属性是loaderParams,专门设计用于自定义加载器的使用。loaderParams属性接受一个带有任意属性的对象作为值,本身不会执行任何操作。loaderParams中的数据被添加到传递给自定义加载器的ImageLoaderConfig对象中,可用于控制 loader 的行为。
未完待续...