• 作者:老汪软件技巧
  • 发表时间: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
/>

除此之外我们还需要提供不同尺寸的图片

image.png

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会在控制台发出警告(仅在开发环境下)。

image.png

可以通过将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}

image.png

生成的占位符的默认大小为 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 的行为。

未完待续...


上一条查看详情 +flutter中 对含有二维码图片进行扫描
下一条 查看详情 +没有了
Top