Selective stacking context for deeply nested child elements

111 Views Asked by At

Consider the following example app, which produces an output that looks like this:

Sandbox Here

enter image description here

JS (React)

export default function App() {
  return (
    <>
      <div className="container">
        <div className="animation">
          <div className="scrollable">
            {Array(10)
              .fill(0)
              .map((_, i) => (
                <div className={`box ${i % 2 ? "" : "top"}`} />
              ))}
          </div>
        </div>
      </div>
      <div className="overlay" />
    </>
  );
}

CSS

.animation {
  /* transform: scale(0.95); */
}

.scrollable {
  display: flex;
}

.box {
  width: 100px;
  height: 100px;
  margin-right: 10px;
  flex-shrink: 0;
  background: fuchsia;
}

.box.top {
  z-index: 10;
}

.overlay {
  position: fixed;
  width: 100%;
  height: 100%;
  top: 0px;
  left: 0px;
  background: rgba(0, 0, 0, 0.5);
}

We have a position: fixed overlay on top of a scrollable collection of pink boxes. The scrollable flex container is wrapped in an .animation class, which we want to apply a transform to (like transform: scale(0.95).

If we uncomment the transform line, all the boxes appear below the overlay, like so:

enter image description here

We can add position: absolute and z-index: 10 to the .animation class in order to place all the boxes on top of the overlay, but that isn't desired either. What we want is to allow some boxes to appear above the overlay, and others below, as shown in the first image above. We need to be able to manipulate the transform of the .animation class, so removing that is not an option.

Making the following adjustments on Safari does the trick, but does not seem to work on Chrome.

CSS

.animation {
  transform: scale(0.95);
  transform-style: preserve-3d;
}

.box.top {
  z-index: 10;
  transform: translateZ(10px);
}

Is it possible, without changing the HTML structure, to reliably achieve the desired effect?

You can find a Sandbox Here

1

There are 1 best solutions below

3
kennarddh On

You can use nth-child pseudo class.

You need to make overlay element and scrollable element in one parent.

.box:nth-child(2n - 1) {
  /* Select every 2 element start from first element */
  z-index: 10;
  position: relative;
}
<div className="container">
  <div className="overlay" />
  <div className="scrollable">
    {{Array(10)
        .fill(0)
        .map((_, i) => (
          <div className="box" />
    ))}
  </div>
</div>

Example: Code sandbox