Is there a fluid CSS height property for elements entering the DOM? Jank happens when using Semantic-UI-React Transitions

387 Views Asked by At

I am not sure if this is the proper place to ask this question; I considered Software Recommendations however I figured I'd give it a shot here first.

I am creating a PWA and with every iteration I'd really like to focus on making it look/feel like less janky and as smooth as possible. Well, like a native app.

So here is the problem—I am using transitions (animations) provided by Semantic-UI-React, and while it works beautifully when an element which is occupying its on space, I was wondering if there a practice or pattern which handles the behavior of adjacent DOM elements to one that is being animated?

I supplied a GIF below to illustrate what I mean. I suppose I could just move the animated elements out of the container of the form to prevent jank, but I wondered if there was a more elegant pattern or approach to this...

Merci!

enter image description here

1

There are 1 best solutions below

1
On BEST ANSWER

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:

  1. 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).

  2. 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.

  3. 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.