Hidden Elements, Paul Hebert

If you use CSS transitions on elements that have the hidden property set or display: None declared, this is not an easy effect to implement. I encountered this problem many times and finally decided to write an NPM package to provide a reusable solution.

The problem

There are many ways to hide elements. For example, opacity, opacity, transform, position or even clip-path are used, but these properties sometimes fail to meet our expected effect. The element is hidden using the visibility: hidden, so that the space previously occupied is left blank, and the hidden effects of these attributes are visible and resolvable to the screen reader.

Declaring hidden elements with the hidden attribute or display: None does not have this problem. But these two hidden elements have an obvious drawback: we can’t apply transtion transitions to them. So how can we solve this problem? I ended up writing an NPM package to handle this problem and learned a lot about document flow, transtion events in the process. Share with you below.

Show and hide.drawer

Suppose you have a.drawer element in a web page that is offset using the CSS Transform property, moved outside the viewport, and then removed from the document flow using the hidden property.

(All of the examples in this article use the hidden property to hide elements in the same way as display: None. The NPM package supports both Settings.

<div class="drawer" hidden>Hello World!</div>

<style>
.drawer {
  transform: translateX(100%).transition: transform ease-out 0.3 s;
}

.drawer.is-open {
  transform: translateX(0);
}
</style>
Copy the code

Here’s what we want to achieve in the end (click here for the demo) :

How can we achieve slide-in and slide-out effects in this way, while still removing hidden elements from the document flow?

Continue to look at.

According to.drawer

Because the Hidden element is not in the document flow, you cannot use a transition effect on it. You can do this by removing the element’s hidden attribute and forcing the document to be reflow so that you can make the transition effect on the CSS properties. In the meantime, you need a little JS code to help you do this.

const drawer = document.querySelector('.drawer');

function show() {
  drawer.removeAttribute('hidden');

  /** * forces the browser to re-paint, then the browser knows that the * element is not 'hidden', and the transition works. * /
  const reflow = element.offsetHeight;

  // Triggers the CSS transition effect
  drawer.classList.add('is-open');
}
Copy the code

hidden.drawer

So far, we know how to show.drawers, but what about transitioning when elements are hidden? This ensures that the hidden element applies the hidden attribute again. The problem is, if we apply the hidden at the moment the element is hidden, the transition effect will not be visible. Instead, wait for the transition effect to finish before the Hidden element. There are two ways to implement this: TransitionEnd and setTimeout.

Transitionend event

The CSS transition End event is raised when the transition effect ends. In our example, the transition effect is automatically triggered by removing the.is-open class name. By using this event callback, we can add the hidden attribute to the element after the transition is complete.

Also, make sure that the event listener is removed after the transition is complete, otherwise when the element is displayed again, a previously bound event callback will be triggered, which is a problem. To do this, we can store the listener as a variable and remove it when the transition is complete:

const listener = (a)= > {
  // 3) Add the hidden attribute to the element
  drawer.setAttribute('hidden'.true);
  // 4) Removes the event listener for the element last
  drawer.removeEventListener('transitionend', listener);
};
// Hide the element
function hide() {
  // 1) Delete the.is-open class name
  drawer.classList.remove('is-open');
  // 2) After the transition effect ends, enter 3)
  drawer.addEventListener('transitionend', listener);
}
Copy the code

Note also that it is possible to click show again while the current.drawer is hiding, but not completely. In this case, we need to manually remove the event listener once more in the show() function above:

function show() {
  drawer.removeEventListener('transitionend', listener);

  / *... * /
}
Copy the code

transitionendIt’s a bubbling event

Transitionend is a bubbling event, so there is an edge case to consider when using the transitionEnd event: in the DOM, event bubbling is passed from child elements to ancestor elements. This can cause problems if the.drawer element contains child elements with transitional effects inside.

If the transition effect of a child element completes before that of the.drawer element, the transitionEnd event listener on that element fires prematurely due to event bubbling.

To avoid this problem, check in the event listener whether the target element that is currently triggering the transitionEnd event is.drawer. If yes, before executing the previous processing logic:

const listener = e= > {
  // The previous processing logic is executed only if the target element is.drawer
  if(e.target === drawer) {
    drawer.setAttribute('hidden'.true);
    drawer.removeEventListener('transitionend', listener); }};Copy the code

Wait using setTimeout

One limitation of using the transitionEnd event is that only one element can be handled for transition scenarios. If you want to achieve the following interlaced animation scene involving the transition effects of multiple elements, not suitable.

Click here to see the demo

Functions to be realized: Click the button in the upper right corner to display and hide the side menu with transition effect; Clicking on the menu link also triggers the hidden transition effect. This requires us to wait for the transition effects of all child elements to complete before adding the hidden property to the.drawer.

The initial processing is to force all child elements to wait until the transitionEnd event is triggered. However, if the user is switching quickly, there may be a problem: some child elements do not transition, and they will never trigger the transitionEnd event.

To solve this problem, we choose to use setTimeout instead of listening for the transitionEnd event:

function hide() {
  element.classList.remove(visibleClass);
  const timeoutDuration = 200;
  const timeout = setTimeout((a)= > {
    element.setAttribute('hidden'.true);
  }, timeoutDuration);
}
Copy the code

In addition, to avoid the previous scenario (click show while the menu is hidden), don’t forget to manually clear the timer in show() :

function show() {
  if (this.timeout) {
    clearTimeout(this.timeout);
  }

  / *... * /
}
Copy the code

The NPM package I eventually created provides support for both timeout and transitionEnd options so you can use them as needed.

Use NPM package

As you can see from the above analysis, the transition effects of hidden elements are a bit complicated to implement. My NPM package wraps the code used in both scenarios mentioned in this example in the transitionHiddenElement module.

The usage is as follows:

import { transitionHiddenElement } from '@cloudfour/transition-hidden-element';

const drawerTransitioner = transitionHiddenElement({
  element: document.querySelector('.drawer'),
  visibleClass: 'is-open'});/ / use
drawerTransitioner.show();
drawerTransitioner.hide();
drawerTransitioner.toggle();
Copy the code

I hope this program will be helpful to your projects. You can get it from NPM or check out the source on GitHub!

(End of text)


Advertising time (long term)

I have a good friend who owns a cattery, and I’m here to promote it for her. Now the cattery is full of Muppets. If you are also a cat lover and have the need, scan her FREE fish QR code. It doesn’t matter if you don’t buy it. You can just look at it.

(after)