ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Angular 复杂动画序列实战:query、stagger、group 与 sequence 的协调编排

Angular 复杂动画序列实战:query、stagger、group 与 sequence 的协调编排 Angular 复杂动画序列实战query、stagger、group 与 sequence 的协调编排【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular本篇技术指南基于 Angular 官方文档中的复杂动画序列章节系统讲解如何用query()、stagger()、group()、sequence()四个核心函数编排列表/网格元素的协调动画并结合 hero-list-page.ts、hero-list-groups.ts 等官方示例与 packages/animations/src/animation_metadata.ts 的源码定义展开实现原理。读完后你将能够独立实现“逐项延迟入场”“并行多属性动画”“实时过滤列表动画”以及重排序列表的动画跟踪等实战场景。需要首先明确的重要前提angular/animations包已在 v20.2 起被标记为废弃deprecated。Angular 团队推荐所有新代码使用原生 CSS 配合animate.enter与animate.leave实现动画。源码中 animation_metadata.ts 里trigger、group、sequence、query、stagger等每一个函数的文档注释都带有deprecated 20.2 Use animate.enter or animate.leave instead. Intent to remove in v23的标记印证了这一状态。既有代码的迁移方法可参考 迁移指南。本文聚焦该动画包的复杂序列能力本身理解它对维护存量应用仍然必要。四个编排函数总览此前简单的动画只针对单个 HTML 元素而 Angular 允许你编排协调的动画序列——例如让一个完整的列表或网格在进出页面时逐项动画可以选择多个动画并行运行也可以让离散的动画按顺序一个接一个执行。控制复杂动画序列的函数如下表函数作用query()查找一个或多个内部 HTML 元素stagger()为多个元素的动画施加级联延迟瀑布式group()并行运行多个动画步骤sequence()按顺序依次运行动画步骤这四个函数在 packages/animations/src/animation_metadata.ts 中都有对应定义并通过 packages/animations/src/animations.ts 统一导出为公开 API。从源码结构看每个函数返回的只是一个带type字段的元数据对象AnimationMetadataType枚举区分了Group、Sequence、Query、Stagger等 13 种元数据类型真正的执行由动画引擎在运行时按元数据树递归解释。query() 函数查找内部元素的入口大多数复杂动画都依赖query()来查找子元素并施加动画。基本用法有两类用法说明query()后接animate()查询简单的 HTML 元素直接对其施加动画query()后接animateChild()查询那些自身带有动画元数据的子元素并触发其动画否则这些动画会被当前/父元素的动画阻塞关于父级阻塞机制源码中animateChild的文档注释给出了明确解释“每次 Angular 触发一个动画时父动画具有优先级任何子动画都会被阻塞。为了让子动画能够运行父动画必须用query()查询包含子动画的每一个元素并用animateChild()运行它们。” 这也说明animateChild是专为query()设计的且只处理 Angular 动画库分配的动画不处理 CSS keyframes/transitions。query()的第一个参数是 CSS 选择器字符串其中还可以包含以下 Angular 专用 tokenToken含义:enter/:leave进入/离开 DOM 的元素:animating当前正在动画的元素*/triggerName带有任意或指定动画触发的元素:self正在动画的元素本身这些 token 可以组合进一个选择器字符串例如query(:self, .record:enter, .record:leave, subTrigger, [...])。query()的第三个参数是AnimationQueryOptions选项对象从 源码定义 可见它包含两个关键开关optional?: boolean— 默认false。必填的 query 在执行时若查不到任何元素会抛出错误设为true则忽略该错误。limit?: number— 限制返回结果的最大数量若为负数则从结果列表末尾向开头方向截取。从源码结构看query()内部基于element.querySelectorAll收集元素。关于“进入/离开”的常见误区并非所有子元素都会被算作正在进入/离开这一点有时反直觉。根据源码中query()的 API 文档能通过:enter/:leave查询到的元素只有那些 Angular 认为“基于自身逻辑”进出 DOM 的元素——即通过ViewContainerRef动态插入的元素以及带有结构性指令结构化模板指令内部是前者的子集的元素。如果一个元素的插入/移除只是其父元素插拔的“连带结果”就应当在父元素的:enter/:leave过渡里用其它方式查询它。还有一个例外带有动画触发的元素即使父元素正在离开也总可以被:leave查询到。用 query() stagger() 为多个元素编排级联动画通过query()查询到子元素后stagger()函数用来定义每个元素之间的时间间隔让元素带着依次递进式的延迟执行动画。下面的官方示例演示了如何用query()与stagger()为英雄列表heroes实现自顶向下的逐个入场每个元素之间带有轻微延迟。完整代码见 hero-list-page.ts 的page-animations区域// hero-list-page.ts animations: [ trigger(pageAnimations, [ transition(:enter, [ query(.hero, [ style({opacity: 0, transform: translateY(-100px)}), stagger(30, [ animate(500ms cubic-bezier(0.35, 0, 0.25, 1), style({opacity: 1, transform: none})), ]), ]), ]), ]), ]逐步拆解这个动画的定义过程用query()查找满足条件、正在进入页面的元素此处是.hero列表项对每个查到的元素先用style()设置统一的初始样式设为透明opacity: 0并用transform将其移出原位translateY(-100px)以便随后滑入用stagger(30, ...)让每个元素的动画彼此延迟 30 毫秒对每个元素执行 0.5 秒的动画使用自定义缓动曲线cubic-bezier(0.35, 0, 0.25, 1)同时完成淡入opacity: 1与取消位移transform: none。该触发器通过组件上的HostBinding(pageAnimations)绑定激活animatePage true。animate()的时间字符串遵循duration [delay] [easing]语法如animate(100ms 0.5s)表示时长 100ms、延迟 500ms这一点在 animation_metadata.ts 中animate()的 JSDoc 里有完整列举animate(500)、animate(1s)、animate(5s 10ms cubic-bezier(.17,.67,.88,.1))等。stagger()的源码定义签名为stagger(timings: string | number, animation: AnimationMetadata | AnimationMetadata[])其中timings是“每个被查到的元素动画启动之后”追加的间隔时间animation是包裹在间隔之内的动画步骤。group() 函数并行动画在级联延迟之外你可能还想配置同时发生的并行动画。例如对同一元素的两个 CSS 属性分别使用不同的easing函数。这时用group()函数。注意一个关键区别group()分组的是动画步骤steps而不是动画元素。group()的行为规则源自源码 JSDoc当步骤由style()或animate()调用定义时组内每次调用都立即执行同时开始若要指定更晚时间应用的样式可以用keyframes()定义带offset的步骤或使用带 delay 值的animate()调用当group()位于sequence()或transition()内部时组内所有动画步骤完成后才继续下一条指令——也就是说整个过渡的时长取决于组内最长的那个步骤。官方示例 hero-list-groups.ts 在:enter与:leave两处都使用了group()为同一个元素同时应用两组独立时序的动画// hero-list-groups.ts (excerpt) trigger(flyInOut, [ state( in, style({ width: *, transform: translateX(0), opacity: 1, }), ), transition(:enter, [ style({width: 10, transform: translateX(50px), opacity: 0}), group([ animate( 0.3s 0.1s ease, style({ transform: translateX(0), width: *, }), ), animate( 0.3s ease, style({ opacity: 1, }), ), ]), ]), transition(:leave, [ group([ animate( 0.3s ease, style({ transform: translateX(50px), width: 10, }), ), animate( 0.3s 0.2s ease, style({ opacity: 0, }), ), ]), ]), ]),解读:enter分支元素从“窄且右移、透明”的初始风格开始进入一个并行组——位移/宽度动画时长 0.3s 并额外延迟 0.1s透明度动画 0.3s 无延迟两条动画并行推进但起点不同实现“位移稍晚于淡入”的效果。:leave分支同理透明度淡出延迟 0.2s 启动。width: *是 AUTO_STYLE 自动样式标记源码中export const AUTO_STYLE *表示动画引擎从元素的当前实际布局取值。sequence() 与 group() 的对比顺序 vs 并行复杂动画中可能同时发生许多事情。如果你想让若干动画一个接一个地依次发生就用sequence()style()步骤立即应用所提供的样式数据animate()步骤在给定的时间区间内应用样式数据。从源码结构看两者的语义差异是向transition()传入一个步骤数组时默认就是按顺序sequentially执行的而group()才是显式的并行。sequence()与group()可以嵌套——当sequence()位于group()或transition()内时只有当内部每个步骤都完成后执行才会继续到下一条指令。group与sequence的源码实现都极为简洁仅构造对应类型的元数据对象// packages/animations/src/animation_metadata.ts export function group( steps: AnimationMetadata[], options: AnimationOptions | null null, ): AnimationGroupMetadata { return {type: AnimationMetadataType.Group, steps, options}; } export function sequence( steps: AnimationMetadata[], options: AnimationOptions | null null, ): AnimationSequenceMetadata { return {type: AnimationMetadataType.Sequence, steps, options}; }二者的第三个维度是共同的AnimationOptionsdelay动画启动延迟默认 0与params开发者自定义参数。综合实战实时过滤列表动画Filter Animation这是官方示例页 Filter/Stagger 标签页的核心场景在Search Heroes文本框中输入Magnet或tornado之类的文本过滤实时生效——每输入一个新字母、过滤变严格就有元素离开页面每删除一个字母英雄列表又逐渐重新进入页面。模板中一个名为filterAnimation的触发器绑定在heroesTotal上见 hero-list-page.html!-- hero-list-page.html -- label forsearchSearch heroes: /label input typetext idsearch #criteria (input)updateCriteria(criteria.value) placeholderSearch heroes / ul classheroes [filterAnimation]heroesTotal for (hero of heroes; track hero) { li classhero div classinner span classbadge{{ hero.id }}/span span classname{{ hero.name }}/span /div /li } /ul组件装饰器中的filterAnimation触发器包含三个过渡完整代码见 hero-list-page.ts 的filter-animations区域// hero-list-page.ts trigger(filterAnimation, [ transition(:enter, * 0, * -1, []), transition(:increment, [ query( :enter, [ style({opacity: 0, width: 0}), stagger(50, [animate(300ms ease-out, style({opacity: 1, width: *}))]), ], {optional: true}, ), ]), transition(:decrement, [ query(:leave, [stagger(50, [animate(300ms ease-out, style({opacity: 0, width: 0}))])]), ]), ]),这个例子完成了以下任务跳过初次进入时的动画transition(:enter, * 0, * -1, [])对空步骤数组匹配页面首次打开/导航时的状态。因为过滤动画是对“已经存在”的元素收窄范围所以初始挂载不需要动画。根据搜索输入过滤英雄updateCriteria()按name过滤HEROES并把过滤后的数量同步到heroesTotal驱动触发器的状态值变化。对每次数量变化:increment查询进入 DOM 的元素:enter先设置为透明且宽度为 0再用stagger(50, ...)让每个元素自顶向下延迟 50ms各自用 300msease-out动画恢复到默认宽度width: *与不透明状态。注意第三个参数{optional: true}——没有元素进入时比如首次渲染不抛错。对每次数量减少:decrement查询离开 DOM 的元素:leave同样 50ms 级联延迟每项 300ms 内把opacity与width动画到 0。这里还隐含一个重要设计触发器绑定的是一个数字heroesTotal利用:increment/:decrement这两个特殊过渡表达式——当绑定到触发器的数值表达式增加或减少时分别匹配。触发器绑定值会先统一转成字符串再做状态匹配布尔值可写作1/true或0/false。重排序列表的动画TrackByFunction 的必要性Angular 开箱即可正确地动画*ngFor或for列表项但如果列表项的排序会发生变化动画就会失效——因为 Angular 会丢失“哪个元素是哪个”的跟踪导致动画错乱。让 Angular 始终知道每个元素身份的唯一办法是给列表指令指定一个TrackByFunction。重要如果你需要动画一个*ngFor列表且其项的顺序在运行期间可能变化务必使用TrackByFunction。否则元素被重新识别为“移除 新增”而非“移动”进出场动画会被错误地应用动画序列也随之崩坏。这也是上面示例中track hero按实体而非索引跟踪写法的原因。动画与组件样式封装View Encapsulation的关系Angular 动画基于组件的 DOM 结构实现并不直接考虑样式封装View Encapsulation。这意味着使用ViewEncapsulation.Emulated的组件动画表现与ViewEncapsulation.None完全一致。例如如果把query()应用在一棵使用模拟封装的组件树顶端该 query 能识别并因此能动画树上任意深度的 DOM 元素而ViewEncapsulation.ShadowDom与ViewEncapsulation.ExperimentalIsolatedShadowDom会改变组件的 DOM 结构——将 DOM 元素“隐藏”在ShadowRoot元素内部。由于动画实现对简单的 DOM 结构有依赖、并不感知ShadowRoot这类操作会使部分动画实现无法正常工作。因此官方建议避免对包含 ShadowDom 封装组件的视图施加动画。复杂动画序列小结Angular 动画多元素的函数组合遵循清晰的模式用query()查找内部元素例如收集某个div内的全部图片stagger()施加级联延迟形成逐项瀑布式动画group()让多个步骤并行推进整体时长由最长步骤决定sequence()让步骤严格顺序执行也是过渡中步骤数组的默认行为。配套要点{optional: true}避免空查询报错limit控制查询数量上限:enter/:leave只能命中“自主进出 DOM”的元素重排序列表必须配TrackByFunctionShadowDom 封装视图应避开动画。延伸阅读动画迁移指南从angular/animations迁移到animate.enter/animate.leave原生 CSS 动画的完整流程动画包 API 定义源码packages/animations/src/animation_metadata.ts、统一导出入口 packages/animations/src/animations.ts官方示例代码hero-list-page.ts、hero-list-groups.ts、hero-list-page.html。【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表