I've created a horizontal carousel element myself for use in a React application. I want the currently active card in the carousel to be centered in the center of the viewport so that this implementation works responsively for any device.
import React, { useState } from "react";
import styles from "./styles.module.scss";
const data = [
{
name: "1"
},
{
name: "2"
},
{
name: "3"
}
];
const Testimonial: React.FC = (): JSX.Element => {
const [activeIndex, setActiveIndex] = useState(0);
const determinePlacement = (): number => {
const width = 260;
const startingOffset =
(Math.max(
document.documentElement.clientWidth || 0,
window.innerWidth || 0
) -
width) /
2;
if (activeIndex === 0) return startingOffset;
return -(activeIndex * width) + startingOffset;
};
const isActive = (i: number): null | string => {
return activeIndex === i ? styles.active : null;
};
return (
<>
<div className={styles.container}>
<div
className={styles["card-wrapper"]}
style={{ transform: `translateX(${determinePlacement()}px)` }}
>
{data.map((card, i) => {
return (
<div
onClick={(): void => setActiveIndex(i)}
className={styles.card}
key={i}
>
{`Card ${i + 1}`}
</div>
);
})}
</div>
</div>
<div className={styles["circles-wrapper"]}>
{data.map((_, i) => {
return (
<div
key={i}
onClick={(): void => setActiveIndex(i)}
className={[styles.circles, isActive(i)].join(" ")}
/>
);
})}
</div>
</>
);
};
export default Testimonial;
$cardWidth: 260px;
.circles-wrapper {
display: flex;
justify-content: center;
margin-top: 24px;
.circles {
width: 16px;
height: 16px;
border-radius: 16px;
background-color: hsla(207, 73%, 95%, 1);
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
margin-right: 8px;
transition: all 0.5s ease;
&.active {
background-color: hsla(207, 73%, 57%, 1);
}
}
}
.container {
display: flex;
position: relative;
flex-direction: column;
margin-top: 32px;
margin-bottom: 32px;
.card-wrapper {
display: flex;
transition: all 1s ease;
.card {
display: flex;
flex-direction: column;
width: $cardWidth;
height: 100%;
background-color: hsla(207, 73%, 95%, 1);
border-radius: 20px;
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
padding: 32px;
margin: 0 10px;
}
}
}
I can't quite figure out why when I re-create this implementation, simplified in a code sandbox (you can view it here https://codesandbox.io/s/currying-wind-krgx7k?file=/src/app.tsx), the elements correctly center in the middle of the viewport but when I run this app locally in dev tools (f12) on mobile devices the spacing is wrong and the elements end up too far to the right?
I'm assuming this has something to do with a lack of understanding on my part about how document.documentElement.clientWidth
and window.innerWidth
work or something to do with viewport widths. Can anyone enlighten me here please?
Edit: turns out the codesandbox example I provided also doesn't work as expected and I'm imagining things.
So question is: how do you reliably center the active carousel card for any viewport?
For those interested, I figured out how to solve this and included a codesandbox below with a working solution.
https://codesandbox.io/s/zen-parm-e5yyn9?file=/src/App.js&resolutionWidth=1024&resolutionHeight=765
In the end, I decided trying to calculate the viewport width and the gap between the start of the viewport and the card was the wrong approach.
I used CSS flex to center a wrapper container for all cards (or boxes, in latest example) which would then be transformed on the x-axis. You need to remember to account for things like margin between elements.
The solution is far simpler, cleaner and works responsively now.