React(V19系) + Chakra UI(V3系) + Framer Motion の組み合わせで「スクロールに応じてヘッダーのメニュー(リンク)が順次アクティブ切り替えされる」サンプルを作ってみましょう。
完成イメージ
下記のようにマウススクロールもしくはヘッダーのNavリンクをクリックすると指定位置を制御する仕組みになっています。
ディレクトリ
└── 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でリンクのホバー時にスケールアニメーション。
サイト



