導入アニメーションの概念は非常に幅広く、さまざまな分野に関係します。ここでは、ゲーム分野の Animate はもちろんのこと、フロントエンド Web アプリケーション レベルに範囲を絞ります。まずは最も単純なものから始めましょう。 現在、ほとんどの Web アプリケーションは、Vue、React などのフレームワークに基づいて開発されています。これらはすべて、データ駆動型のビューに基づいています。そこで、これらのフレームワークが存在しなかったときにアニメーションやトランジション効果をどのように実装したかと、データ駆動型を使用してそれらを実現する方法を比較してみましょう。 従来のトランジションアニメーションアニメーション効果はエクスペリエンスにおいて非常に重要な役割を果たしますが、多くの開発者にとっては非常に弱いリンクとなる可能性があります。 CSS3 の登場以降、多くの初心者にとって最もよく使用されるアニメーション遷移は CSS3 の機能であると考えられます。 CSS トランジションアニメーションCSSでトランジションアニメーションを開始するのは非常に簡単です。トランジション属性を記述するだけです。デモはこちらです <div id="app" class="normal"></div> 。普通 { 幅: 100ピクセル; 高さ: 100px; 背景色: 赤; 遷移: すべて 0.3 秒; } .normal:hover { 背景色: 黄色; 幅: 200ピクセル; 高さ: 200px; } 効果は依然として非常に良好です。CSS3 トランジションは基本的にほとんどのアニメーション要件を満たしています。満たされない場合は、実際の CSS3 アニメーションもあります。 アニメーション-CSS 有名な CSS アニメーション ライブラリ。使用している人なら誰でも知っています。 CSS3 トランジションでも CSS3 アニメーションでも、クラス名を切り替えるだけで使用できます。コールバック処理をしたい場合は、ブラウザも ontransitionend や onanimationend などのアニメーション フレーム イベントを提供しており、JS インターフェイスを通じてリッスンできます。 var el = document.querySelector('#app') el.addEventListener('transitionstart', () => { console.log('遷移開始') }) el.addEventListener('transitionend', () => { console.log('遷移終了') }) さて、これが CSS アニメーションの基礎です。ほとんどのアニメーション遷移要件は JS カプセル化によっても実現できますが、CSS でサポートされている属性アニメーションしか制御できないという制限があり、制御は比較的弱いです。 jsアニメーション結局のところ、js はカスタム コーディング プログラムなので、アニメーションを強力に制御し、CSS ではサポートされていないさまざまな効果を実現できます。 では、js でアニメーションを実装するための基礎は何でしょうか? <div id="app" class="normal"></div> // Tween は単なるイージング関数です var el = document.querySelector('#app') var time = 0、begin = 0、change = 500、duration = 1000、fps = 1000 / 60; 関数 startSport() { var val = Tween.Elastic.easeInOut(時間、開始、変更、継続時間); el.style.transform = 'translateX(' + val + 'px)'; 時間 <= 継続時間の場合 時間 += fps } それ以外 { console.log('アニメーションが終了し、再開します') 時間 = 0; } タイムアウトを設定する(() => { スポーツを始める() }, fps) } スポーツを始める() タイムライン上のプロパティを継続的に更新するには、setTimeout または requestAnimation を使用します。 Tweenイージング関数は補間の概念に似ています。一連の変数を与えると、その間隔内の任意の時点で値を取得できます。これは純粋な数式であり、ほぼすべてのアニメーションフレームワークで使用されています。詳しく知りたい場合は、Zhang XinxuのTween.jsを参照してください。 さて、このミニマリスト デモは、js アニメーションのコア基盤でもあります。プログラムを通じて遷移値の生成プロセスを完全に制御していることがわかります。他のすべての複雑なアニメーション メカニズムはこのパターンに従います。 従来のフレームワークと Vue/React フレームワークの比較これまでの例では、CSS トランジションでも JS トランジションでも、DOM 要素を直接取得し、DOM 要素に対して属性操作を実行します。 Vue フレームワークでのトランジションアニメーションまずは文書を読んでみてください Vue トランジションアニメーション 使い方については説明しません。Vue が提供するトランジション コンポーネントがアニメーション トランジション サポートをどのように実装しているかを分析しましょう。 遷移コンポーネントまず、遷移コンポーネントのコード(パス「src/platforms/web/runtime/components/transition.js」)を確認します。 // 補助関数、propsデータをコピーするエクスポート関数 extractTransitionData (comp: Component): Object { 定数データ = {} const オプション: ComponentOptions = comp.$options // 小道具 for (const キー in options.propsData) { データ[キー] = comp[キー] } // イベント。 定数リスナー: ?Object = options._parentListeners for (const キー in リスナー) { data[camelize(キー)] = listeners[キー] } データを返す } エクスポートデフォルト{ 名前: 'transition'、 プロパティ: transitionProps、 abstract: true, // 抽象コンポーネント。DOM にレンダリングされないことを意味し、開発に役立ちます render (h: Function) { // スロットを通じて実際のレンダリング要素の子要素を取得します 子: any = this.$slots.default とします。 定数モード: 文字列 = this.mode 定数rawChild: VNode = children[0] // 一意のキーを追加する // コンポーネントインスタンス。このキーは保留中の離脱ノードを削除するために使用されます // 入力中。 定数 id: 文字列 = `__transition-${this._uid}-` 子.key = getKey(id) : 子.キー // props を通じて渡されたデータを保存するため、transition 属性をデータに挿入します。const data: Object = (child.data || (child.data = {})).transition = extractTransitionData(this) const oldRawChild: VNode = this._vnode 定数 oldChild: VNode = getRealChild(oldRawChild) // 動的な遷移にとって重要です! const oldData: オブジェクト = oldChild.data.transition = extend({}, data) // 遷移モードを処理する if (mode === 'out-in') { // プレースホルダーノードを返し、終了時にキューの更新を行う this._leaving = true mergeVNodeHook(oldData, 'afterLeave', () => { this._leaving = false this.$forceUpdate() }) プレースホルダー(h, rawChild)を返す } それ以外の場合 (モード === 'in-out') { 遅延させる const performLeave = () => { delayedLeave() } mergeVNodeHook(データ、'afterEnter'、performLeave) mergeVNodeHook(データ、'enterCancelled'、performLeave) mergeVNodeHook(oldData、'delayLeave'、leave => { delayedLeave = leave }) } rawChildを返す } } ご覧のとおり、このコンポーネント自体の機能は比較的単純です。スロットを通じてレンダリングする必要がある要素の子要素を取得し、transition の props 属性データを、ライフサイクルへの後続の挿入のために data の transition 属性にコピーします。ライフサイクル管理には mergeVNodeHook が使用されます。 モジュール/移行次に、ライフサイクル関連のパスを確認します。 関数 _enter (_: 任意、vnode: VNodeWithData) { (vnode.data.show !== true) の場合 { 入力(vnode) } } デフォルトの inBrowser をエクスポートしますか? { 作成: _enter、 アクティブ化: _enter、 削除 (vnode: VNode、rm: 関数) { (vnode.data.show !== true) の場合 { 退出(vnode, rm) } } } : {} ここではブラウザ環境を分析しているため、inBrowser は true と見なされます。 エクスポート関数 addTransitionClass (el: any, cls: string) { const transitionClasses = el._transitionClasses || (el._transitionClasses = []) (遷移クラスのインデックス(cls)<0)の場合){ 遷移クラス.push(cls) クラスを追加します(el, cls) } } エクスポート関数removeTransitionClass(el: any, cls: string) { if (el._transitionClasses) { 削除(el._transitionClasses、cls) } クラスを削除します(el, cls) } エクスポート関数 enter (vnode: VNodeWithData, toggleDisplay: ?() => void) { 定数 el: 任意 = vnode.elm // 今すぐLeaveコールバックを呼び出す if (isDef(el._leaveCb)) { el._leaveCb.cancelled = true el._leaveCb() } // 前のステップでデータに注入された遷移データ const data = resolveTransition(vnode.data.transition) if (isUndef(データ)) { 戻る } /* イスタンブールは無視します */ if (isDef(el._enterCb) || el.nodeType !== 1) { 戻る } 定数{ css、 タイプ、 入力クラス、 クラスに入る、 アクティブクラスを入力、 表示クラス、 表示されるクラス、 アクティブクラスを表示、 入る前、 入力、 入力後、 キャンセルされました、 前に現れる、 現れる、 出現後、 表示キャンセル、 間隔 } = データ コンテキストをactiveInstanceにする transitionNode = activeInstance.$vnode とします。 const isAppear = !context._isMounted || !vnode.isRootInsert if (isAppear && !appear && appear !== '') { 戻る } // 適切なタイミングで注入するクラス名を取得します const startClass = isAppear && appearClass ?表示クラス :クラスを入力 定数 activeClass = isAppear && appearActiveClass ?appearActiveClass :アクティブクラスを入力 定数 toClass = isAppear && appearToClass ?クラスに現れる :クラスに入る 定数 beforeEnterHook = isAppear ? (表示される前 || 入力する前) : 前に入る const enterHook = isAppear ? (typeof appear === 'function' ? appear : enter) : 入力 定数 afterEnterHook = isAppear ? (afterAppear || afterEnter) :afterEnter const enterCancelledHook = isAppear ? (キャンセル表示 || キャンセル入力) :enterキャンセル 定数明示的入力期間: 任意 = toNumber( isObject(期間) ? 期間.入力 : 間隔 ) const expectsCSS = css !== false && !isIE9 const userWantsControl = getHookArgumentsLength(enterHook) // 遷移終了後のコールバック処理、遷移時にクラスを削除する const cb = el._enterCb = once(() => { if (expectsCSS) { トランジションクラスの削除(el, toClass) トランジションクラスの削除(el, アクティブクラス) } cb.cancelledの場合{ if (expectsCSS) { トランジションクラスの削除(el, 開始クラス) } キャンセルされたフックを入力 && キャンセルされたフックを入力(el) } それ以外 { afterEnterHook && afterEnterHook(el) } el._enterCb = null }) // DOM に入るときに、遷移の開始クラスを追加します beforeEnterHook && beforeEnterHook(el) if (expectsCSS) { // トランジションが始まる前にデフォルトのスタイルを設定します addTransitionClass(el, startClass) トランジションクラスを追加します(el、アクティブクラス) // ブラウザは次のフレームをレンダリングし、デフォルトのスタイルを削除してtoClassを追加します // 終了イベントリスナーを追加します。コールバックは上記の cb です。 次のフレーム(() => { トランジションクラスの削除(el, 開始クラス) キャンセルされた場合 遷移クラスを追加します(el, toClass) if (!userWantsControl) { if (isValidDuration(explicitEnterDuration)) { setTimeout(cb、明示的なEnterDuration) } それ以外 { 遷移終了時(el, タイプ, cb) } } } }) } (vnode.data.show) の場合 { トグルディスプレイ && トグルディスプレイ() エンターフック && エンターフック(el, cb) } if (!expectsCSS && !userWantsControl) { 関数 } } Enter は、実際に遷移またはアニメーションの終了イベントを監視する whenTransitionEnds 関数を使用します。 エクスポート let transitionEndEvent = 'transitionend' エクスポート let animationEndEvent = 'animationend' エクスポート関数 whenTransitionEnds ( el: 要素、 予想される型: ?文字列、 cb: 機能 ){ const { type, timeout, propCount } = getTransitionInfo(el, expectedType) if (!type) が cb() を返す const イベント: 文字列 = type === TRANSITION ? transitionEndEvent : animationEndEvent 終了 = 0 定数終了 = () => { el.removeEventListener(イベント、onEnd) cb() } 定数onEnd = e => { (e.target === el)の場合{ if (++ended >= propCount) { 終わり() } } } タイムアウトを設定する(() => { if (終了 < propCount) { 終わり() } }, タイムアウト + 1) el.addEventListener(イベント、onEnd) } さて、上のソースコードのコメント分析によると、次のことがわかります。
leave のプロセスは、className が逆に追加および削除されることを除いて、enter のプロセスと同じです。 結論:Vue のアニメーション遷移処理方法は、基本的に従来の DOM と同じですが、Vue のさまざまなライフサイクルに統合されて処理されます。本質的には、DOM が追加または削除されたときにも処理されます。 Reactのトランジションアニメーションああ、React のドキュメントをざっと見たところ、遷移アニメーションの処理は見つかりませんでした。ねえ、ネイティブでは公式にサポートされていないようです。 しかし、useState を通じて状態を保持したり、render で状態に応じて className を切り替えたりと、自分で実装することはできますが、複雑な場合はどうすればよいでしょうか。 幸運なことに、コミュニティでホイールプラグインの反応遷移グループを見つけました クラスTransitionはReact.Componentを拡張します。 静的コンテキストタイプ = TransitionGroupContext コンストラクタ(props, context) { super(プロパティ、コンテキスト) 親グループ = コンテキスト 出現させる = 親グループ && !parentGroup.isMounting ? props.enter : props.appear 初期ステータス this.appearStatus = null もし(props.in){ (現れる)場合{ 初期ステータス = 終了 this.appearStatus = 入力中 } それ以外 { 初期ステータス = 入力済み } } それ以外 { props.unmountOnExit の場合 || props.mountOnEnter の場合 { 初期ステータス = マウント解除 } それ以外 { 初期ステータス = 終了 } } this.state = { ステータス: 初期ステータス } this.nextCallback = null } // DOMを初期化するときに、デフォルトの初期状態を更新します。componentDidMount() { this.updateStatus(true、this.appearStatus) は、 } // データが更新されたら、対応する状態を更新します。componentDidUpdate(prevProps) { nextStatus = null とする if (prevProps !== this.props) { const { ステータス } = this.state if (this.props.in) { if (ステータス !== 入力中 && ステータス !== 入力済み) { nextStatus = 入力中 } } それ以外 { if (ステータス === 入力中 || ステータス === 入力済み) { nextStatus = 終了 } } } this.updateStatus(false、nextStatus) は、 } updateStatus(マウント = false、nextStatus) { if (nextStatus !== null) { // nextStatus は常に ENTERING または EXITING になります。 this.cancelNextCallback() if (nextStatus === ENTERING) { this.performEnter(マウント) } それ以外 { this.performExit() } } そうでない場合 (this.props.unmountOnExit && this.state.status === EXITED) { this.setState({ ステータス: マウント解除 }) } } performEnter(マウント) { const { 入力 } = this.props const 出現 = this.context ? this.context.isMounting : マウント const [maybeNode, maybeAppearing] = this.props.nodeRef ? [登場] : [ReactDOM.findDOMNode(this)、出現] 定数タイムアウト = this.getTimeouts() const enterTimeout = 表示される? timeouts.appear: timeouts.enter // ENTERアニメーションなし ENTEREDへ右にスキップ // これをマウントして実行する場合、appear を設定する必要があります if ((!マウント && !enter) || config.disabled) { this.safeSetState({ ステータス: ENTERED }, () => { this.props.onEntered(おそらくNode) }) 戻る } this.props.onEnter(おそらくノード、おそらく表示される) this.safeSetState({ ステータス: ENTERING }, () => { this.props.onEntering(おそらくノード、おそらく表示される) this.onTransitionEnd(enterTimeout, () => { this.safeSetState({ ステータス: ENTERED }, () => { this.props.onEntered(おそらくノード、おそらく表示される) }) }) }) } 終了する() { const { exit } = this.props 定数タイムアウト = this.getTimeouts() const maybeNode = this.props.nodeRef ? 未定義 : ReactDOM.findDOMNode(これ) // 終了アニメーションなし、EXITED へすぐにスキップ if (!exit || config.disabled) { this.safeSetState({ ステータス: EXITED }, () => { this.props.onExited(おそらくNode) }) 戻る } this.props.onExit(おそらくNode) this.safeSetState({ ステータス: 終了 }, () => { this.props.onExiting(おそらくNode) this.onTransitionEnd(timeouts.exit, () => { this.safeSetState({ ステータス: EXITED }, () => { this.props.onExited(おそらくNode) }) }) }) } キャンセルネクストコールバック() { if (this.nextCallback !== null) { this.nextCallback.cancel() this.nextCallback = null } } safeSetState(nextState, コールバック) { // これは必要ないはずですが、奇妙な競合状態が発生します // テストではsetStateコールバックとアンマウントが実行される可能性があるため、常に // アンマウント後に保留中の setState コールバックをキャンセルできます。 コールバック = this.setNextCallback(コールバック) this.setState(nextState, コールバック) } setNextCallback(コールバック) { アクティブ = true にする this.nextCallback = イベント => { (アクティブ)の場合{ アクティブ = 偽 this.nextCallback = null コールバック(イベント) } } this.nextCallback.cancel = () => { アクティブ = 偽 } this.nextCallback を返す } // 遷移終了をリッスンする onTransitionEnd(タイムアウト、ハンドラー) { this.setNextCallback(ハンドラ) 定数ノード = this.props.nodeRef ? this.props.nodeRef.current : ReactDOM.findDOMNode(これ) const タイムアウトまたはリスナーがない = タイムアウト == null && !this.props.addEndListener if (!node || タイムアウトまたはリスナーがない場合) { setTimeout(this.nextCallback, 0) 戻る } if (this.props.addEndListener) { const [maybeNode, maybeNextCallback] = this.props.nodeRef ? [this.nextCallback] : [ノード、this.nextCallback] this.props.addEndListener(おそらくNode、おそらくNextCallback) } タイムアウトが null の場合 setTimeout(this.nextCallback、タイムアウト) } } 与える() { 定数ステータス = this.state.status if (ステータス === マウント解除) { nullを返す } 定数{ 子供たち、 // `Transition` のフィルタープロパティ 中: _in, マウントオンEnter: _マウントオンEnter、 アンマウント時終了: _unmountOnExit, 表示される: _表示される, 入力: _enter, 終了: _exit, タイムアウト: _timeout、 終了リスナーを追加: _終了リスナーを追加、 オンエンター: _オンエンター、 入力時: _入力時、 入力時: _onEntered, 終了時: _onExit、 終了時: _onExiting、 終了時: _onExited, ノード参照: _nodeRef, ...子プロパティ } = this.props 戻る ( // ネストされた遷移を可能にする <TransitionGroupContext.Provider 値 = {null}> {typeof children === 'function' ? 子(ステータス、子プロパティ) : React.cloneElement(React.Children.only(children), childProps)} </TransitionGroupContext.Provider> ) } } ご覧のとおり、React のさまざまなライフサイクル関数で処理される点を除けば、Vue と非常によく似ています。 この時点で、Vue トランジション コンポーネントと React トランジション グループ コンポーネントの両方が CSS 属性のアニメーションに重点を置いていることがわかります。 データ駆動型アニメーション実際のシーンでは、CSS では処理できないアニメーションに必ず遭遇します。現時点では、2 つの解決策があります。 ref を通じて dom を取得し、従来の js ソリューションを採用します。 上記は、フロントエンドにアニメーショントランジション効果を実装する方法の詳細です。フロントエンドにアニメーショントランジション効果を実装する方法の詳細については、123WORDPRESS.COMの他の関連記事に注目してください。 以下もご興味があるかもしれません:
|
<<: MySQL パーティションテーブルの制限と制約の詳細な説明
>>: Linux システムで複数のバージョンの PHP を共存させるソリューション (超シンプル)
Linux サーバーで作業している場合、ネットワーク カード/イーサネット カードに静的 IP アド...
序文Javaプログラミングでは、ほとんどのアプリケーションはMavenに基づいて構築されており、配信...
背景プロジェクトにはメニューノードのすべてのノードをチェックする要件があります。オンラインでチェック...
Raspberry Pi モデルは 4b、1G RAM です。システムはubuntu19.10サーバ...
最近のプロジェクトに取り組んでいるとき、下の図に示すように、画像を参照すると常に下部に空白スペースが...
では、早速レンダリングを見てみましょう。 コア コードはtransition: cubic-bezi...
1. キャンセル ボタンが押されたときの破線ボックス<br /> 入力に属性値 hide...
Redisの本やSpring Cloud Alibabaの本を執筆した際に、一部の分散コンポーネント...
目次1. MySQL 結合バッファ2. JoinBufferCacheストレージスペースの割り当て3...
ごみ箱機能をオンにすると、削除されたファイルの元のデータをタイムアウトなしで復元できるため、誤って削...
mysql 8.0.22 winx64のインストールと設定のグラフィックチュートリアルは参考までに、...
この記事では、5つ星の評価を獲得するためのJSの具体的なコードを参考までに共有します。具体的な内容は...
Nginxの仕組みNginx はコアとモジュールで構成されています。 Nginx 自体は実際にはほと...
数日前に CentOS8 がリリースされました。8 の最初のバージョンですが、今日は VM12 に ...
序文多くの場合、ユーザーが自分のデータに対して実行する操作に基づいて何かを行う必要があります。たとえ...