質問: material-uiの下記リンクスナックバーのようなものを作成しようとしています。 その際のアニメーションで、要素が削除されたときに縦に移動するアニメーションの実装方法がわからず困っています。 理想↓↓ ![画像の説明をここに入力](https://i.stack.imgur.com/swPw8.gif) 現実↓↓ このように削除されたときにガタッと要素が詰められてしまう ![画像の説明をここに入力](https://i.stack.imgur.com/bGHml.gif) 横のアニメーションは `transform: translateX(-250px);` -> `transform: translateX(0);` のように行っています。 どのような実装の方法があるでしょうか?思いつかず悩んでおります... 教えていただける方いらっしゃればよろしくお願いします🙏
回答: 左右にスライドするアニメーションが期待通りの動きであるならば、縦方向の動きも横方向と同様、 `transitionY` に適切な値を投入すれば上下方向のアニメーションができるはずです。 また、画面外に消えたスナックバーをDOMからremoveするのであれば、アニメーションが終わったタイミングを見計らう必要もあるでしょう。 質問文にコードが載っていないので実際どのように実装されているか定かではありませんが、比較的簡単に実現できる方法としては、 * スナックバーのpositionを `fixed`にする * 位置関係は`left` `bottom`で調整する * `setTimeout` を使って、スライドするアニメーションと画面に残ったスナックバーを下に下げるアニメーションの実行にディレイをかける という方針になるかと思います。 codepenでサンプルを作成したので、参考にしてください。おそらく現状のあなたの実装とは異なる部分が多いと思いますが、実装例の一つとして参考になるはずです。 ``` var snackbar_stack = []; // 画面上に出ているスナックバーを入れておく配列 var SNACKBAR_MAX = 3; //画面上に配置できるsnackbarの最大数 var SNACKBAR_HEIGHT = 50; // スナックバーのheight値 var snackbar_area = document.querySelector('div.snackbar-area'); function add_snackbar(){ let sb = document.querySelector('#snackbar-template').content.cloneNode(true); let outer = sb.querySelector('.snackbar-outer'); // console.log(outer); let shiftbar = null; // もし最大数を超えるスナックバーを追加する際、追い出されるスナックバーのNode let idx = snackbar_stack.push(outer) -1; // idxはスナックバー挿入位置 if(snackbar_stack.length > SNACKBAR_MAX){ // 最大数を超えるスナックバーがpushされている場合は、先頭を取り出しておく。 shiftbar = snackbar_stack.shift(); } // console.log(shiftbar); // スナックバーをDOMに追加 snackbar_area.appendChild(sb); // 追加するスナックバーの初期Y位置。 outer.style.bottom = `${idx * SNACKBAR_HEIGHT}px`; // 追加するスナックバーと追い出されるスナックバーのX位置をsetTimeoutで指定。 // こうすることでtransitionが有効になる。(ディレイ0でもOK) setTimeout(() => { // スライドアウトするほう if(shiftbar){ shiftbar.style.left = '-500px'; } // スライドインしてくる方 outer.style.left = `0px`; },0); // スナックバーのスライドイン、スライドアウトアニメが終了した後に残りのスナックバーを // 下方向にスライドさせる。ディレイ値にtransitionで指定したtransition-durationと同じ秒数を設定。 // 同じくtransitionが有効。 setTimeout( () => { // スライドアウトしたスナックバーはこのタイミングでDOMから削除すれば表示に影響はない。 if(shiftbar){ shiftbar.remove(); } // 各スナックバーのY方向を再設定。 snackbar_stack.forEach((e,i) => e.style.bottom = `${i * SNACKBAR_HEIGHT}px` ); }, 300); } ``` ``` .snackbar-outer{ /* positionは画面ベースで固定 */ position: fixed; display: flex; justify-content: center; align-items: center; margin: 5px; /* width, heightは具体的に指定しておく */ width: 200px; height: 40px; /* 挿入時の初期位置(X方向) */ left: -300px; /* Y方向の初期位置は状況により変わるので初期値は適当で良い */ bottom: 0; background-color: #333; color: #ddd; border-radius: 5px; box-shadow: 2px 2px 5px 1px #00000080; /* 比較的早目で、functionをease-inにすると軽く重力で落下するようなエフェクトになる */ transition: all .3s ease-in; .snackbar-inner{ width: 100%; text-align: center; } } ``` ``` add snackbar ```