Anonymous
Круглая линейная анимация отстает
Сообщение
Anonymous » 08 май 2025, 00:35
Итак, в своем портфолио я попытался создать круговую линейную анимацию со своими навыками, используя Claude-3.7. Я совершенно новичок в этом разделе, и я сталкиваюсь с отставанием, а анимация не гладкая. Я много пробовал с Клодом, чтобы решить это, но не удалось.
Код: Выделить всё
/* Enhanced scroll animation styles with forced continuous animation */
.scroll-wrapper {
overflow: hidden;
white-space: nowrap;
width: 100%;
transition: opacity 1s;
position: relative;
perspective: 1000px;
transform-style: preserve-3d;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
will-change: transform;
}
.scroll-content {
display: inline-block;
white-space: nowrap;
animation-name: continuous-scroll;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-play-state: running !important; /* Force animation to always play */
will-change: transform;
transform: translateZ(0);
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
-webkit-font-smoothing: subpixel-antialiased;
-webkit-transform: translate3d(0, 0, 0);
-webkit-perspective: 1000;
}
/* Force animation during scrolling */
.force-animation {
animation-play-state: running !important;
}
@keyframes continuous-scroll {
0% {
transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(-50%, 0, 0);
}
}
< /code>
И это файл, в котором реализована анимация < /p>
'use client';
import { Skill as SkillType } from '@/types/skill';
import { skillTypeOrder } from '@/types/skill';
import { fetchSkills } from '@/services/skillService';
import { useTheme } from '@/context/ThemeContext';
import { Icon } from '@iconify/react';
import { useEffect, useState, useRef } from 'react';
import { motion } from 'framer-motion';
const Skill = () => {
const { isDarkMode } = useTheme();
const [isVisible, setIsVisible] = useState(false);
const [skillsData, setSkillsData] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const [viewMode, setViewMode] = useState('scroll');
const scrollContentRefs = useRef([]);
const SCROLL_SPEED = 65;
const [isScrolling, setIsScrolling] = useState(false);
// Fetch skills data from the database
useEffect(() => {
const getSkillsData = async () => {
try {
const data = await fetchSkills();
setSkillsData(data);
setIsLoading(false);
} catch (err) {
console.error('Failed to fetch skills data:', err);
setError('Failed to load skills data');
setIsLoading(false);
}
};
getSkillsData();
setIsVisible(true);
}, []);
const groupedSkills = skillsData.reduce((acc, skill) => {
if (!acc[skill.type]) {
acc[skill.type] = [];
}
acc[skill.type].push(skill);
return acc;
}, {} as Record);
// Animation variants for the grid items
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1
}
}
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.5
}
}
};
// Toggle view mode
const toggleViewMode = () => {
setViewMode(prev => prev === 'scroll' ? 'grid' : 'scroll');
};
// Enhanced scroll animation with prevention of stopping during scroll
useEffect(() => {
if (viewMode !== 'scroll' || !isVisible || skillsData.length === 0) return;
const adjustScrollAnimations = () => {
scrollContentRefs.current.forEach((ref, index) => {
if (!ref) return;
// Get the actual width of the first set of items
const children = Array.from(ref.children);
const halfPoint = children.length / 2;
// Calculate the width of the first set
let firstSetWidth = 0;
for (let i = 0; i < halfPoint; i++) {
const child = children[i] as HTMLElement;
if (child) {
// Include the element width and its margins
const style = window.getComputedStyle(child);
const marginLeft = parseInt(style.marginLeft);
const marginRight = parseInt(style.marginRight);
firstSetWidth += child.offsetWidth + marginLeft + marginRight;
}
}
// Calculate duration based on consistent scroll speed
// Use a minimum of 6 seconds to ensure smoother animations for small categories
const animDuration = Math.max(6, firstSetWidth / SCROLL_SPEED);
// Apply animation with forced GPU acceleration
ref.style.animationDuration = `${animDuration}s`;
ref.style.transform = 'translate3d(0, 0, 0)';
ref.style.willChange = 'transform';
// Ensure animation keeps running during scroll
ref.style.animationPlayState = 'running';
// Force a clean reflow
void ref.offsetWidth;
// Set animation direction based on index
const direction = index % 2 === 0 ? 'normal' : 'reverse';
ref.style.animationDirection = direction;
});
};
// Run initial animation setup
setTimeout(adjustScrollAnimations, 50);
setTimeout(adjustScrollAnimations, 300);
setTimeout(adjustScrollAnimations, 1000);
// Handle scroll events specifically to prevent animation pausing
let scrollTimeout: NodeJS.Timeout;
const handleScroll = () => {
setIsScrolling(true);
// Force animations to keep running during scroll
scrollContentRefs.current.forEach(ref => {
if (ref) {
ref.style.animationPlayState = 'running';
}
});
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
setIsScrolling(false);
}, 100);
};
// Handle resize events
const handleResize = () => {
// Clear animations temporarily to avoid jank
scrollContentRefs.current.forEach(ref => {
if (ref) {
ref.style.animationPlayState = 'paused';
}
});
// Small delay to allow resize to complete
setTimeout(() => {
adjustScrollAnimations();
// Resume animations
scrollContentRefs.current.forEach(ref => {
if (ref) {
ref.style.animationPlayState = 'running';
}
});
}, 100);
};
// Add scroll event listener
window.addEventListener('scroll', handleScroll, { passive: true });
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', handleResize);
clearTimeout(scrollTimeout);
};
}, [viewMode, isVisible, skillsData, groupedSkills]);
// Create a callback ref setter function that fixes the TypeScript error
const setScrollContentRef = (element: HTMLDivElement | null, index: number) => {
scrollContentRefs.current[index] = element;
};
return (
Skills
{/* View mode toggle */}
setViewMode('scroll')}
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-300 ${viewMode === 'scroll' ? 'bg-[var(--button-bg)] text-white shadow-sm' : 'text-[var(--card-text)] hover:bg-[var(--hover-bg)]'}`}
>
Scroll
setViewMode('grid')}
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-300 ${viewMode === 'grid' ? 'bg-[var(--button-bg)] text-white shadow-sm' : 'text-[var(--card-text)] hover:bg-[var(--hover-bg)]'}`}
>
Grid
{isLoading ? (
) : error ? (
{error}
) : skillsData.length === 0 ? (
No skills available
) : (
// Define the priority order for skill types
(() => {
// Sort the skill types according to the defined order
return Object.entries(groupedSkills)
.sort(([typeA], [typeB]) => {
const indexA = skillTypeOrder.indexOf(typeA);
const indexB = skillTypeOrder.indexOf(typeB);
// If both types are in the priority list, sort by their position
if (indexA >= 0 && indexB >= 0) {
return indexA - indexB;
}
// If only typeA is in the list, it comes first
if (indexA >= 0) return -1;
// If only typeB is in the list, it comes first
if (indexB >= 0) return 1;
// If neither is in the list, maintain alphabetical order
return typeA.localeCompare(typeB);
});
})()
.map(([type, skills]: [string, SkillType[]], typeIndex: number) => (
{type}
{viewMode === 'scroll' ? (
// Scrolling view with fixed ref callback
setScrollContentRef(el, typeIndex)}
className={`scroll-content ${isScrolling ? 'force-animation' : ''}`}
style={{
// Default animation duration that will be overridden by useEffect
// Set a reasonable default to avoid initial stutter
animationDuration: '20s',
animationDirection: typeIndex % 2 === 0 ? 'normal' : 'reverse'
}}
>
{/* First set of items */}
{skills.map((skill: SkillType) => (
{skill.name}
))}
{/* Duplicate set for seamless scrolling */}
{skills.map((skill: SkillType) => (
key={`second-${skill.id}`}
className="inline-flex items-center space-x-2 md:space-x-3 px-4 md:px-6 py-3 md:py-4 mx-6 md:mx-12 rounded-xl border border-[var(--border-color)] bg-[var(--card-bg)] hover:bg-[var(--hover-bg)] transition-all duration-300 transform hover:scale-90"
>
{skill.name}
))}
) : (
// Grid view with animation
{skills.map((skill: SkillType) => (
{skill.name}
))}
)}
))
)}
);
};
export default Skill;
< /code>
Пожалуйста, дайте мне несколько предложений, как сделать анимацию более плавной без отставания. а также что может это вызвать? Lang-HTML PrettyPrint-Override ">
Scroll Animation Test
body {
margin: 0;
font-family: sans-serif;
background: #f5f5f5;
}
.scroll-wrapper {
overflow: hidden;
white-space: nowrap;
width: 100%;
height: 100px;
background: #222;
padding: 20px 0;
}
.scroll-content {
display: inline-block;
white-space: nowrap;
animation-name: continuous-scroll;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-duration: 20s;
animation-play-state: running;
will-change: transform;
transform: translate3d(0, 0, 0);
}
.item {
display: inline-block;
margin: 0 30px;
padding: 10px 20px;
background: #fff;
color: #222;
border-radius: 8px;
font-weight: bold;
font-size: 18px;
}
@keyframes continuous-scroll {
0% {
transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(-50%, 0, 0);
}
}
HTML
CSS
JS
React
Node
TypeScript
Tailwind
HTML
CSS
JS
React
Node
TypeScript
Tailwind
Подробнее здесь:
https://stackoverflow.com/questions/796 ... is-lagging
1746653750
Anonymous
Итак, в своем портфолио я попытался создать круговую линейную анимацию со своими навыками, используя Claude-3.7. Я совершенно новичок в этом разделе, и я сталкиваюсь с отставанием, а анимация не гладкая. Я много пробовал с Клодом, чтобы решить это, но не удалось.[code]/* Enhanced scroll animation styles with forced continuous animation */ .scroll-wrapper { overflow: hidden; white-space: nowrap; width: 100%; transition: opacity 1s; position: relative; perspective: 1000px; transform-style: preserve-3d; backface-visibility: hidden; -webkit-backface-visibility: hidden; will-change: transform; } .scroll-content { display: inline-block; white-space: nowrap; animation-name: continuous-scroll; animation-timing-function: linear; animation-iteration-count: infinite; animation-play-state: running !important; /* Force animation to always play */ will-change: transform; transform: translateZ(0); backface-visibility: hidden; -webkit-backface-visibility: hidden; -webkit-font-smoothing: subpixel-antialiased; -webkit-transform: translate3d(0, 0, 0); -webkit-perspective: 1000; } /* Force animation during scrolling */ .force-animation { animation-play-state: running !important; } @keyframes continuous-scroll { 0% { transform: translate3d(0, 0, 0); } 100% { transform: translate3d(-50%, 0, 0); } } < /code> И это файл, в котором реализована анимация < /p> 'use client'; import { Skill as SkillType } from '@/types/skill'; import { skillTypeOrder } from '@/types/skill'; import { fetchSkills } from '@/services/skillService'; import { useTheme } from '@/context/ThemeContext'; import { Icon } from '@iconify/react'; import { useEffect, useState, useRef } from 'react'; import { motion } from 'framer-motion'; const Skill = () => { const { isDarkMode } = useTheme(); const [isVisible, setIsVisible] = useState(false); const [skillsData, setSkillsData] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [viewMode, setViewMode] = useState('scroll'); const scrollContentRefs = useRef([]); const SCROLL_SPEED = 65; const [isScrolling, setIsScrolling] = useState(false); // Fetch skills data from the database useEffect(() => { const getSkillsData = async () => { try { const data = await fetchSkills(); setSkillsData(data); setIsLoading(false); } catch (err) { console.error('Failed to fetch skills data:', err); setError('Failed to load skills data'); setIsLoading(false); } }; getSkillsData(); setIsVisible(true); }, []); const groupedSkills = skillsData.reduce((acc, skill) => { if (!acc[skill.type]) { acc[skill.type] = []; } acc[skill.type].push(skill); return acc; }, {} as Record); // Animation variants for the grid items const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: 0.1 } } }; const itemVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { duration: 0.5 } } }; // Toggle view mode const toggleViewMode = () => { setViewMode(prev => prev === 'scroll' ? 'grid' : 'scroll'); }; // Enhanced scroll animation with prevention of stopping during scroll useEffect(() => { if (viewMode !== 'scroll' || !isVisible || skillsData.length === 0) return; const adjustScrollAnimations = () => { scrollContentRefs.current.forEach((ref, index) => { if (!ref) return; // Get the actual width of the first set of items const children = Array.from(ref.children); const halfPoint = children.length / 2; // Calculate the width of the first set let firstSetWidth = 0; for (let i = 0; i < halfPoint; i++) { const child = children[i] as HTMLElement; if (child) { // Include the element width and its margins const style = window.getComputedStyle(child); const marginLeft = parseInt(style.marginLeft); const marginRight = parseInt(style.marginRight); firstSetWidth += child.offsetWidth + marginLeft + marginRight; } } // Calculate duration based on consistent scroll speed // Use a minimum of 6 seconds to ensure smoother animations for small categories const animDuration = Math.max(6, firstSetWidth / SCROLL_SPEED); // Apply animation with forced GPU acceleration ref.style.animationDuration = `${animDuration}s`; ref.style.transform = 'translate3d(0, 0, 0)'; ref.style.willChange = 'transform'; // Ensure animation keeps running during scroll ref.style.animationPlayState = 'running'; // Force a clean reflow void ref.offsetWidth; // Set animation direction based on index const direction = index % 2 === 0 ? 'normal' : 'reverse'; ref.style.animationDirection = direction; }); }; // Run initial animation setup setTimeout(adjustScrollAnimations, 50); setTimeout(adjustScrollAnimations, 300); setTimeout(adjustScrollAnimations, 1000); // Handle scroll events specifically to prevent animation pausing let scrollTimeout: NodeJS.Timeout; const handleScroll = () => { setIsScrolling(true); // Force animations to keep running during scroll scrollContentRefs.current.forEach(ref => { if (ref) { ref.style.animationPlayState = 'running'; } }); clearTimeout(scrollTimeout); scrollTimeout = setTimeout(() => { setIsScrolling(false); }, 100); }; // Handle resize events const handleResize = () => { // Clear animations temporarily to avoid jank scrollContentRefs.current.forEach(ref => { if (ref) { ref.style.animationPlayState = 'paused'; } }); // Small delay to allow resize to complete setTimeout(() => { adjustScrollAnimations(); // Resume animations scrollContentRefs.current.forEach(ref => { if (ref) { ref.style.animationPlayState = 'running'; } }); }, 100); }; // Add scroll event listener window.addEventListener('scroll', handleScroll, { passive: true }); window.addEventListener('resize', handleResize); return () => { window.removeEventListener('scroll', handleScroll); window.removeEventListener('resize', handleResize); clearTimeout(scrollTimeout); }; }, [viewMode, isVisible, skillsData, groupedSkills]); // Create a callback ref setter function that fixes the TypeScript error const setScrollContentRef = (element: HTMLDivElement | null, index: number) => { scrollContentRefs.current[index] = element; }; return ( Skills {/* View mode toggle */} setViewMode('scroll')} className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-300 ${viewMode === 'scroll' ? 'bg-[var(--button-bg)] text-white shadow-sm' : 'text-[var(--card-text)] hover:bg-[var(--hover-bg)]'}`} > Scroll setViewMode('grid')} className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-300 ${viewMode === 'grid' ? 'bg-[var(--button-bg)] text-white shadow-sm' : 'text-[var(--card-text)] hover:bg-[var(--hover-bg)]'}`} > Grid {isLoading ? ( ) : error ? ( {error} ) : skillsData.length === 0 ? ( No skills available ) : ( // Define the priority order for skill types (() => { // Sort the skill types according to the defined order return Object.entries(groupedSkills) .sort(([typeA], [typeB]) => { const indexA = skillTypeOrder.indexOf(typeA); const indexB = skillTypeOrder.indexOf(typeB); // If both types are in the priority list, sort by their position if (indexA >= 0 && indexB >= 0) { return indexA - indexB; } // If only typeA is in the list, it comes first if (indexA >= 0) return -1; // If only typeB is in the list, it comes first if (indexB >= 0) return 1; // If neither is in the list, maintain alphabetical order return typeA.localeCompare(typeB); }); })() .map(([type, skills]: [string, SkillType[]], typeIndex: number) => ( {type} {viewMode === 'scroll' ? ( // Scrolling view with fixed ref callback setScrollContentRef(el, typeIndex)} className={`scroll-content ${isScrolling ? 'force-animation' : ''}`} style={{ // Default animation duration that will be overridden by useEffect // Set a reasonable default to avoid initial stutter animationDuration: '20s', animationDirection: typeIndex % 2 === 0 ? 'normal' : 'reverse' }} > {/* First set of items */} {skills.map((skill: SkillType) => ( {skill.name} ))} {/* Duplicate set for seamless scrolling */} {skills.map((skill: SkillType) => ( key={`second-${skill.id}`} className="inline-flex items-center space-x-2 md:space-x-3 px-4 md:px-6 py-3 md:py-4 mx-6 md:mx-12 rounded-xl border border-[var(--border-color)] bg-[var(--card-bg)] hover:bg-[var(--hover-bg)] transition-all duration-300 transform hover:scale-90" > {skill.name} ))} ) : ( // Grid view with animation {skills.map((skill: SkillType) => ( {skill.name} ))} )} )) )} ); }; export default Skill; < /code> Пожалуйста, дайте мне несколько предложений, как сделать анимацию более плавной без отставания. а также что может это вызвать? Lang-HTML PrettyPrint-Override "> Scroll Animation Test body { margin: 0; font-family: sans-serif; background: #f5f5f5; } .scroll-wrapper { overflow: hidden; white-space: nowrap; width: 100%; height: 100px; background: #222; padding: 20px 0; } .scroll-content { display: inline-block; white-space: nowrap; animation-name: continuous-scroll; animation-timing-function: linear; animation-iteration-count: infinite; animation-duration: 20s; animation-play-state: running; will-change: transform; transform: translate3d(0, 0, 0); } .item { display: inline-block; margin: 0 30px; padding: 10px 20px; background: #fff; color: #222; border-radius: 8px; font-weight: bold; font-size: 18px; } @keyframes continuous-scroll { 0% { transform: translate3d(0, 0, 0); } 100% { transform: translate3d(-50%, 0, 0); } } HTML CSS JS React Node TypeScript Tailwind HTML CSS JS React Node TypeScript Tailwind [/code] Подробнее здесь: [url]https://stackoverflow.com/questions/79611297/circular-linear-animation-is-lagging[/url]