Как анимировать чистые данные, такие как числа от 0 до 100, в кадре-движении?
<motion.div>
{num}
</motion.div>
Во фреймере анимировать стили довольно просто, но как я могу анимировать чистое число, я хочу, чтобы 0 было анимировано до 100 с конфигурацией перехода, как я могу это сделать?
3 ответа
В настоящее время Framer Motion может только анимировать атрибуты CSS узлов DOM. В его системе отслеживания проблем есть предлагаемая функция, которая упоминает произвольную "анимацию без головы".
А пока я думаю, вам нужно использовать библиотеку более низкого уровня, например Popmotion:
import { spring } from "popmotion";
const node = document.getElementById("counter");
spring({
from: 0,
to: 100,
stiffness: 200,
damping: 40
}).start({
update(value) {
node.textContent = value.toFixed(2);
}
});
Обратите внимание, что Framer Motion основан на Popmotion, поэтому вы можете использовать эту библиотеку, не увеличивая размер пакета.
Предыдущий ответ мне не помог, поэтому я реализовал его здесь, используя часть анимированного контента этой страницы https://www.framer.com/motion/animation/
import {motion, useMotionValue, useTransform, animate} from 'framer-motionn';
const Counter = ({ from, to, duration }) => {
const count = useMotionValue(from);
const rounded = useTransform(count, (latest) => Math.round(latest));
useEffect(() => {
const controls = animate(count, to, { duration: duration });
return controls.stop;
}, []);
return <motion.p>{rounded}</motion.p>;
};
Для тех, кто ищет подход на основе TypeScript, включая выполнение в представлении.
import { animate } from "framer-motion";
import React, { useEffect, useRef, useState } from "react";
import { motion } from "framer-motion";
interface CounterProps {
from: number;
to: number;
}
const Counter: React.FC<CounterProps> = ({ from, to }) => {
const nodeRef = useRef<HTMLParagraphElement | null>(null);
const [isInView, setIsInView] = useState(false);
useEffect(() => {
const node = nodeRef.current;
if (!node) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsInView(true);
}
});
},
{ threshold: 0.1 }
);
observer.observe(node);
return () => {
observer.unobserve(node);
};
}, []);
useEffect(() => {
if (!isInView) return;
const node = nodeRef.current;
if (!node) return;
const controls = animate(from, to, {
duration: 1,
onUpdate(value) {
node.textContent = Math.round(value).toString();
},
});
return () => controls.stop();
}, [from, to, isInView]);
return (
<motion.p
ref={nodeRef}
initial={{ opacity: 0, scale: 0.1 }}
animate={isInView ? { opacity: 1, scale: 1 } : {}}
transition={{ duration: 0.4 }}
/>
);
};
export default Counter;