0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React(V19系)】ChakraUI(V3系)とMotion(旧 framer-motion)を使ってリンクのアクティブ切り替えを実装

Last updated at Posted at 2025-11-10

React(V19系) + Chakra UI(V3系) + Framer Motion の組み合わせで「スクロールに応じてヘッダーのメニュー(リンク)が順次アクティブ切り替えされる」サンプルを作ってみましょう。

完成イメージ

下記のようにマウススクロールもしくはヘッダーのNavリンクをクリックすると指定位置を制御する仕組みになっています。

image.png

image.png

image.png

image.png

ディレクトリ

└── ReactAppProject/
    ├── src/
    │   ├── framerMotionComponents/
    │   │   └── Header.tsx
    │   └── Aoo.tsx
    └── //後略

実装コード

src/framerComponents/Header.tsx
import { Flex, Image, Link } from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { useEffect, useState } from 'react';

const links = [
  { name: 'Home', href: '#home' },
  { name: 'About', href: '#about' },
  { name: 'Services', href: '#services' },
  { name: 'Contact', href: '#contact' },
];

export const MenuHeader = () => {
  const [activeSection, setActiveSection] = useState<string>('home');
  useEffect(() => {
    const handleScroll = () => {
      const scrollY = window.scrollY;
      const sectionOffsets = links.map((link) => {
        const el = document.querySelector(link.href);
        if (!el) return { id: link.name.toLowerCase(), offset: 0 };
        return {
          id: link.name.toLowerCase(),
          offset: (el as HTMLElement).offsetTop,
        };
      });
      const current = sectionOffsets.reduce((closest, section) => {
        return scrollY >= section.offset - 200 ? section.id : closest;
      }, sectionOffsets[0].id);

      setActiveSection(current);
    };
    window.addEventListener('scroll', handleScroll);
    handleScroll();
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  return (
    <Flex
      as="header"
      position="fixed"
      bg="gray.100"
      top={0}
      left={0}
      width="full"
      height="80px"
      shadow="md"
      px={0}
      align="center"
      justify="space-between"
      zIndex={10}
    >
      <Image src="./mazent.svg" alt="Logo" height="50px" />
      <Flex gap={6}>
        {links.map((link) => {
          const isActive = activeSection === link.name.toLowerCase();
          return (
            <motion.div
              key={link.name}
              whileHover={{ scale: 1.1 }}
              transition={{ type: 'spring', stiffness: 300 }}
            >
              <Link
                href={link.href}
                fontWeight={isActive ? 'bold' : 'medium'}
                color={isActive ? 'blue.600' : 'gray.600'}
                borderBottom={isActive ? '2px solid' : 'none'}
                borderColor="blue.500"
                pb="1"
                transition="all 0.2s ease"
              >
                {link.name}
              </Link>
            </motion.div>
          );
        })}
      </Flex>
    </Flex>
  );
};

App.tsx
// App.tsx
import React from 'react';
import { Box, ChakraProvider, Text, defaultSystem } from '@chakra-ui/react';
import { MenuHeader } from './framerMotionComponents/Header';

function Section({ id, bg, children }: { id: string; bg: string; children: React.ReactNode }) {
  return (
    <Box
      id={id}
      height="100vh"
      bg={bg}
      display="flex"
      alignItems="center"
      justifyContent="center"
      scrollMarginTop="80px"
    >
      <Text fontSize="4xl" fontWeight="bold" color="white">
        {children}
      </Text>
    </Box>
  );
}

export default function App() {
  return (
    <ChakraProvider value={defaultSystem}>
      <MenuHeader />
      <Box mt="80px">
        <Section id="home" bg="blue.400">
          Home Section
        </Section>
        <Section id="about" bg="teal.400">
          About Section
        </Section>
        <Section id="services" bg="purple.400">
          Services Section
        </Section>
        <Section id="contact" bg="pink.400">
          Contact Section
        </Section>
      </Box>
    </ChakraProvider>
  );
}

🧠 説明

1.useEffectでスクロールイベントを監視して、現在表示中のセクションを特定。

2.activeSectionに応じてリンクをハイライト(色・下線・太字)。

3.framer-motionでリンクのホバー時にスケールアニメーション。

サイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?