news 2026/9/15 16:58:01

自定义组件动画从状态驱动到性能优化:原理、生命周期与Chrome排查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
自定义组件动画从状态驱动到性能优化:原理、生命周期与Chrome排查

写自定义组件的人很多,但真正能把组件里的动画做得“有质感”的,说实话不多。很多人做自定义组件时,把动画当成最后的装饰,样式写完、功能跑通,再甩一个transition: all 0.3s ease上去,就算交差了。但组件化开发做到一定深度就会发现,动画根本不是装饰,它是组件状态变化的“可视化翻译”——用户点了、等了、拖了、切了,你都得让界面给出恰当的反馈。这也正是“第四章 自定义组件、动画”这种章节存在的意义:不是教你怎么用几个CSS属性,而是让你把动画当作组件设计的一部分来考虑。

这篇文章我按自己这几年的实际开发经验来写,覆盖Web前端里自定义组件动画的核心机制、生命周期协同、原生事件绑定、Chrome性能问题的排查思路,最后附一个完整的数字滚动组件实现,再对照一下Android、QML、Unity里的组件动画思路。适合已经能独立写组件、但想把动画和交互做深一层的开发者参考。

1. 先搞清楚:自定义组件里的动画,到底在“动”什么

1.1 组件不是模板,状态变化才是动画的源头

很多初学者对“自定义组件”的理解停留在“封装一段HTML+CSS+JS”,觉得组件就是把重复的界面代码抽出来复用。这个理解没错,但只触及了表面。组件真正强大的地方在于:它把“状态”和“界面”绑定在了一起。一个按钮组件,它有loading状态、disabled状态、active状态;一个下拉框组件,它有展开/收起状态、选中/未选中状态。用户和组件交互,本质就是改变这些状态。

那么动画在组件里到底该扮演什么角色?说白了:动画是状态变化的“过渡解释器”。如果没有动画,状态切换是瞬间完成的,用户只能看到“结果”;有了动画,用户能看到“过程”,能感知到“我做的操作生效了”。比如你点击一个收藏按钮,如果心形只是“啪”地变红,用户其实需要一点时间来确认“哦,点上了”;但如果有一个缩放回弹的过程,反馈就变得非常明确。

理解了这一层,你就明白为什么transition不能解决所有问题——因为transition只适合处理“样式值连续变化”的场景,而组件里的状态切换往往伴随着DOM的创建删除、元素的位移、列表的重排,这些靠一个transition: all根本搞不定。

1.2 组件动效的三种基本形态

我习惯把组件动画分成三类,开发和排查问题时这样分类非常清晰:

进入/退出动画:组件挂载、卸载时的动画。比如Modal弹出、Toast出现、下拉菜单展开。这类动画的核心难点是“退出动画”——因为组件卸载了,动画就没法播了,你必须在卸载前等动画播完。

状态切换动画:组件内部状态变化引起的视觉变化。比如Tab切换时下划线滑动、开关按钮的滑块移动、数字变化时的滚动。这类动画的核心是“从旧值平滑过渡到新值”。

持续反馈动画:组件在等待异步操作或表达某种运行状态。比如Loading转圈、骨架屏的闪烁、音频播放器的均衡器跳动。这类动画没有明确的起点和终点,通常是循环播放。

这三类动画的写法、调试手段、性能开销都不一样。后面我会逐个展开讲它们在自定义组件里怎么落地。

2. 底层机制:CSS过渡、关键帧和 Web Animations API 之间的选择

2.1 三种手段的边界在哪里

做组件动画,手头有三种基础工具:CSStransition、CSS@keyframesanimation属性)、以及Web Animations API(WAAPI,即element.animate())。很多教程把它们混着讲,但实战中它们的适用场景完全不同。

手段触发方式适用场景劣势
transition样式属性变化时触发状态切换、Hover反馈、开关切换只能做起始值和结束值之间的补间,中间状态不可控
animation类名或内联样式触发进入/退出、持续循环、多关键帧动画完成后的回调需要监听animationend,容易漏
WAAPI代码直接调用动态计算起止值、手势驱动的动画、与JS交互复杂的动画兼容性需要处理(现代浏览器基本都支持)

我的选择原则很简单:状态切换优先用transition,进入/退出和循环动画优先用animation,需要根据运行时数据动态计算动画起止值的,用WAAPI。为什么WAAPI不可替代?因为animation的关键帧百分比是写死在样式表里的,你要让数字从1234滚到5678,CSS根本没法动态生成关键帧,只能用JS算。而WAAPI可以这样写:

element.animate( [ { transform: 'translateY(0)' }, { transform: 'translateY(-100px)' } ], { duration: 600, easing: 'cubic-bezier(0.2, 0.8, 0.2, 1)', fill: 'both' } );

这个方法返回一个Animation对象,有finishedPromise、cancel()reverse()等方法,配合组件生命周期非常顺手。

2.2 动画完成后的状态保持:fill-mode、延迟和结束回调

热搜词里有一条“css3动画延迟和完成后状态的保持”,这确实是组件开发时很容易踩坑的点。

先说“完成后状态的保持”。animation播放完成后,元素会默认回到动画前的状态,或者精确地说,回到没有应用动画时的样式。如果你想让元素保持在动画最后一帧的样子,需要设置animation-fill-mode: forwards。但这里有个隐蔽的坑:forwards只保持动画最后一帧的样式,如果动画结束后你还要让元素继续响应别的样式变化,比如拖拽移动,forwards可能和后续的transform冲突,因为动画还在“占用”transform属性。

更严重的问题是:如果动画结束后还需要触发新的动画,你直接改类名往往没用,因为浏览器认为同一个属性已经在动画控制下。我踩过好多次,解决方法是:要么在animationend回调里先取消动画再重新执行,要么用WAAPI,每次调用element.animate()都会创建一个新的Animation实例,天然规避了这个问题。

再说“延迟”。CSS的animation-delay是“延迟开始”,但它在延迟期间,动画关键帧里的from状态并不会提前应用。比如你在做一个弹窗从透明到不透明的动画,设了animation-delay: 300ms,那么前300ms里弹窗是什么状态?答案是它原本的默认状态,通常是完全不透明、直接显示出来的。这就会导致弹窗“闪现”一下再开始动画。正确的做法是把初始状态写到样式里:opacity: 0,然后动画从opacity: 0开始,并且设置animation-fill-mode: backwardsboth,让元素在延迟期间就处于from关键帧状态。

.modal { opacity: 0; animation: fadeIn 0.3s ease-out 0.2s both; } @keyframes fadeIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }

这个both解决了“延迟期间显示初始状态”和“结束后保持末帧”两个问题。

2.3 执行次数与逆向播放:不只是循环两次

“css3动画执行次数和逆向播放”是另一个高频搜索。CSS里控制这两个行为的是animation-iteration-countanimation-direction

iteration-count: infinite是无限循环,2是执行两次。但很多组件场景里,循环的“次数”其实是个业务变量,比如通知消息的提醒动画,可能要根据未读数决定闪几次。CSS里写死次数虽然可以,但如果你需要在动画结束后拿到精确的“每一次”回调,animationiteration事件就很有用。我通常会在组件里这样监听:

element.addEventListener('animationiteration', () => { // 每一次循环结束时的回调 unreadCount--; if (unreadCount <= 0) { element.classList.remove('reminder-blink'); } });

至于animation-directionreverse是从tofrom播,alternate是正反交替。组件开发的场景里,alternate很适合做“呼吸灯”效果:透明度从0.4到1再回到0.4,不需要写两个关键帧或两段动画。

.breathe { animation: breathe 1.6s ease-in-out infinite alternate; } @keyframes breathe { from { opacity: 0.4; } to { opacity: 1; } }

但要注意,alternate的“返回”也是动画过程,它的时长是2 x duration,布局张力不太一样。如果你希望“去”是快的、“回”是慢的,alternate控制不了,你需要在keyframes里自己定义非对称曲线,或者拆成两个阶段。

3. 组件生命周期与动画的协同:从加类名到状态驱动

3.1 进入/退出的正确姿势:先播完,再销毁

自定义组件最常踩的坑是“退出动画根本播不出来”。原因很简单:组件被移除时,DOM节点直接从文档里消失了,浏览器根本没有机会渲染动画的中间帧。

我常见的解决方案有两种:

方案一:延迟销毁。在组件“请求退出”时,先给元素添加一个.exit类,触发退出动画,监听animationendtransitionend,等动画结束后再真正移除DOM。核心代码大概是:

close() { const el = this.shadowRoot.querySelector('.toast'); el.addEventListener('animationend', () => this.remove(), { once: true }); el.classList.add('toast-exit'); }

方案二:借助框架的过渡机制。如果你用Vue,<Transition>组件天然处理了这个问题;React的话可能要用react-transition-group或者自己封装useTransitionHook。原理都是一样的:卸载前先停留一个动画周期。

这里有个细节:如果组件的进入动画和退出动画时长不一样,animationend事件触发后要校验event.animationName,避免退出的类名把进入动画也带进来误杀。我见过不止一个项目在这里出bug,动画名一多,监听回调就会被反复触发。

3.2 列表增删和位置变化的 FLIP 方案

还有一个场景经常让组件开发者头疼:一个列表组件里,某一项被删除后,后面的项要“补位”,这个补位动画怎么做?如果只是删除项自身淡出,那补位项是瞬间跳上去的,非常生硬。要做出流畅的补位动画,通常要用FLIP技巧。

FLIP是Fast Layout Inheritance Pattern(或者First Last Invert Play)的缩写,核心思路:

  1. First:记录元素变化前的位置信息(比如getBoundingClientRect())。
  2. Last:让界面发生变化(增删改),再记录元素变化后的位置。
  3. Invert:计算位置变化量,反向应用transform,让元素看起来还停留在变化前的位置。
  4. Play:把反向的transform过渡到transform: none,动画就完成了。

在自定义列表组件里,删除一个子项时,后续子项的位置发生变化,你就可以给它们做FLIP。现在浏览器正在逐渐支持View Transitions APIdocument.startViewTransition),能简化一部分FLIP逻辑,但原生兼容性还不算完美,老项目里手写FLIP依然有存在价值。

// 极简FLIP function flip(el) { const first = el.getBoundingClientRect(); // ...执行DOM变更 const last = el.getBoundingClientRect(); const dx = first.left - last.left; const dy = first.top - last.top; el.animate( [{ transform: `translate(${dx}px, ${dy}px)` }, { transform: 'translate(0, 0)' }], { duration: 300, easing: 'ease-out' } ); }

3.3 异步数据加载时的动画切换时序

自定义组件里最常见的异步场景是“数据加载中 -> 数据就绪 -> 内容展示”。这里涉及两类动画的衔接:Loading/骨架屏动画和内容入场动画。

我早期犯过的错是:数据一到,立刻把Loading组件切换成内容,结果Loading旋转动画直接中断,内容瞬间出现,整个界面显得特别“碎”。后来我总结出一个节奏:

  1. 数据到达后,先让Loading做一次退场动画(透明+轻微缩放)。
  2. 退场动画结束后挂载内容DOM,同时内容做入场动画。
  3. 如果数据加载特别快(比如从缓存命中),可以跳过Loading显示,直接入场,避免闪烁。

这个“时序控制”放在自定义组件里,最好抽象成一个简单的状态机:loadingreadyerror。组件的渲染逻辑完全由状态驱动,动画只是状态切换时的旁路效果。这样组件对外暴露的API就特别干净,使用方只需要关心状态,不需要关心动画细节。

4. 组件绑定原生事件:手势、交互与动画的衔接

4.1 原生事件的绑定位置和动画触发时机

“自定义组件绑定原生事件”听起来简单,但真正做起来有不少门道。首先是事件绑定的位置:如果你用的是Shadow DOM,事件绑在this.shadowRoot内部元素上就行;如果是普通组件,通常绑在根元素上。但无论如何,要避免在组件外层用全局事件监听器去控制内部动画,那样会把组件的封装性破坏掉。

其次是触发时机。比如一个按钮组件,用户点击后你希望它先做一个“按下去”的缩放动画,然后触发外部的业务回调。如果你在click事件里同时做了这两件事,动画刚播到一半,业务逻辑就已经改变DOM了,动画被强制打断。更合理的做法:

handleClick(event) { const animation = this.btn.animate( [{ transform: 'scale(0.96)' }, { transform: 'scale(1)' }], { duration: 120, easing: 'ease-out' } ); animation.finished.then(() => { this.dispatchEvent(new CustomEvent('press', { detail: { event } })); }); }

这样业务回调会在动画结束后触发,反馈完整、不打断。

4.2 拖拽、滑动组件里的 pointer/touch 事件处理

自定义组件里最复杂的一类交互动画是拖拽和滑动。比如一个滑块组件、一个抽屉组件、一个可拖拽排序的列表组件。这类组件的共同点是:动画不是“开始->结束”,而是跟随着手指/鼠标的位置实时更新。

这里的关键是用pointer事件族(pointerdownpointermovepointerup)而不是传统的mousedown+touchstart双重绑定。pointer事件统一了鼠标和触摸,还省去了处理touchmove时禁止页面滚动的麻烦。

pointermove的触发频率非常高(每秒60次甚至更多),如果你在回调里直接操作DOM属性,很容易造成卡顿。正确做法是配合requestAnimationFrame做帧对齐:

onPointerMove(e) { // 只在下一帧更新一次位置,避免一帧内多次触发 if (this.rafId !== null) return; this.rafId = requestAnimationFrame(() => { this.updateDragPosition(e.clientX, e.clientY); this.rafId = null; }); }

还有一点,拖拽开始后要给组件设置touch-action: none,否则移动端浏览器会劫持手势。这个属性写在样式里很有用,但很多开发者会忘记。

4.3 节流、防抖和动画的“最终态”处理

交互类动画还有一个常见问题是“手抖”。用户按下拖动,可能在几毫秒内产生多次pointerdown,或者快速滑动过程中小幅抖动,导致组件反复横跳。

针对快速多次触发,如果你做的是滑动手势,最好加一点“惯性”逻辑:记录最近几次位置和时间,在手势结束时按速度计算一个衰减动画。很多原生滚动组件都有这种惯性效果,Web组件要复现,通常会用一次WAAPI或transition补一段自动滑动。

针对抖动,可以通过“阈值”过滤:位移小于某个像素不更新状态。不要小看这个阈值,它能让组件的手感完全不一样。我之前写过一个抽屉组件,起初没有任何阈值,用户想关闭抽屉时稍微动了一下就误触发,加了10px阈值后,手感立刻稳了。

5. Chrome 里动画卡顿的排查与修复

5.1 先分清是主线程卡顿还是合成器卡顿

热搜里有“chrome网页动画展示的时候很卡”,这个问题在自定义组件动画里尤其常见,因为组件往往封装了复杂的内部结构,动画属性还没选对,渲染性能就直接崩了。

开发工具里打开Performance面板录制动画过程,有两种典型情况:

一种是主线程长时间占用,表现为FPS很低、长任务很多。这通常是JS密集计算、同步布局或强制重排引起的。比如动画每一帧里都去读取offsetWidthoffsetTop等样式,会触发“强制回流”。

另一种是主线程平稳,但动画仍然一顿一顿的。这可能是因为你动的属性不是transform/opacity,比如你在动画widthheighttopleftfilter,这些属性会触发Layout或Paint,浏览器每帧都要重新计算布局和绘制,开销非常大。

建议优先原则:动画只动transformopacity。坐标系移动用transform: translate(),改变大小用transform: scale(),软阴影用filter或伪元素透明度配合。

5.2will-change不是万能补药

will-change是告诉浏览器某个属性即将变化,浏览器会提前做优化(比如把元素提升到合成器层)。但是滥用会让浏览器创建过多的合成层,反而占用大量显存,移动端尤其明显。

我见过一个项目,所有做动画的元素都加了will-change: transform,结果是一个列表页里同时存在几百个合成层,滚动时掉帧严重。正确的做法是:只在动画即将开始前加will-change,动画结束后移除。用WAAPI时,element.animate()自动做了一些优化,通常不需要手动设置will-change

el.style.willChange = 'transform'; el.animate(/* ... */).finished.finally(() => { el.style.willChange = 'auto'; });

如果实在没有抓手,原则是:合成层数量控制在十几个以内,绝对不要给所有子元素都开。

5.3 一个真实的排查案例

我之前维护过一个数据可视化的组件,里面有一个环形进度条动画。用户反馈在Chrome上动画一启动就卡不住,掉到30帧。

用Performance录制后发现,动画过程中每一帧都触发了大量Paint操作,原因是环形进度条用stroke-dasharray做动画。stroke-dasharray变化会触发Paint,而且Paint区域是整个SVG的包围盒,耗时不低。

解决办法是:把环形进度条的“基础环”和“进度环”拆成两层,进度环通过旋转变换实现,或者干脆用两个半圆拼接,通过transform: rotate()控制角度。改造后,Paint区域变小了,也没有stroke-dasharray的连续变化,帧率恢复到了60。

这个案例给我留下的经验是:动画卡顿的第一排查顺序是“属性选对没有”,而不是“代码性能够不够”。很多问题不是JS算不过来,是渲染管线里选了最贵的路径。

6. 从需求到交付:一个数字滚动组件的完整实现

6.1 需求拆解和方案为什么这么选

说了这么多理论,看一个完整的实例会更有感觉。这里我做一个数字滚动组件,对应热搜里的“tailwind数字动画”,你用Tailwind也好、原生CSS也好,核心逻辑是一样的。

需求很简单:给一个数字组件,当数值变化时,数字像老式翻页钟一样向上滚动到新值。这个组件在很多数据大屏、计数器、Dashboard里很常见。

方案不使用<span>直接替换文本,因为文本替换没有滚动过程。我的方案是:把每一位数字拆成一个独立的“滚筒”,滚筒内部是一个高度为0到9总高度的长条,通过transform: translateY()控制显示哪个数字。滚动动画就是控制translateY从旧数字平滑过渡到新数字。

6.2 实现细节

HTML骨架大概是这样,一位数字对应一个.digit-roller

<div class="number-roller">function setDigit(digitEl, target, duration = 600) { const current = parseInt(digitEl.dataset.value || '0', 10); if (current === target) return; const offsetY = -target * digitEl.clientHeight; // 高位要换行,通过index计算每位的偏移,为了简化这里直接用transform digitEl.animate( [ { transform: `translateY(${-current * digitEl.clientHeight}px)` }, { transform: `translateY(${offsetY}px)` } ], { duration, easing: 'cubic-bezier(0.2, 0.7, 0.2, 1)' } ).finished.then(() => { digitEl.dataset.value = String(target); }); }

实际组件里还要处理数字位数变化,比如从9变成10,需要新增一个“十位”滚筒。通常做法是在数值变化时重新检查位数,不足的补位滚筒,多余的可以保留(下一个0到9的循环里可能还会用到)或者移除(移除的时候可以做一次缩放淡出动画)。

6.3 测试和边界条件

这类组件看起来简单,但边界条件特别多:

连续快速变化:比如1 -> 9 -> 3,动画还没放完就要播下一个。如果不处理,会出现“跳变”或者“同一位同时播两个动画”的冲突。我的处理方式是在每次更新前调用digitEl.getAnimations().forEach(a => a.cancel()),取消旧动画再播新的。

弱网或异步场景:组件可能在数据返回时才更新数值,如果数据到达非常密集,可以考虑节流:以requestAnimationFrame为最小更新粒度,在一个帧里把多个数据变化合并成一次动画。

初始渲染:首次渲染时不要播放滚动动画,直接显示目标数字。判断方式很简单:如果dataset.value === undefined,说明是首次,直接设置样式不加动画。

SSR/无动画环境:用户开启了“减少动态效果”的辅助功能(prefers-reduced-motion),这种时候要尊重用户偏好,直接切换到无动画的显示模式。这不是锦上添花,而是无障碍的硬要求。

@media (prefers-reduced-motion: reduce) { .digit-roller { animation: none !important; transition: none !important; } }

完整代码我通常会把基础滚动逻辑抽成一个Roller类,再给数字组件封装业务层。这样“数值更新”和“滚动动画”解耦,测试也好写。

7. 跳出 Web:Android、QML、Unity 的组件动画思路对照

7.1 Android:属性动画与自定义 View 的 onDraw 协作

Android自定义View做动画,核心是ValueAnimatorObjectAnimator。它们是“属性动画”,直接操作数值,然后通过invalidate()重绘。这和我前面讲的“状态驱动”是一个思路:动画只是数值插值的过程,onDraw根据最新的数值绘制界面。

写自绘View时,特别注意性能:不要在onDraw里做对象分配,translate/scale尽量用canvas的矩阵变换,而不要直接改坐标数值。同时动画过程中要避免触发requestLayout,只调invalidate(),否则会频繁重新测量布局,卡得厉害。

7.2 QML:状态、过渡与 Behavior 的声明式动画

QML里的组件动画和Web有本质不同,它是声明式的:定义statestransitions,组件状态变化时自动播放过渡动画。写起来非常简洁:

Rectangle { id: box states: [ State { name: "active"; PropertyChanges { target: box; x: 200 } } ] transitions: Transition { NumberAnimation { properties: "x"; duration: 300; easing.type: Easing.OutQuad } } MouseArea { onClicked: box.state = "active" } }

这里最接近Web的是Behavior:它可以跟随某个属性变化自动播放动画,相当于Web里transition的声明式版本。QML性能优化的重点也和Web一样:多使用transform,少用会被布局引擎重排的属性。

7.3 Unity:Animator 状态机与代码驱动的差异

Unity的UI动画通常依赖Animator状态机,把动画切成一个个状态,靠触发条件切换。这个模式非常适合UI组件的状态管理:一个按钮有Normal、Hover、Pressed、Disabled四个动画状态,你不需要手动控制动画播放时机,只需要切换状态参数。

但状态机不擅长“动态值计算”的动画,比如血条随伤害数值变化、数字滚动这类,你还是得写代码用Lerp插值或DOTween这类Tween库。DOTween其实就是Unity世界里的WAAPI,直接执行一段数值补间,非常接近element.animate()的思路。

我自己的感觉是:不管哪个平台,组件动画的底层思维都是相通的——把动画当作状态变化的可视化映射,用声明方式描述“什么时候播”,用代码控制“怎么播到哪”。平台差异只是API形态不同而已。

8. 动画工作流里容易被忽略的事

8.1 从设计稿到代码:动效参数怎么定

很多前端拿到设计稿只顾着还原位置、颜色、字号,动效参数全靠“感觉”。这样往往做出俩版本,要么太慢显得拖沓,要么太快看不清楚。我建议和设计沟通的时候,直接约定四个东西:

  • 时长:短反馈100-200ms,状态切换200-300ms,大区块入场300-500ms。
  • 缓动:默认ease-out,强化速度感可以用cubic-bezier(0.2, 0.8, 0.2, 1),回弹效果可以用一次spring模拟。
  • 位移量:元素移动距离、缩放比例要有明确的数值。
  • 是否叠加:透明度和位移、缩放是否在动画里同时出现。

这些参数要是能用CSS变量定义,组件之间的动效风格就很好统一了:

:root { --anim-duration-fast: 120ms; --anim-duration-base: 240ms; --anim-easing-out: cubic-bezier(0.2, 0.8, 0.2, 1); --anim-easing-spring: cubic-bezier(0.34, 1.56, 0.64, 1); }

8.2 动画库的取舍:自己写还是用第三方

Web动画方案里比较流行的有GreenSock(GSAP)、Motion One、Framer Motion(React专用)、Vue Transitions等。我的使用经验是:如果只是组件内的简单状态切换,原生CSS加WAAPI完全够用,引入动画库反而增加包体积和学习成本;如果要做复杂的连续动画、滚动驱动的场景动画、或者需要精确控制时间线,GSAP这种工业级库能省非常多事。

组件库自带的最小化动画策略是:把动画能力抽象成组件内部的一个模块,外部不直接感知。比如我的数字滚动组件完全自给自足,不依赖任何动画库;但一个复杂的“引导页”组件,我就会用GSAP的timeline来编排各个元素的出场顺序。

8.3 踩过坑之后想分享的几点

回头看我近几年的项目,最常出问题的不是动画本身,而是“动画状态”和“业务状态”脱节。比如某个组件在动画播放中被外部更新了数据,新旧逻辑交错,界面和数据就对不上了。现在我把组件设计成:外部只能修改业务状态,动画完全由内部根据状态差异自动触发。这个边界一旦划清楚,组件基本不会出“神经病式”的乱动。

还有一个很实用的建议:在每个组件开发调试阶段,用一个全局开关来禁用所有动画(animation: none+transition: none)。这方便调试布局逻辑,也方便复测无动画场景下的表现。很多动画bug在无动画模式下会原形毕露,比如“数据变了但界面没更新”这类问题,根本不是动画的事,是状态同步bug,但常常被动画效果掩盖。

做组件动画这件事,说到底是“用工程方法做设计反馈”。上面这些方法覆盖了我日常工作中八成以上的场景。如果你正在写自定义组件,不妨先把“什么属性可以动”“状态变化时动画怎么衔接”“没了动画会不会出问题”这三个问题想清楚,再动手写代码。方向对了,动画效果就不会跑偏。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 16:56:56

域名申请的流程全解析:不懂代码怎么挑服务商哪家好

域名申请的流程全解析:不懂代码怎么挑服务商哪家好 自己不会代码想做网站,却卡在第一步:域名怎么买、流程怎么走、哪家服务商靠谱?别急,这不仅是技术门槛,更是很多老板的第一道坎。选对域名和建站路径,比写代码重要十倍。今天用10年实战经验,把域名申请的流程掰开揉碎讲清楚,顺带告诉你,当技术小白面对“哪家好…

作者头像 李华
网站建设 2026/9/15 16:56:42

无人机循迹与图像识别:STM32+OpenMV串口联调核心解析

简介&#xff1a;这是面向电赛无人机赛项的完整工程资料包&#xff0c;聚焦循迹、图形与颜色识别、串口通讯三大核心功能&#xff0c;适合参加电赛或有无人机自动驾驶学习需求的选手与开发者。工程包含19个文件&#xff0c;共15KB&#xff0c;以Python源码为主&#xff0c;辅以…

作者头像 李华
网站建设 2026/9/15 16:54:06

领域语料MLM继续预训练:从数据清洗到RoBERTa实战指南

如果你突然接到一个任务&#xff0c;要用公司内部积累了多年的工单语料训练一个NLP模型来做智能客服、舆情分析或者知识检索&#xff0c;你大概率会遇到一个尴尬的局面&#xff1a;开源的中文RoBERTa预训练模型在通用语料上表现不错&#xff0c;但一旦面对满屏的行业黑话、产品…

作者头像 李华
网站建设 2026/9/15 16:52:21

Unity DOTS万人同屏实战:ECS与Job System优化萌宠战场

做类萌宠宠之战这种游戏&#xff0c;最让我上头的画面不是什么高清大世界&#xff0c;而是战斗刚开的那个瞬间&#xff1a;上万只宠物从四面八方涌向同一个目标&#xff0c;屏幕上密密麻麻全是单位&#xff0c;压迫感直接拉满。但这份压迫感首先压垮的是程序员自己。我第一版原…

作者头像 李华