How I made the Orbit like skills section with framer motion and tailwind Css
After going through Hirok's new portfolio website on X(formerly Twitter), I thought that would be the perfect idea I've been looking for mine here.
Well, I've been going around YouTube and blog posts for a portfolio inspiration and all I could see are 3D animated stuff that will make your potential clients hiss and leave before a model loads complete.
Of all stuff there, the main feature people ask about is the orbital skill section.
The section as shown at the cover image of this post shows Nextjs as a tool, surrounded by other tools in an orbital manner, this tells how for quite some time now, I've been building stuff in the @nextjs, tailwindcss and shadcn Ui ecosystem
In this post, I will be walking us through a step by step guide on how I created the visually engaging skills section with rotating orbits using Framer Motion in Next.js.
This section will feature three orbits with different sets of skills, each orbit rotating at different speeds, some clockwise and some counterclockwise.
So let's jump into the process step by step
1. Setting Up the Environment
Before we start, ensure that you have a Next.js project set up. If you haven't already, you can create one with:
npx create-next-app@latest
Then, install Framer Motion and Next.js Image component dependencies:
npm install framer-motion
Now you’re ready to start building the skills section.
2. The Skill Arrays
We’ll define three separate arrays for each orbit. Each skill has a name and an image URL. You can make use images in your public folder or however you import images.
/code const skillsOrbit1 = [
{ name: 'JavaScript', image: '/js.png' },
{ name: 'React', image: '/react.png' },
{ name: 'TypeScript', image: '/typescipt.png' }
];
const skillsOrbit2 = [
{ name: 'MongoDB', image: '/mongodb.png' },
{ name: 'Prisma', image: '/prisma.png' },
{ name: 'Framer Motion', image: '/motions.png' },
{ name: 'TailwindCSS', image: '/tailwind.png' }
];
const skillsOrbit3 = [
{ name: 'Next.js', image: '/nextjs.png' },
{ name: 'ShadCN UI', image: '/shadcn.png' },
{ name: 'Google Docs', image: '/docs.png' }
];
Here, each orbit contains a different number of skills, which we will position on circular paths around a central point.
Also we'll declare an orbit duration variable for the duration of full rotation on each orbit
Let orbitDuration = 30; //this will be adjusted dynamically based on the orbits size
3. Creating the Central Skill
Next, at the center of our orbits, we’ll place one main skill (in this case, Next.js), which remains static while the other skills rotate around it. When using framer-motion, ensure you wrap your component with its ‘motion.div’ component for animation. We are also using tailwindcss to style the div making sure it's relative and its position properly centered.
<motion.div className="relative w-24 h-24 rounded-full flex justify-center items-center">
<Image
src="/nextjs.png"
alt="Next.js"
className="rounded-full"
width={90}
height={90}
/>
</motion.div>
This central skill is important as it serves as a focal point, and the rotating orbits surround it.
4. Defining the Orbits
For each orbit, we’ll also use Framer Motion’s motion.div component to allow us animate the orbits continuously.
<motion.div className="absolute w-[300px] h-[300px] rounded-full"
style={{ border: '2px dashed rgba(255, 255, 255, 0.5)' }}
animate={{ rotate: 360 }}
transition={{
repeat: Infinity,
duration: orbitDuration, // Define duration for full rotation
ease: 'linear',
}}
>
{/* Skills go here */}
</motion.div>
For each orbit, we adjust the size, border style, and the rotation speed.
Orbit 1 will rotate in 30 seconds.
Orbit 2 will rotate slightly slower, in 45 seconds and rotate in the opposite direction.
Orbit 3 is the slowest, rotating in 60 seconds.
5. Placing the Skills in Orbits
We place the skills around the orbit using CSS transforms. We map through each skill and calculate the rotation based on the total number of skills in that orbit.
{skillsOrbit1.map((skill, i) => (
<motion.div
key={skill.name}
className="absolute w-12 h-12 rounded-full"
style={{
top: '50%',
left: '50%',
transform: `rotate(${(i / skillsOrbit1.length) * 360}deg) translate(-50%, -50%) translateX(150px)`,
transformOrigin: '0 0',
}}
>
<Image
src={skill.image}
alt={skill.name}
className="w-full h-full rounded-full object-contain"
width={225}
height={225}
/>
</motion.div>
))}
Let's break down what the major part of this code does:
rotate(${(i / skillsOrbit1.length) * 360}deg)
Purpose: This rotates each skill element around the center of the orbit.
The rotate() function rotates an element by a given degree. In this case, it calculates the rotation angle dynamically based on the index (i) of the skill and the total number of skills in the orbit (skillsOrbit1.length).
(i / skillsOrbit1.length) * 360 evenly spaces out the skills along the orbit.
For example, if there are 4 skills, this formula will generate rotation angles of 0°, 90°, 180°, and 270°, ensuring that the skills are evenly distributed around the circle.
translate(-50%, -50%)
Purpose: This centers each skill element at its respective position along the orbit.
The translate() function moves an element along the X and Y axes by a given amount.
translate(-50%, -50%) moves the element by 50% of its width and height upwards and to the left, effectively centering it.
Without this, the skill elements would be positioned from their top-left corner instead of their center, causing them to be misaligned along the orbit.
translateX(150px)
Purpose: This moves each skill outward from the center of the orbit.
Note that this can be dynamic for responsiveness!
You can create a function to check for the size of each screens and declare it as a variable then pass it down dynamically.
The translateX() function moves an element along the X-axis by a specified amount. In this case, it moves the skill element 150px to the right of its current position. (Larger screen sizes might require more Px to make sure the images are properly aligned on the orbit.
After rotating the element around the center using the rotate() function, the translateX() function pushes the element away from the center, placing it on the circumference of the orbit.
The value 150px determines the radius of the orbit, meaning this controls how far the skill is from the center of the circle.
transformOrigin: '0 0'
Purpose: This defines the point around which the rotation occurs.
transformOrigin specifies the origin point (pivot) for transformations like rotate().
'0 0' sets the top-left corner of the element as the origin point, meaning the rotation will happen around this point.
If this were not set, the default would be the center of the element, which could cause the element to rotate around its own center instead of around the central point of the orbit.
7. Final Result
After following these steps, you’ll have a section with three distinct rotating orbits. Each orbit containing a unique set of skills, and they all rotate at different speeds and directions, creating a mesmerizing, dynamic effect.
Conclusion
Using Framer Motion with Next.js allow us create a powerful and creative animations with minimal code. By leveraging motion components and some simple CSS transforms, we’ve built an eye-catching, rotating skills section that can be customized with various animations, skills, and effects.
This kind of creative section can be a fun way to display technologies and tools that you or your team are proficient in. You can further enhance this by adding hover effects, tooltips, or integrating it with other parts of your project.
Make sure you check out the live code
Follow me on X(Twitter)
And Medium
Subscribe to my newsletter
Read articles from Emmanuel Onoja directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Emmanuel Onoja
Emmanuel Onoja
I'm a software developer specialized in developing modern frontend infrastructures, I am content writer who writes mostly blockchain related stuff, A student food technologist who doesn't Cook!' My passion is building stuff that makes life easier for everyone. I also have interests in AI tech!