ホームページ > 記事 > ウェブフロントエンド > トランジションについてご存知ですか?トランジションについてもっと学びましょう!
トランジションをご存知ですか?もしかしたら、トランジションについて知らないのですか?トランジションについては、以下の記事で画像と文章を組み合わせて詳しく解説していますので、お役に立てれば幸いです。
この記事では、トランジション
アニメーションについて詳しく学びます。そう、CSS3 Transition
アニメーションです。 「とてもシンプルではないですか? 何がそんなに話しやすいのですか?」と疑問に思われるかもしれません。
確かに、Transition
アニメーションは非常に使いやすいです。 transition-lay
、transition-duration
、transition-property
、transition-timing-function
属性を要素に追加するだけです。フィルタリング効果。より簡単な使用法は、短縮された transition
属性を直接使用することです:
transition: <property> <duration> <timing-function> <delay>; // transition-delay 默认为 0 // transition-property 默认为 all // transition-timing-function 默认为 ease transition: 0.3s;
トランジション アニメーションは使用するのにほとんど費用対効果が低いため、詳しくは調べていません。ソースコードと MDN ドキュメントを参照してください。その後、一部の知識が十分に理解されていないことが判明したため、読者がトランジション アニメーションについてより深く理解するのに役立つことを願ってこの記事を書きました。 (学習ビデオ共有: css ビデオ チュートリアル)
読解のコストをできる限り減らすために、この記事は少し冗長に書かれており、ほとんどの例は画像付きになります——【複数枚 警告開始! 】
簡単に言うとトランジションアニメーションで、通常は DOM ノードのスタイルがページ上で即座に更新されます。幅と高さ、透明度の変更、背景色の変更など。
たとえば、ボタンの上にマウスを移動すると、ボタンのインタラクティブ性を強調するために、ホバー時にボタンのスタイルが変更され、ユーザーがそれを認識できるようになります。トランジションアニメーションがないと、ユーザーは硬直した印象を与えてしまいます。
.button { // ... background-color: #00a8ff; } .button:hover { background-color: #fbc531; transform: scale(1.2); }
コードの遷移行を追加すると、変更がよりスムーズになります。
.button { // ... transition: 1s; } // ...
この例では、background-color
と transform
を transition
属性と組み合わせて変更しました。ブラウザは自動的に属性を変更します。 value 時間の変化に応じて、古い値から移行中の新しい値に徐々に移行します。これは視覚的にはアニメーション効果です。
は
Animation
、Transition
アニメーションとは区別され、最初から最後まで トランジション効果を表現することに重点を置いています。変化。アニメーションは変更する必要がなく、ループ再生可能です▶️。
すべての属性変更にトランジション効果があるわけではないことに注意してください
一部の CSS プロパティは、黒以外の列挙値のみをサポートします。と白の場合、中間状態はありません。たとえば、visibility:visible;
は visibility:hidden;
に変更されます。可視と白の間には中間状態がないため、アニメーション効果はありません。見えない。ブラウザー上のパフォーマンスでは、期間に達した直後に要素が非表示に変化します。
.button:hover { //... visibility: hidden; }
transition-delay
などの遷移効果を持たないように運命づけられています。 ,transition-duration
はすべてすぐに有効になります。ここで追加する価値があります。transition-*
属性はすぐに有効であるため、このコード行がホバー中にのみ追加された場合、その効果はアニメーション、移動中はアニメーションなし。 遷移可能な属性の変更であっても、中間状態を計算できないため、遷移効果が失われる可能性があります。たとえば、box-shadow
属性はトランジション アニメーションをサポートしていますが、「outset
」から inset
に切り替えると、これも mutation になります。 。
.button { // ... box-shadow: 0 0 0 1px rgb(0 0 0 / 15%); transition: 1s; } .button:hover { // ... box-shadow: inset 0 0 0 10px rgb(0 0 0 / 15%); }
height: 100px
=> height: auto
からのアニメーションはありません。 上記の内容では Transition の基本的な使い方を説明しましたが、実際の開発シナリオで遭遇する問題を見てみましょう。
シナリオの質問: カスタム ドロップダウン セレクターのアニメーション要件を受け取ったとします。デザイナーによって与えられたレンダリングは次のとおりです:
这是很常见的出现-消失动画,在很多组件库里面都会出现,点击触发器(按钮)时才在页面上渲染 Popup (下拉内容),并且 Popup 出现的同时需要有渐现和下滑的动画;展开之后再次点击按钮,Popup 需要渐隐和上滑。
平时使用的时候并没有过多注意它的实现,不妨现在让我们动手试验一下。
暂时忽略 popup 的内容,用了个 div 来占位模拟,HTML 结构很简单。
<div class="wrapper"> <div id="button"></div> <div id="popup"></div> </div>
在点击按钮的时候,让 popup 显示/隐藏,然后切换 popup
的 .active
类名。
const btn = document.querySelector("#button"); const popup = document.querySelector("#popup"); if (!popup.classList.contains("active")) { popup.style.display = "block"; popup.classList.add("active"); } else { popup.style.display = "none"; popup.classList.remove("active"); }
编写 CSS
样式,在不 active
时透明度设置为 0,向上偏移,active
时则不偏移且透明度设置为 1。
#popup { display: none; opacity: 0; transform: translateY(-8px); transition: 1s; &.active { opacity: 1; transform: translateY(0%); } }
完整代码 在这里,看起来代码没什么问题,点击按钮切换的时候,popup 应该会有动画过渡效果。然而实际运行效果:
硬邦邦地完全没有过渡效果,这是为啥?明明已经设置了 transition
,且 opacity
和 translateY
都是可计算可过渡的数值,也产生了变化,浏览器为什么不认呢?
在查文档之前,我们先尝试使用万精油 setTimeout
。
方案一:setTimeout 万精油
修改 JS
代码:
btn.addEventListener("click", () => { if (!popup.classList.contains("active")) { popup.style.display = "block"; setTimeout(() => { popup.classList.add("active"); }, 0); } else { popup.classList.remove("active"); setTimeout(() => { popup.style.display = "none"; }, 600); } });
可以看到添加了 setTimeout
之后,transition
动画就生效了。
隐藏时的 setTimeout 600ms
对应 CSS 中设置的 transition: 0.6s
,就是动画完成之后才将 display
设置为 none
。
主要困惑的点在于为什么显示的时候也需要加 setTimeout
呢?setTimeout 0
在这里起到的作用是什么?带着问题去翻看规范文档。
在规范文档的 Starting of transitions 章节找到下面这段话:
When a style change event occurs, implementations must start transitions based on the computed values that changed in that event. If an element is not in the document during that style change event or was not in the document during the previous style change event, then transitions are not started for that element in that style change event.
翻译一下,当样式变更事件发生时,实现(浏览器)必须根据变更的属性执行过渡动画。但如果样式变更事件发生时或上一次样式变更事件期间,元素不在文档中,则不会为该元素启动过渡动画。
结合浏览器构建 RenderTree 的过程,我们可以很清晰地定位到问题:当样式变更时间发生时,display: none
的 DOM 元素并不会出现在 RenderTree 中(style.display='block'
不是同步生效的,要在下一次渲染的时候才会更新到 Render Tree),不满足 Starting of transitions 的条件。
所以 setTimeout 0
的作用是唤起一次 MacroTask,等到 EventLoop 执行回调函数时,浏览器已经完成了一次渲染,再加上 .active
类名,就有了执行过渡动画的充分条件。
优化方案二:精准卡位 requestAnimationFrame
既然目的为了让元素先出现到 RenderTree 中,和渲染相关,很容易想到可以将 setTimeout
替换成 requestAnimationFrame
,这样会更精准,因为 requestAnimation 执行时机和渲染有关。
if (!popup.classList.contains("active")) { popup.style.display = "block"; requestAnimationFrame(() => { popup.classList.add("active"); }); }
补充一个小插曲:在查找资料的过程中了解到 requestAnimationFrame 的规范是要求其回调函数在 Style/Layout 等阶段之前执行,起初 Chrome 和 Firefox 是遵循规范来实现的。而 Safari 和 Edge 是在执行的时机是在之后。 从现在的表现上来看,Chrome 和 Firefox 也改成了在之后执行,翻看以前的文档会说需要嵌套两层 requestAnimationFrame,现在已经不需要了。Is requestAnimationFrame called at the right point?
优化方案三:Force Reflow
在规范文档中,还留意到以下这句话:
Implementations typically have a style change event to correspond with their desired screen refresh rate, and when up-to-date computed style or layout information is needed for a script API that depends on it.
意思是说,浏览器通常还会在两种情况下会产生样式变更事件,一是满足屏幕刷新频率(不就是 requestAnimationFrame?),二是当 JS 脚本需要获取最新的样式布局信息时。
在 JS 代码中,有些 API
被调用时,浏览器会同步地计算样式和布局,频繁调用这些 API(offset*/client*/getBoundingClientRect/scroll*/...等等)通常会成为性能瓶颈。
然而在这个场景却可以产生奇妙的化学反应:
if (!popup.classList.contains("active")) { popup.style.display = "block"; popup.scrollWidth; popup.classList.add("active"); }
注意看,我们只是 display 和 add class 之间读取了一下 scrollWidth,甚至没有赋值,过渡动画就活过来了。
原因是 scrollWidth
强制同步触发了重排重绘,再下一行代码时,popup 的 display 属性已经更新到 Render Tree 上了。
优化方案四:过渡完了告诉我 onTransitionEnd
现在【出现】动画已经搞明白了,在看开源库的源码中发现像 vue, bootstrap, react-transition-group 等库都是使用了 force reflow 的方法,而 antd 所使用的 css-animte 库则是通过设置 setTimeout。
【消失】动画还不够优雅,前面我们是直接写死 setTimeout 600
,让元素在动画结束时消失的。这样编码可复用性差,修改动画时间还得改两处地方(JS + CSS),有没有更优雅的实现?
popup.classList.remove("active");setTimeout(() => { popup.style.display = "none"; }, 600);
文档中也提到了 Transition Events,包括 transitionrun
,transitionstart
,transitionend
,transitioncancel
,看名字就知道事件代表什么意思,这里可以用 transitionend
进行代码优化。
if (!popup.classList.contains("active")) { popup.style.display = "block"; popup.scrollWidth; popup.classList.add("active"); } else { popup.classList.remove("active"); popup.addEventListener('transitionend', () => { popup.style.display = "none"; }, { once: true }) }
需要注意 transition events
同样也有冒泡、捕获的特性,如果有嵌套 transition 时需要留意 event.target
。
到这里我们已经用原生 JS 完成了一个出现、消失的动画实现,完整的代码在这里。文章的最后,我们参照 vue-transition
来开发一个 React Transition 的单个元素动画过渡的最小实现。
根据动画过程拆分成几个过程:
*-enter
类名)*-enter-active
类名)*-enter-active
类名)enter-to 和 leave-to 暂时用不上,leave 阶段和 enter 基本一致也不再赘述。
直接看代码:
export const CSSTransition = (props: Props) => { const { children, name, active } = props; const nodeRef = useRef<HTMLElement | null>(null); const [renderDOM, setRenderDOM] = useState(active); useEffect(() => { requestAnimationFrame(() => { if (active) { setRenderDOM(true); nodeRef.current?.classList.add(`${name}-enter`); // eslint-disable-next-line @typescript-eslint/no-unused-expressions nodeRef.current?.scrollWidth; nodeRef.current?.classList.remove(`${name}-enter`); nodeRef.current?.classList.add(`${name}-enter-active`); nodeRef.current?.addEventListener("transitionend", (event) => { if (event.target === nodeRef.current) { nodeRef.current?.classList.remove(`${name}-enter-active`); } }); } else { nodeRef.current?.classList.add(`${name}-leave`); // eslint-disable-next-line @typescript-eslint/no-unused-expressions nodeRef.current?.scrollWidth; nodeRef.current?.classList.remove(`${name}-leave`); nodeRef.current?.classList.add(`${name}-leave-active`); nodeRef.current?.addEventListener("transitionend", (event) => { if (event.target === nodeRef.current) { nodeRef.current?.classList.remove(`${name}-leave-active`); setRenderDOM(false); } }); } }); }, [active, name]); if (!renderDOM) { return null; } return cloneElement(Children.only(children), { ref: nodeRef }); };
这个组件接收三个 props,分别是
使用方式:
<CSSTransition name="fade" active={active}> // 一个需要做过渡动画的 ReactElement </CssTransition>
借助 transition-delay
,加一点技巧实现 stagger 效果:
完整的示例代码在这里,注意:这只是个快速实现用于演示的示例,有非常多的问题没有考虑在内,仅可用于学习参考。
原本以为非常基础简单的知识点,分分钟可以写完这篇文章。没想到中途查文档,看资料,制作演示 DEMO 还是花了不少时间。好在整理资料的过程中也理清了很多知识点。希望这篇文章对你熟悉 Transition 动画有所帮助 。
相关推荐:web前端入门视频
以上がトランジションについてご存知ですか?トランジションについてもっと学びましょう!の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。