skip to Main Content

I created a small reproduction of my code with which i have an issue. I wanted to create a functionality of small square element (.box) sliding to the center of selected answer. Currently its sliding wrong as its going completely different direction than it should. I assume i would need to use commented line // const boxRect = box.getBoundingClientRect(); and play with its position but tried several times without success.

The important thing is that the first slide must start from .box initial position defined in css. Then it should just change its position between selected answers.

const { useRef } = React;

const ANSWERS = ["answer 1", "answer 2", "answer 3", "answer 4"];

const App = () => {
   const boxRef = useRef(null);

  const handleSelectAnswer = (answer) => {
    const selectedAnswerElement = document.querySelector(
      `[data-answer="${answer}"]`
    );
    const box = boxRef.current;
    const answersContainer = document.querySelector(".answers");

    if (selectedAnswerElement && box && answersContainer) {
      const selectedAnswerRect = selectedAnswerElement.getBoundingClientRect();
      const containerRect = answersContainer.getBoundingClientRect();
      // const boxRect = box.getBoundingClientRect();

      const offsetX =
        selectedAnswerRect.left -
        containerRect.left +
        selectedAnswerRect.width / 2;

      const offsetY =
        selectedAnswerRect.top -
        containerRect.top +
        selectedAnswerRect.height / 2;

      box.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
    }
  };

  return (
    <div className="answers">
      {ANSWERS.map((answer) => (
        <button
          key={answer}
          data-answer={answer}
          onClick={() => handleSelectAnswer(answer)}
        >
          {answer}
        </button>
      ))}

      <div className="box" ref={boxRef} />
    </div>
  
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
.answers {
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(2, 270px);
  grid-template-rows: repeat(2, 1fr);
  column-gap: 1rem;
  row-gap: 1rem;
  color: white;
  position: relative;
  width: max-content;
}

.answers button {
  height: 150px;
}

.box {
  width: 50px;
  height: 50px;
  background-color: lightblue;
  position: absolute;
  bottom: -25%;
  left: 0;
  right: 0;
  margin: 0 auto;
  transition: all 0.5s ease;
  z-index: 999;
}
<div id="root">
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>

</div>

2

Answers


  1. I have removed default left, right and bottom css from your box elements so it will not cause any issue in dynamic position property in js and Also I have used state to store the x and y position of box element.
    Please check updated below code.

    const { useRef, useState, useEffect } = React;
    
    const ANSWERS = ["answer 1", "answer 2", "answer 3", "answer 4"];
    
    const App = () => {
       const boxRef = useRef(null);
         const [boxPosition, setBoxPosition] = useState({ x: 0, y: 0 });
    
    
      const handleSelectAnswer = (answer) => {
        const selectedAnswerElement = document.querySelector(
          `[data-answer="${answer}"]`
        );
        const box = boxRef.current;
        const answersContainer = document.querySelector(".answers");
    
        if (selectedAnswerElement && box && answersContainer) {
          const selectedAnswerRect = selectedAnswerElement.getBoundingClientRect();
          const containerRect = answersContainer.getBoundingClientRect();
          const boxRect = box.getBoundingClientRect();
    
           const offsetX = selectedAnswerRect.left - containerRect.left;
          const offsetY = selectedAnswerRect.top - containerRect.top;
    
          const boxOffsetX = offsetX + selectedAnswerRect.width / 2 - boxRect.width / 2;
          const boxOffsetY = offsetY + selectedAnswerRect.height / 2 - boxRect.height / 2;
          
          setBoxPosition({ x: boxOffsetX, y: boxOffsetY });
        }
      };
      
       useEffect(() => {
        handleSelectAnswer(ANSWERS[0])
      },[])
    
      return (
        <div className="answers">
          {ANSWERS.map((answer) => (
            <button
              key={answer}
              data-answer={answer}
              onClick={() => handleSelectAnswer(answer)}
            >
              {answer}
            </button>
          ))}
    
          <div className="box" ref={boxRef} style={{ transform: `translate(${boxPosition.x}px, ${boxPosition.y}px)` }} />
        </div>
      
      );
    };
    
    ReactDOM.render(<App />, document.getElementById("root"));
    .answers {
      margin: 0 auto;
      display: grid;
      grid-template-columns: repeat(2, 270px);
      grid-template-rows: repeat(2, 1fr);
      column-gap: 1rem;
      row-gap: 1rem;
      color: white;
      position: relative;
      width: max-content;
    }
    
    .answers button {
      height: 150px;
    }
    
    .box {
      width: 50px;
      height: 50px;
      background-color: lightblue;
      position: absolute;
      margin: 0 auto;
      transition: all 0.5s ease;
      z-index: 999;
    }
    <div id="root">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>
    
    </div>

    Let me know if it works for you or not.

    Login or Signup to reply.
  2. You can compute the position using JS, but then you probably want to use CSS handle everything else, by setting CSS variables:

    const { useRef } = React;
    
    const ANSWERS = ["answer 1", "answer 2", "answer 3", "answer 4"];
    
    const App = () => {
      const boxRef = useRef(null);
    
      const handleSelectAnswer = (answer) => {
        const box = boxRef.current;
        const answersContainer = document.querySelector(".answers");
        const selectedAnswerElement = answersContainer.querySelector(`[data-answer="${answer}"]`);
    
        if (selectedAnswerElement && box && answersContainer) {
          const { top, left, width, height } = selectedAnswerElement.getBoundingClientRect();
          const { width: bw, height: bh } = box.getBoundingClientRect();
          box.classList.add(`moving`);
          box.style.setProperty(`--selected-answer-top`, top + height/2);
          box.style.setProperty(`--selected-answer-left`, left + width/2);
        }
      };
    
      return (
        <div className="answers">
          {ANSWERS.map((answer) => (
            <button key={answer} data-answer={answer} onClick={() => handleSelectAnswer(answer)}>
              {answer}
            </button>
          ))}
        <div className="box" ref={boxRef} />
        </div>
      );
    };
    
    ReactDOM.render(<App />, document.getElementById("root"));
    :root {
      --answer-height: 150;
    }
    
    html, body {
      padding: 0;
      margin: 0;
    }
    
    .answers button {
      height: calc(var(--answer-height) * 1px);
    }
    
    .box {
      --d: 25;
      --w: calc(var(--d)*2px);
      --h: calc(var(--d)*2px);
      --selected-answer-top: calc(var(--d) + var(--answer-height));
      --selected-answer-left: var(--d);
      position: absolute;
      top: calc(var(--selected-answer-top) * 1px - var(--h) / 2);
      left: calc(var(--selected-answer-left) * 1px - var(--w) / 2);
      width: var(--w);
      height: var(--h);
      background: grey;
      transition: top, left;
      transition-duration: 0.5s;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>
    <div id="root"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search