Home > database >  React js dynamic z-index when click on a component
React js dynamic z-index when click on a component

Time:12-10

What i want to accomplish is when i click on a box, the previous box to be behind the one that is on top, for better reference please check the next code.

https://codesandbox.io/s/optimistic-payne-4644yf?file=/src/styles.css

Desired behavior:

  1. click on red box
  2. click on blue box

and the sequence from bottom to top would be: green,red,blue

I tried a lot of ways but im keep messing up the code, so any help will be welcomed.

CodePudding user response:

do you mean something like this?

const { useState, useEffect } = React

const Test = () => {
  const [data, setData] = useState([
    { id: 1, label: "box 1", class: "box1", z: 0 },
    { id: 2, label: "box 2", class: "box2", z: 1 },
    { id: 3, label: "box 3", class: "box3", z: 2 }
  ]);
  
  const handleClickBox = id => {
    setData(p => {
      let tmpArr = [...p];
      tmpArr = tmpArr.sort((a) => a.id - (id   1)).reverse().map((ta, i) => ({ ...ta, z: i })).sort((a, b) => a.id - b.id);
      return tmpArr;
    })
  }

  return <div className="box-wrapper">
        {data.map((d, i) => {
          return (
            <div
              className={d.class}
              key={d.id}
              style={{ left: i * 100, zIndex: d.z }}
              onClick={() => handleClickBox(d.id)}
            >
              {d.label}
            </div>
          );
        })}
      </div>
}

ReactDOM.createRoot(
    document.getElementById("root")
).render(
    <Test />
);
.box-wrapper {
  position: relative;
}

.box1,
.box2,
.box3 {
  position: absolute;
  width: 300px;
  height: 150px;
  color: white;
  top: 0;
}

.box1 {
  background: green;
}
.box2 {
  background: red;
}
.box3 {
  background: blue;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

CodePudding user response:

When I was typing my response Layhout answered. That solution works, but mine is slightly different, you need to know the greatest value of zIndex.

import "./styles.css";
import { useState } from "react";

export default function App() {
  const [data, setData] = useState([
    { id: 1, label: "box 1", class: "box1", index: 0 }, // red
    { id: 2, label: "box 2", class: "box2", index: 1 }, // blue
    { id: 3, label: "box 3", class: "box3", index: 2 } // green
  ]);

  const handlePosition = (index, selectedIndex) =>
    index === selectedIndex ? 2 : index > selectedIndex ? index - 1 : index;

  const handleClick = (selectedIndex) => {
    // nothing happens if we click on the first item
    if (selectedIndex === 2) return;

    setData(
      data.map((i) => ({
        ...i,
        index: handlePosition(i.index, selectedIndex)
      }))
    );
  };

  return (
    <div className="App">
      <div className="box-wrapper">
        {data.map((b) => {
          return (
            <div
              className={b.class}
              key={b.id}
              style={{ zIndex: b.index }}
              onClick={() => handleClick(b.index)}
            >
              {b.label}
            </div>
          );
        })}
      </div>
    </div>
  );
}

Here is the full implementation on CodeSandbox.

The most important fact is zIndex of each object is also a UI state, so it needs to be in useState to change with user clicks. After this, you need to implement an algorithm to reorder items based on the clicked item. That is this function:

const handlePosition = (index, selectedIndex) =>
    index === selectedIndex ? 2 : index > selectedIndex ? index - 1 : index;

CodePudding user response:

It seems that the desired result may actually be a solution that handles z-index independently, without adding to the given data, and is capable of handling more than 3 div items if needed.

Here is a basic example that uses a state array activeList to handle the changes of z-index, so it is independent to data and can still work if data scales.

It uses the index of the state array to calculate z-index for each item. On click event, it pushes an item to the end of array (so it will have the highest z-index), as a lightweight approach to handle the re-order of z-index.

Forked live demo on: codesandbox

import "./styles.css";
import { useState } from "react";

export default function App() {
  const [activeList, setActiveList] = useState([]);

  const handleClick = (id) =>
    setActiveList((prev) => {
      const current = prev.filter((item) => item !== id);
      return [...current, id];
    });

  const data = [
    { id: 1, label: "box 1", class: "box1" },
    { id: 2, label: "box 2", class: "box2" },
    { id: 3, label: "box 3", class: "box3" }
  ];

  return (
    <div className="App">
      <div className="box-wrapper">
        {data.map((b) => {
          const activeIndex = activeList.findIndex((id) => id === b.id);
          const zIndex = activeIndex >= 0 ? activeIndex   1 : 0;
          return (
            <div
              className={b.class}
              key={b.id}
              style={{ zIndex }}
              onClick={() => handleClick(b.id)}
            >
              {b.label}
            </div>
          );
        })}
      </div>
    </div>
  );
}

Hope this could help.

  • Related