I am trying to re-create the string-sytems.co.uk (or you can see the GIF below) to learn the concepts of Next.js, Tailwind CSS, and Framer Motion.
I'm new to all these things and need to design the Hero Section first. There is a Header inside a cube that rotates and changes the text while rotating and changing the background images around the hero section.
You can try the code base here, what I tried: codesandbox.io example
You can understand much more from the code if you tweak it a bit, but I’ll provide a few pointers. An essential setting for the 3D effect is the preserve-3d
value for transformStyle
.
After that, I defined the rotation circle's radius, then adjusted the height and width of the animated element accordingly. The rest is just formatting.
motion.div
- Motion Docsconst { useEffect, useRef } = React;
const { motion } = Motion;
const blockSize = '64px'; // rotate radius
const width = `calc(${blockSize} * 6)`;
const duration = 2; // speed
function RotatingBlock() {
return (
// container
React.createElement('div', { className: "flex items-center justify-center h-screen bg-gray-900" },
// animated element
React.createElement(motion.div, {
className: "relative",
animate: {
rotateX: [0, -90, -180, -270, -360],
},
transition: {
repeat: Infinity,
duration: duration * 4,
ease: "linear",
},
style: {
transformStyle: "preserve-3d", // 3D style
width: width,
height: `calc(${blockSize} * 2)`,
},
},
// 1.
React.createElement('div', {
className: "absolute w-full h-full bg-blue-500 flex items-center justify-center text-white text-lg font-bold",
style: {
transform: `rotateX(0deg) translateZ(${blockSize})`,
backfaceVisibility: "hidden",
},
}, "Side 1"),
// 2.
React.createElement('div', {
className: "absolute w-full h-full bg-red-500 flex items-center justify-center text-white text-lg font-bold",
style: {
transform: `rotateX(90deg) translateZ(${blockSize})`,
backfaceVisibility: "hidden",
},
}, "Side 2"),
// 3.
React.createElement('div', {
className: "absolute w-full h-full bg-green-500 flex items-center justify-center text-white text-lg font-bold",
style: {
transform: `rotateX(180deg) translateZ(${blockSize})`,
backfaceVisibility: "hidden",
},
}, "Side 3"),
// 4.
React.createElement('div', {
className: "absolute w-full h-full bg-yellow-500 flex items-center justify-center text-white text-lg font-bold",
style: {
transform: `rotateX(270deg) translateZ(${blockSize})`,
backfaceVisibility: "hidden",
},
}, "Side 4"),
)
)
);
}
ReactDOM.createRoot(document.getElementById("root")).render(React.createElement(RotatingBlock));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/framer-motion@12.4.5/dist/framer-motion.min.js"></script>
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
<div id="root"></div>
Specifically, to achieve your example, one thing missing is a slight delay between the two rotations. To speed up the process, I solved this with a trick; I've incorporated into the animation that it stays in one position for a longer time, as seen in the rotateX
setting. Here, due to the rounding, I've "overlapped" the squares on top of each other so that no gap is visible at the rounded corners. However, this is only a nice solution if the sides of the rectangles have the same color.
const { useEffect, useRef } = React;
const { motion } = Motion;
const blockSize = '32px'; // rotate radius
const width = `32rem`;
const duration = 10; // speed
function RotatingBlock() {
return (
// container
React.createElement('div', { className: "flex items-center justify-center h-screen bg-stone-50" },
// animated element
React.createElement(motion.div, {
className: "relative",
animate: {
rotateX: [
0, 0, 0, 0,
-90, -90, -90, -90,
-180, -180, -180, -180,
-270, -270, -270, -270,
-360,
],
},
transition: {
repeat: Infinity,
duration: duration,
ease: "linear",
},
style: {
transformStyle: "preserve-3d", // 3D style
width: width,
// At a 2x multiplier, the rectangles just touch each other, but due to the rounding, it creates an optical illusion as if I've "overlapped" them. If you don't color them blue, you'll see the trick.
height: `calc(${blockSize} * 2.25)`,
},
},
// 1.
React.createElement('div', {
className: "absolute w-full h-full bg-blue-500 flex items-center justify-center text-white text-4xl font-bold rounded-lg",
style: {
transform: `rotateX(0deg) translateZ(${blockSize})`,
backfaceVisibility: "hidden",
},
}, "cloud solutions"),
// 2.
React.createElement('div', {
className: "absolute w-full h-full bg-blue-500 flex items-center justify-center text-white text-4xl font-bold rounded-lg",
style: {
transform: `rotateX(90deg) translateZ(${blockSize})`,
backfaceVisibility: "hidden",
},
}, "enterprise-grade security"),
// 3.
React.createElement('div', {
className: "absolute w-full h-full bg-blue-500 flex items-center justify-center text-white text-4xl font-bold rounded-lg",
style: {
transform: `rotateX(180deg) translateZ(${blockSize})`,
backfaceVisibility: "hidden",
},
}, "increased collaboration"),
// 4.
React.createElement('div', {
className: "absolute w-full h-full bg-blue-500 flex items-center justify-center text-white text-4xl font-bold rounded-lg",
style: {
transform: `rotateX(270deg) translateZ(${blockSize})`,
backfaceVisibility: "hidden",
},
}, "optimising processes"),
)
)
);
}
ReactDOM.createRoot(document.getElementById("root")).render(React.createElement(RotatingBlock));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/framer-motion@12.4.5/dist/framer-motion.min.js"></script>
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
<div id="root"></div>