diff --git a/src/components/SkillsMarquee.tsx b/src/components/SkillsMarquee.tsx index 66698f4..a723f26 100644 --- a/src/components/SkillsMarquee.tsx +++ b/src/components/SkillsMarquee.tsx @@ -1,5 +1,4 @@ -import { motion, useAnimation } from "framer-motion"; -import { useState, useEffect } from "react"; +import { motion, type Variants } from "framer-motion"; import { useTranslations } from "@/i18n/utils"; import { type SkillItem } from "@/types"; @@ -13,8 +12,6 @@ const allSkills: SkillItem[] = [ { name: "JavaScript", icon: "javascript" }, { name: "React", icon: "react" }, { name: "Vue", icon: "vue" }, - { name: "微信小程序", icon: "wechat" }, - { name: "UniApp", icon: "vue" }, // Using vue icon as fallback // Frontend Development { name: "Next.js", icon: "nextjs" }, @@ -29,7 +26,7 @@ const allSkills: SkillItem[] = [ { name: "Node.js", icon: "nodejs" }, { name: "Express.js", icon: "express" }, { name: "Nest.js", icon: "nestjs" }, - { name: "Fastify.js", icon: "fastify" }, + // { name: "Fastify.js", icon: "fastify" }, { name: "Hono.js", icon: "nodejs" }, // Using nodejs icon as fallback // Database & Storage @@ -75,6 +72,7 @@ function SkillItem({ skill }: { skill: SkillItem }) { /** * Marquee row component that scrolls skills horizontally + * Optimized for performance with constant animation */ function MarqueeRow({ skills, direction = "left", speed = 50 }: { skills: SkillItem[]; @@ -83,57 +81,34 @@ function MarqueeRow({ skills, direction = "left", speed = 50 }: { }) { // Duplicate skills array to create seamless infinite scroll const duplicatedSkills = [...skills, ...skills]; - // State to control animation play/pause - const [isHovered, setIsHovered] = useState(false); - const controls = useAnimation(); - // Start animation on component mount - useEffect(() => { - const startAnimation = () => { - controls.start({ - x: direction === "left" ? ["-50%", "0%"] : ["0%", "-50%"], - transition: { - x: { - repeat: Infinity, - repeatType: "loop", - duration: speed, - ease: "linear" - } + // Define animation variants to ensure type safety with hardware acceleration + const variants: Variants = { + animate: { + transform: direction === "left" ? ["translateX(0%)", "translateX(-50%)"] : ["translateX(-50%)", "translateX(0%)"], + transition: { + transform: { + repeat: Infinity, + repeatType: "loop" as const, + duration: speed, + ease: [0, 0, 1, 1] // 使用数组形式的 linear easing } - }); - }; - - startAnimation(); - }, [controls, direction, speed]); - - // Handle hover state changes - useEffect(() => { - if (isHovered) { - controls.stop(); - } else { - controls.start({ - x: direction === "left" ? ["-50%", "0%"] : ["0%", "-50%"], - transition: { - x: { - repeat: Infinity, - repeatType: "loop", - duration: speed, - ease: "linear" - } - } - }); + } } - }, [isHovered, controls, direction, speed]); + }; return ( -
setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > +
{duplicatedSkills.map((skill, index) => ( diff --git a/src/pages/index.astro b/src/pages/index.astro index 035147d..809c934 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -177,7 +177,7 @@ const pageTitle = t('site.title');
{services.en.map((service) => ( -
+
@@ -352,7 +352,7 @@ const pageTitle = t('site.title');
-
+
{t('project.tag.business')} @@ -360,7 +360,7 @@ const pageTitle = t('site.title');
-
+
Taskify App @@ -419,7 +419,7 @@ const pageTitle = t('site.title');
-
+
{t('project.tag.opensource')} @@ -427,7 +427,7 @@ const pageTitle = t('site.title');
-
+
E-Shop Platform @@ -486,7 +486,7 @@ const pageTitle = t('site.title');
-
+
{t('project.tag.personal')} @@ -494,7 +494,7 @@ const pageTitle = t('site.title');
-
+
Portfolio Site diff --git a/src/pages/zh/index.astro b/src/pages/zh/index.astro index c5f8b0a..e81ccb6 100644 --- a/src/pages/zh/index.astro +++ b/src/pages/zh/index.astro @@ -177,7 +177,7 @@ const pageTitle = t('site.title');
{services.zh.map((service) => ( -
+