Unfortunately, there is no particularly elegant approach to this.
Regarding the question in the title of your post, a common approach is to animate the height
of the new elements. If you don't know the height of the elements, unfortunately animating from 0px
to auto
does not yet work but you can fudge it by animating max-height
from 0px
to a value slightly larger than the maximum size you expect the element to be.
But don't do that.
It will cause the browser to layout the page on every animation frame and will almost certainly jank on lower-end devices.
Instead, you're better off to animate transform
.
The most common approach is to:
Grab the original position of the elements (using getBoundingClientRect()
etc.) that are going to be affected just before you add the new elements to the DOM (perhaps using getSnapshotBeforeUpdate
unless you're using hooks, in which case you can use useRef
to similar effect).
After you've added the new elements (which you're presumably also animating in by using transform
with a suitable scale()
function), calculate the delta from where the offset elements are now, compared to where they used to be.
Setup a transform
animation from the negative of the delta, to zero (i.e. the FLIP approach). E.g. if the element has been shifted 300px down the page, animate transform
from translateY(-300px)
to none
.
Of course you need to do that for all the elements in the flow that are affected so it might be easiest to put them all in one container and just animate that.
Regarding the third point you have a few choices of technology, none of which are great:
CSS transitions. These are simplest but you'll have to trigger a style flush to get the negative delta starting point to stick. e.g.
elem.style.transform = `translateY(-${offset}px)`;
elem.style.transition = `transform .3s`;
getComputedStyle(elem).transform; // Flush style
elem.style.transform = `none`;
CSS animations. Producing @keyframes
rules dynamically using the CSSOM is a pain but at least you don't need to trigger a style flush (and you can set an animation with an implicit to-keyframe for convenience).
@keyframes random-id { from: { translateY(-300px); } }
Web animations. This is probably the most well-suited.
elem.animate({ transform: [ `translateY(-${offset}px)`, 'none' ] }, 300);
// In future when browsers ship support for implicit to/from keyframes:
elem.animate({ transform: `translateY(-${offset}px)`, offset: 0 }, 300);
Unfortunately Safari only has Web Animations support in Tech Preview so Safari users will get the un-animated version until the next Safari release. Edge users will also get the un-animated version until the Chromium based Edge ships. There is a polyfill too but it's not actively maintained at this point in time.
If you're using React, Animating the Unanimatable is a helpful article on all this.
The other tricky part is making sure the scroll doesn't jump for which you might need to look into scroll anchoring.