feat(3d): add camera animation

when hover on navbar
This commit is contained in:
xfy
2025-05-23 01:58:21 +08:00
parent 8e6cc3f8be
commit bbc4ba4eff
3 changed files with 80 additions and 1 deletions

View File

@ -2,16 +2,24 @@ import { useFrame } from '@react-three/fiber';
import { easing } from 'maath';
import { JSX, useRef } from 'react';
import { useMediaQuery } from 'react-responsive';
import useStore from 'store';
import * as THREE from 'three';
const DeskCamera = ({ children }: { children: JSX.Element }) => {
const groupRef = useRef<THREE.Group>(null);
const navbarHoverItems = useStore((state) => state.navbarHoverItems);
const isMobile = useMediaQuery({ query: '(max-width: 768px)' });
useFrame((state, delta) => {
// enter animation
easing.damp3(state.camera.position, [0, 0, 26], 0.25, delta);
if (!isMobile && groupRef.current) {
const hasHover = Object.values(navbarHoverItems).some((v) => v);
if (groupRef.current && hasHover) {
easing.dampE(groupRef.current.rotation, [0, 0, 0], 0.25, delta);
}
// mouse move animation
if (!isMobile && groupRef.current && !hasHover) {
const x = (() => {
const _x = state.pointer.y / 3;
if (_x < 0) {
@ -37,6 +45,40 @@ const DeskCamera = ({ children }: { children: JSX.Element }) => {
}
});
// move camera to specific position when hovering navbar
useFrame((state, delta) => {
const map = {
blog: () => {
easing.damp3(state.camera.position, [0, 8.8, -10], 0.25, delta);
},
projects: () => {
easing.damp3(state.camera.position, [-10, 8, -12], 0.25, delta);
if (groupRef.current) {
easing.dampE(groupRef.current.rotation, [0, -7.2, 0], 0.25, delta);
}
},
tags: () => {
easing.damp3(state.camera.position, [-7, 5, -14], 0.25, delta);
if (groupRef.current) {
easing.dampE(groupRef.current.rotation, [1, 0, 0], 0.25, delta);
}
},
friends: () => {
easing.damp3(state.camera.position, [10, 5, -12], 0.25, delta);
},
about: () => {
easing.damp3(state.camera.position, [-24, 14, -10], 0.25, delta);
},
};
for (const [key, value] of Object.entries(navbarHoverItems)) {
if (value) {
/** @ts-expect-error */
map[key]();
}
}
});
return <group ref={groupRef}>{children}</group>;
};

View File

@ -5,6 +5,7 @@ import clsx from 'clsx';
import Link from 'next/link';
import { memo, useEffect, useState } from 'react';
import { FiMenu } from 'react-icons/fi';
import useStore from 'store';
import DarkModeBtn from './dark-mode-btn';
const txtMenu = [
@ -71,6 +72,10 @@ const HeadBar = () => {
};
}, []);
const toggleNavbarHoverItems = useStore(
(state) => state.toggleNavbarHoverItems,
);
return (
<>
<header
@ -122,6 +127,8 @@ const HeadBar = () => {
'mb-2 last:mb-0 md:mb-0',
'md:mr-4 md:last:mr-0',
)}
onMouseOver={() => toggleNavbarHoverItems(m.name.toLowerCase())}
onMouseOut={() => toggleNavbarHoverItems(m.name.toLowerCase())}
>
<Link href={m.path}>{m.name}</Link>
</li>

View File

@ -1,8 +1,21 @@
import { create } from 'zustand';
// type NavbarHoverItemsKey = ['blog', 'projects', 'tags', 'friends', 'about'];
interface MainStore {
/** Loading state of the model */
modelLoading: boolean;
toggleLoading: (loaded: boolean) => void;
/** mouse hover on navbar */
navbarHoverItems: {
blog: boolean;
projects: boolean;
tags: boolean;
friends: boolean;
about: boolean;
};
toggleNavbarHoverItems: (item: string) => void;
}
const useStore = create<MainStore>()((set) => ({
@ -11,6 +24,23 @@ const useStore = create<MainStore>()((set) => ({
set(() => ({
modelLoading: loaded,
})),
navbarHoverItems: {
blog: false,
projects: false,
tags: false,
friends: false,
about: false,
} as const,
toggleNavbarHoverItems: (item) =>
set((state) => ({
navbarHoverItems: {
...state.navbarHoverItems,
[item as unknown as string]:
/** @ts-expect-error */
!state.navbarHoverItems[item],
},
})),
}));
export default useStore;