Круглая линейная анимация отстаетCSS

Разбираемся в CSS
Ответить
Anonymous
 Круглая линейная анимация отстает

Сообщение Anonymous »

Итак, в своем портфолио я попытался создать круговую линейную анимацию со своими навыками, используя 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
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «CSS»