Разработка Timeline-визуализаций для сайта
Timeline-визуализации отображают события во времени: история компании, аудит-лог, биография, лента активности проекта. Могут быть вертикальными (лента новостей) и горизонтальными (исторический таймлайн).
Библиотеки
- vis-timeline — интерактивный горизонтальный таймлайн с группами, drag-drop
- react-chrono — красивые вертикальные и горизонтальные линии
- Framer Motion — кастомная анимация для вертикального таймлайна
vis-timeline — интерактивный горизонтальный
npm install vis-timeline vis-data
import { useEffect, useRef } from 'react';
import { Timeline, DataSet } from 'vis-timeline/standalone';
import 'vis-timeline/styles/vis-timeline-graph2d.css';
function ProjectTimeline({ events, groups }) {
const containerRef = useRef<HTMLDivElement>(null);
const timelineRef = useRef<Timeline>();
useEffect(() => {
if (!containerRef.current) return;
const items = new DataSet(events.map(e => ({
id: e.id,
group: e.groupId,
content: `<div class="timeline-item">${e.title}</div>`,
start: e.startDate,
end: e.endDate,
className: `status-${e.status}`
})));
const groupsDS = new DataSet(groups.map(g => ({
id: g.id,
content: g.name
})));
const timeline = new Timeline(containerRef.current, items, groupsDS, {
start: new Date(Date.now() - 7 * 24 * 3600000),
end: new Date(Date.now() + 30 * 24 * 3600000),
height: '400px',
locale: 'ru',
groupOrder: 'id',
zoomMin: 1000 * 60 * 60 * 24, // минимум 1 день
zoomMax: 1000 * 60 * 60 * 24 * 365 // максимум 1 год
});
timeline.on('select', ({ items: selectedIds }) => {
if (selectedIds.length > 0) {
const item = events.find(e => e.id === selectedIds[0]);
onEventSelect(item);
}
});
timelineRef.current = timeline;
return () => timeline.destroy();
}, []);
return <div ref={containerRef} />;
}
Вертикальный Timeline (CSS + React)
import { motion } from 'framer-motion';
interface TimelineEvent {
id: string;
date: string;
title: string;
description: string;
type: 'milestone' | 'update' | 'issue';
actor?: string;
}
function VerticalTimeline({ events }: { events: TimelineEvent[] }) {
const icons = {
milestone: '🎯',
update: '📝',
issue: '⚠️'
};
return (
<div className="relative">
{/* Вертикальная линия */}
<div className="absolute left-8 top-0 bottom-0 w-0.5 bg-gray-200" />
<div className="space-y-6 pl-20">
{events.map((event, index) => (
<motion.div
key={event.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.05 }}
className="relative"
>
{/* Иконка на линии */}
<div className="absolute -left-12 w-8 h-8 rounded-full bg-white border-2 border-blue-300 flex items-center justify-center text-sm">
{icons[event.type]}
</div>
{/* Карточка события */}
<div className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
<div className="flex items-start justify-between mb-1">
<h4 className="font-semibold text-gray-800">{event.title}</h4>
<time className="text-xs text-gray-500 ml-4 shrink-0">
{formatDate(event.date)}
</time>
</div>
<p className="text-sm text-gray-600">{event.description}</p>
{event.actor && (
<p className="text-xs text-gray-400 mt-2">{event.actor}</p>
)}
</div>
</motion.div>
))}
</div>
</div>
);
}
react-chrono
import { Chrono } from 'react-chrono';
function CompanyHistory({ milestones }) {
const items = milestones.map(m => ({
title: m.year.toString(),
cardTitle: m.title,
cardDetailedText: m.description,
media: m.imageUrl ? {
type: 'IMAGE',
source: { url: m.imageUrl }
} : undefined
}));
return (
<Chrono
items={items}
mode="VERTICAL_ALTERNATING"
theme={{
primary: '#3b82f6',
secondary: '#eff6ff',
cardBgColor: '#ffffff',
titleColor: '#374151'
}}
cardHeight={200}
useReadMore={false}
/>
);
}
Сроки
Вертикальный Timeline с анимациями — 2–3 дня. vis-timeline с группами и drag-drop — 4–6 дней.







