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>
You can move the box on top of an answer by moving its center to "on top of the answer's center", e.g.
const { width: bw, height: bh } = box.getBoundingClientRect();
const { top, left, width, height } = selectedAnswerElement.getBoundingClientRect();
const newBoxTop = top + height/2 - bh/2 + window.scrollY);
const newBoxLeft = left + width/2 - bw/2 + window.scrollX);
(Remembering to take scroll x/y into account)
And while you can compute the position using JS, you probably want to use CSS to handle the rest by setting CSS variables rather than messing with element.style
properties directly, because you want your CSS to live in your CSS files, not in your CSS and your JS. Define your CSS rules in terms of CSS variables, and then make JS update those variables only:
const { useRef, useEffect, useState } = React;
const ANSWERS = ["answer 1", "answer 2", "answer 3", "answer 4"];
const App = () => {
const boxRef = useRef(null);
// Switch the box to use absolute positioning once it's on the page.
useEffect(() => {
const box = boxRef.current;
const { top, left } = box.getBoundingClientRect();
box.classList.add(`ready`);
box.style.setProperty(`--top`, top);
box.style.setProperty(`--left`, left);
}, []);
// then move its center to a clicked answer's box's center
const handleSelectAnswer = (answer) => {
const box = boxRef.current;
const { width: bw, height: bh } = box.getBoundingClientRect();
const selectedAnswerElement = document.querySelector(`.answers [data-answer="${answer}"]`);
const { top, left, width, height } = selectedAnswerElement.getBoundingClientRect();
// move the centers, remembering to take h/vscroll into account:
box.style.setProperty(`--top`, top + height/2 - bh/2 + window.scrollY);
box.style.setProperty(`--left`, left + width/2 - bw/2 + window.scrollX);
};
return (
<React.Fragment>
<div className="answers">
{ANSWERS.map((answer) => (
<button key={answer} data-answer={answer} onClick={() => handleSelectAnswer(answer)}>
{answer}
</button>
))}
<div className="box" ref={boxRef} />
</div>
</React.Fragment>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
.answers {
display: grid;
grid-template-columns: repeat(2, 270px);
grid-template-rows: 1fr 1fr auto;
grid-template-areas:
"a a"
"a a"
"box box";
column-gap: 1rem;
row-gap: 1rem;
width: max-content;
margin: 0 auto;
color: white;
}
.answers button {
height: 150px;
}
.box {
grid-area: box;
width: 50px;
height: 50px;
margin: 0 auto;
background-color: lightblue;
}
.box.ready {
--top: 0;
--left: 0;
position: absolute;
top: calc(var(--top) * 1px);
left: calc(var(--left) * 1px);
transition: all 0.5s ease;
}
<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>