DEV Community

Fulya Cimendere
Fulya Cimendere

Posted on

Creating an Interactive Navigation Circle with React

A Deep Dive into Scroll-Based Animations

Have you ever wanted to create a unique navigation experience that combines smooth scrolling with visual feedback? In this article, I'll walk you through how I implemented an interactive navigation system featuring a rotating clock animation that responds to both button clicks and scroll positions.

Magic Circle View

The Navigation System: More Than Just Buttons

The navigation system consists of two main parts:

  1. A set of gradient buttons for different sections ("About", "Project", "Experience", "Contact") - for more about gradient buttons visit reactbits 
  2. A circular clock-like indicator that rotates to point at the currently active section.

Navigation Implementation

The navigation buttons are implemented using a combination of React components and the react-scroll library. Here's the key part from the Home component:

{["about", "project", "experience", "contact"].map((section) => (
  <GradientTextButton key={section} aria-label={`Navigate to ${section}>
    <Link
      key={section}
      id={`link-${section}`}
      to={section}
      smooth={true}
      duration={500}
      onClick={() => handleClick(`link-${section}`)}
      tabIndex={0}
      onKeyDown={(e) => {
        if (e.key === "Enter") handleClick(`link-${section}`);
      }}
    >
      <span className="dark:text-lighttext">
        {section.charAt(0).toUpperCase() + section.slice(1)}
      </span>
    </Link>
  </GradientTextButton>
))}
Enter fullscreen mode Exit fullscreen mode

What makes this special?

  • The the the smooth={true} property ensures smooth scrolling animation
  • Each button triggers two actions:
  1. Scrolls to the target section
  2. Updates the activeId state which controls the circle rotati on

The Magic Circle

The circle indicator is where things get interesting. The Circle.jsx component listens for changes to the id prop (which represents the active section). Based on this prop, it adjusts the rotation of the inner circle to correspond to the section. This is done through simple CSS transforms, allowing the circle to rotate based on which section is active.

const getRotation = (id) => {
  switch (id) {
    case "link-about": return `rotate(145deg)`;
    case "link-project": return `rotate(170deg)`;
    case "link-experience": return `rotate(190deg)`;
    case "link-contact": return `rotate(215deg)`;
    default: return `rotate(0deg)`;
  }
};
Enter fullscreen mode Exit fullscreen mode

The clever part: The rotation values are carefully calibrated to point at each section's button position, creating a seamless connection between the indicator and navigation.

Scroll-Based Updates

The system doesn't just respond to clicks - it also updates based on scroll position using the Intersection Observer API. In RightSection.jsx:

const sections = ["about", "project", "experience", "contact"];
const activeSection = useIntersectionObserver(sections);

useEffect(() => {
  if (activeSection) {
    onSectionChange(`link-${activeSection}`);
  }
}, [activeSection, onSectionChange]);
Enter fullscreen mode Exit fullscreen mode

Why this matters: This creates a two-way connection where both scrolling and clicking update the navigation state, ensuring the UI always reflects the user's current position.

Bonus: What Could Be Better

  1. Progressive Animation:  Implement velocity-based animation for more natural movement 
  2. Performance Improvements:  Implement virtual scrolling for longer content sections  Optimize the Intersection Observer thresholds for smoother transitions  3.** Accessibility Enhancements: **

Thanks for reading!

Follow me: LinkedIn | GitHub | Portfolio

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs