Interactive Timeline Component
A comprehensive timeline component with interactive animations, multiple layouts, and rich content support for displaying chronological events and milestones
Responsive Design
Yes
Dark Mode Support
No
lines
11
Browser Compatibility
No
Live Preview
Interact with the component without leaving the page.
Interactive Timeline Component
A comprehensive timeline component that displays chronological events with interactive animations, multiple layout options, and rich content support. Perfect for showcasing company history, project milestones, or any sequential data.
Features
- Multiple Layout Options: Vertical, horizontal, and alternating layouts
- Interactive Animations: Smooth scroll-triggered animations and hover effects
- Rich Content Support: Text, images, videos, and custom HTML content
- Responsive Design: Adapts to all screen sizes and devices
- Customizable Styling: Flexible theming and color schemes
- Navigation Controls: Jump to specific timeline points
- Progress Indicator: Visual progress tracking through timeline
- Accessibility Support: ARIA attributes and keyboard navigation
Demo
<div class="timeline-container">
<!-- Timeline Header -->
<div class="timeline-header">
<h2 class="timeline-title">Company Milestones</h2>
<div class="timeline-controls">
<button class="timeline-nav-btn" data-target="2020">2020</button>
<button class="timeline-nav-btn" data-target="2021">2021</button>
<button class="timeline-nav-btn" data-target="2022">2022</button>
<button class="timeline-nav-btn" data-target="2023">2023</button>
<button class="timeline-nav-btn" data-target="2024">2024</button>
</div>
</div>
<!-- Timeline Progress -->
<div class="timeline-progress">
<div class="timeline-progress-bar"></div>
</div>
<!-- Vertical Timeline -->
<div class="timeline timeline-vertical" id="main-timeline">
<!-- Timeline Item 1 -->
<div class="timeline-item" data-year="2020" data-date="2020-01-15">
<div class="timeline-marker">
<div class="timeline-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
</div>
</div>
<div class="timeline-content">
<div class="timeline-date">January 15, 2020</div>
<h3 class="timeline-title">Company Founded</h3>
<p class="timeline-description">
Our journey began with a simple idea: to create innovative solutions that make a difference.
Founded by a team of passionate developers and designers.
</p>
<div class="timeline-media">
<img src="https://images.unsplash.com/photo-1560472354-b33ff0c44a43?w=400&h=200&fit=crop"
alt="Company founding" class="timeline-image">
</div>
<div class="timeline-tags">
<span class="timeline-tag">Startup</span>
<span class="timeline-tag">Innovation</span>
</div>
</div>
</div>
<!-- Timeline Item 2 -->
<div class="timeline-item timeline-item-right" data-year="2020" data-date="2020-06-10">
<div class="timeline-marker">
<div class="timeline-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-1 16H9V7h9v14z"/>
</svg>
</div>
</div>
<div class="timeline-content">
<div class="timeline-date">June 10, 2020</div>
<h3 class="timeline-title">First Product Launch</h3>
<p class="timeline-description">
After months of development, we launched our first product to the market.
The response was overwhelming and exceeded all our expectations.
</p>
<div class="timeline-stats">
<div class="timeline-stat">
<span class="stat-number">1,000+</span>
<span class="stat-label">Users</span>
</div>
<div class="timeline-stat">
<span class="stat-number">50+</span>
<span class="stat-label">Reviews</span>
</div>
</div>
</div>
</div>
<!-- Timeline Item 3 -->
<div class="timeline-item" data-year="2021" data-date="2021-03-22">
<div class="timeline-marker">
<div class="timeline-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
</svg>
</div>
</div>
<div class="timeline-content">
<div class="timeline-date">March 22, 2021</div>
<h3 class="timeline-title">Series A Funding</h3>
<p class="timeline-description">
Secured $2M in Series A funding to accelerate growth and expand our team.
This milestone allowed us to scale our operations significantly.
</p>
<div class="timeline-achievement">
<div class="achievement-badge">
<span class="achievement-amount">$2M</span>
<span class="achievement-text">Funding Raised</span>
</div>
</div>
</div>
</div>
<!-- Timeline Item 4 -->
<div class="timeline-item timeline-item-right" data-year="2021" data-date="2021-09-15">
<div class="timeline-marker">
<div class="timeline-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M16 4c0-1.11.89-2 2-2s2 .89 2 2-.89 2-2 2-2-.89-2-2zm4 18v-6h2.5l-2.54-7.63A2.996 2.996 0 0 0 17.06 7H16c-.8 0-1.54.37-2.01.99L12 10.5 10.01 7.99A2.996 2.996 0 0 0 8.01 7H6.94c-1.4 0-2.59.93-2.9 2.37L1.5 16H4v6h2v-6h1.5l1.7-5.1L12 14.5l2.8-3.6L16.5 16H18v6h2z"/>
</svg>
</div>
</div>
<div class="timeline-content">
<div class="timeline-date">September 15, 2021</div>
<h3 class="timeline-title">Team Expansion</h3>
<p class="timeline-description">
Grew our team from 5 to 25 talented individuals across engineering, design, and marketing.
Building a diverse and inclusive workplace culture.
</p>
<div class="timeline-team">
<div class="team-avatars">
<div class="team-avatar">👨💻</div>
<div class="team-avatar">👩🎨</div>
<div class="team-avatar">👨💼</div>
<div class="team-avatar">👩💻</div>
<div class="team-avatar">👨🎨</div>
<div class="team-more">+20</div>
</div>
</div>
</div>
</div>
<!-- Timeline Item 5 -->
<div class="timeline-item" data-year="2022" data-date="2022-05-08">
<div class="timeline-marker">
<div class="timeline-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
</div>
</div>
<div class="timeline-content">
<div class="timeline-date">May 8, 2022</div>
<h3 class="timeline-title">Product 2.0 Release</h3>
<p class="timeline-description">
Launched a completely redesigned version of our platform with advanced features,
improved performance, and enhanced user experience.
</p>
<div class="timeline-features">
<div class="feature-list">
<div class="feature-item">✨ New UI/UX Design</div>
<div class="feature-item">🚀 50% Performance Boost</div>
<div class="feature-item">🔧 Advanced Analytics</div>
<div class="feature-item">📱 Mobile App</div>
</div>
</div>
</div>
</div>
<!-- Timeline Item 6 -->
<div class="timeline-item timeline-item-right" data-year="2023" data-date="2023-01-20">
<div class="timeline-marker">
<div class="timeline-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm4.59-12.42L10 14.17l-2.59-2.58L6 13l4 4 8-8-1.41-1.42z"/>
</svg>
</div>
</div>
<div class="timeline-content">
<div class="timeline-date">January 20, 2023</div>
<h3 class="timeline-title">Global Expansion</h3>
<p class="timeline-description">
Expanded operations to 15 countries across 3 continents.
Established regional offices and localized our platform for international markets.
</p>
<div class="timeline-map">
<div class="map-regions">
<span class="region">🇺🇸 North America</span>
<span class="region">🇪🇺 Europe</span>
<span class="region">🌏 Asia Pacific</span>
</div>
</div>
</div>
</div>
<!-- Timeline Item 7 -->
<div class="timeline-item" data-year="2024" data-date="2024-01-15">
<div class="timeline-marker">
<div class="timeline-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M9 11H7v6h2v-6zm4 0h-2v6h2v-6zm4 0h-2v6h2v-6zm2.5-9H19v2h-1.5v17c0 1.1-.9 2-2 2h-11c-1.1 0-2-.9-2-2V4H1V2h3.5c.28 0 .5.22.5.5s.22.5.5.5h9c.28 0 .5-.22.5-.5s.22-.5.5-.5z"/>
</svg>
</div>
</div>
<div class="timeline-content">
<div class="timeline-date">January 15, 2024</div>
<h3 class="timeline-title">Looking Forward</h3>
<p class="timeline-description">
As we celebrate our 4th anniversary, we're excited about the future.
New innovations, partnerships, and opportunities await us in 2024 and beyond.
</p>
<div class="timeline-future">
<div class="future-goals">
<div class="goal-item">🎯 AI Integration</div>
<div class="goal-item">🤝 Strategic Partnerships</div>
<div class="goal-item">🌱 Sustainability Focus</div>
</div>
</div>
</div>
</div>
</div>
<!-- Horizontal Timeline -->
<div class="timeline timeline-horizontal" id="horizontal-timeline" style="display: none;">
<div class="timeline-track">
<div class="timeline-line"></div>
<div class="timeline-item-horizontal" data-year="2020">
<div class="timeline-marker-horizontal">
<div class="timeline-icon">🚀</div>
</div>
<div class="timeline-content-horizontal">
<div class="timeline-year">2020</div>
<div class="timeline-title">Founded</div>
</div>
</div>
<div class="timeline-item-horizontal" data-year="2021">
<div class="timeline-marker-horizontal">
<div class="timeline-icon">💰</div>
</div>
<div class="timeline-content-horizontal">
<div class="timeline-year">2021</div>
<div class="timeline-title">Series A</div>
</div>
</div>
<div class="timeline-item-horizontal" data-year="2022">
<div class="timeline-marker-horizontal">
<div class="timeline-icon">🎉</div>
</div>
<div class="timeline-content-horizontal">
<div class="timeline-year">2022</div>
<div class="timeline-title">Product 2.0</div>
</div>
</div>
<div class="timeline-item-horizontal" data-year="2023">
<div class="timeline-marker-horizontal">
<div class="timeline-icon">🌍</div>
</div>
<div class="timeline-content-horizontal">
<div class="timeline-year">2023</div>
<div class="timeline-title">Global</div>
</div>
</div>
<div class="timeline-item-horizontal" data-year="2024">
<div class="timeline-marker-horizontal">
<div class="timeline-icon">🔮</div>
</div>
<div class="timeline-content-horizontal">
<div class="timeline-year">2024</div>
<div class="timeline-title">Future</div>
</div>
</div>
</div>
</div>
<!-- Timeline Layout Toggle -->
<div class="timeline-layout-toggle">
<button class="layout-btn active" data-layout="vertical">Vertical</button>
<button class="layout-btn" data-layout="horizontal">Horizontal</button>
</div>
</div>.timeline-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
position: relative;
}
/* Timeline Header */
.timeline-header {
text-align: center;
margin-bottom: 3rem;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 2rem;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.timeline-title {
font-size: 2.5rem;
font-weight: 700;
color: white;
margin: 0 0 1.5rem 0;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.timeline-controls {
display: flex;
justify-content: center;
gap: 1rem;
flex-wrap: wrap;
}
.timeline-nav-btn {
padding: 0.75rem 1.5rem;
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 25px;
color: white;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
}
.timeline-nav-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
.timeline-nav-btn.active {
background: white;
color: #667eea;
}
/* Timeline Progress */
.timeline-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.2);
z-index: 1000;
}
.timeline-progress-bar {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
width: 0;
transition: width 0.3s ease;
}
/* Vertical Timeline */
.timeline-vertical {
position: relative;
padding: 2rem 0;
}
.timeline-vertical::before {
content: '';
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 4px;
background: linear-gradient(180deg, #667eea, #764ba2);
transform: translateX(-50%);
border-radius: 2px;
box-shadow: 0 0 20px rgba(102, 126, 234, 0.5);
}
.timeline-item {
position: relative;
margin-bottom: 4rem;
display: flex;
align-items: center;
opacity: 0;
transform: translateY(50px);
transition: all 0.6s ease;
}
.timeline-item.animate {
opacity: 1;
transform: translateY(0);
}
.timeline-item:nth-child(even) {
flex-direction: row-reverse;
}
.timeline-item-right {
flex-direction: row-reverse;
}
.timeline-marker {
position: absolute;
left: 50%;
transform: translateX(-50%);
z-index: 10;
width: 60px;
height: 60px;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 0 4px #667eea, 0 0 20px rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
}
.timeline-item:hover .timeline-marker {
transform: translateX(-50%) scale(1.1);
box-shadow: 0 0 0 6px #667eea, 0 0 30px rgba(102, 126, 234, 0.5);
}
.timeline-icon {
width: 30px;
height: 30px;
color: #667eea;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
.timeline-icon svg {
width: 100%;
height: 100%;
}
.timeline-content {
background: white;
border-radius: 16px;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
width: calc(50% - 60px);
margin: 0 30px;
position: relative;
border: 1px solid rgba(102, 126, 234, 0.1);
transition: all 0.3s ease;
}
.timeline-item:hover .timeline-content {
transform: translateY(-5px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}
.timeline-content::before {
content: '';
position: absolute;
top: 50%;
width: 0;
height: 0;
border: 15px solid transparent;
transform: translateY(-50%);
}
.timeline-item:not(.timeline-item-right) .timeline-content::before {
right: -30px;
border-left-color: white;
}
.timeline-item-right .timeline-content::before {
left: -30px;
border-right-color: white;
}
.timeline-date {
color: #667eea;
font-weight: 600;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 0.5rem;
}
.timeline-content .timeline-title {
font-size: 1.5rem;
font-weight: 700;
color: #1a1a1a;
margin: 0 0 1rem 0;
}
.timeline-description {
color: #666;
line-height: 1.6;
margin-bottom: 1.5rem;
}
.timeline-media {
margin: 1.5rem 0;
}
.timeline-image {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.timeline-tags {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-top: 1rem;
}
.timeline-tag {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
}
.timeline-stats {
display: flex;
gap: 2rem;
margin-top: 1.5rem;
}
.timeline-stat {
text-align: center;
}
.stat-number {
display: block;
font-size: 2rem;
font-weight: 700;
color: #667eea;
}
.stat-label {
font-size: 0.875rem;
color: #666;
text-transform: uppercase;
letter-spacing: 1px;
}
.timeline-achievement {
margin-top: 1.5rem;
}
.achievement-badge {
background: linear-gradient(135deg, #10b981, #059669);
color: white;
padding: 1rem 2rem;
border-radius: 12px;
text-align: center;
display: inline-block;
}
.achievement-amount {
display: block;
font-size: 1.5rem;
font-weight: 700;
}
.achievement-text {
font-size: 0.875rem;
opacity: 0.9;
}
.timeline-team {
margin-top: 1.5rem;
}
.team-avatars {
display: flex;
align-items: center;
gap: 0.5rem;
}
.team-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
border: 2px solid white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.team-more {
width: 40px;
height: 40px;
border-radius: 50%;
background: #f3f4f6;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 600;
color: #666;
border: 2px solid white;
}
.timeline-features {
margin-top: 1.5rem;
}
.feature-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 0.75rem;
}
.feature-item {
background: #f8fafc;
padding: 0.75rem 1rem;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
border-left: 4px solid #667eea;
}
.timeline-map {
margin-top: 1.5rem;
}
.map-regions {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
.region {
background: rgba(102, 126, 234, 0.1);
color: #667eea;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 600;
border: 1px solid rgba(102, 126, 234, 0.2);
}
.timeline-future {
margin-top: 1.5rem;
}
.future-goals {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
}
.goal-item {
background: linear-gradient(135deg, #f59e0b, #d97706);
color: white;
padding: 1rem;
border-radius: 12px;
text-align: center;
font-weight: 600;
font-size: 0.875rem;
}
/* Horizontal Timeline */
.timeline-horizontal {
margin: 3rem 0;
padding: 2rem;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.timeline-track {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 2rem 0;
overflow-x: auto;
min-width: 800px;
}
.timeline-line {
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
transform: translateY(-50%);
border-radius: 2px;
}
.timeline-item-horizontal {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
z-index: 10;
cursor: pointer;
transition: all 0.3s ease;
}
.timeline-item-horizontal:hover {
transform: translateY(-5px);
}
.timeline-marker-horizontal {
width: 60px;
height: 60px;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 0 4px #667eea, 0 5px 15px rgba(0, 0, 0, 0.2);
margin-bottom: 1rem;
transition: all 0.3s ease;
}
.timeline-item-horizontal:hover .timeline-marker-horizontal {
transform: scale(1.1);
box-shadow: 0 0 0 6px #667eea, 0 8px 25px rgba(0, 0, 0, 0.3);
}
.timeline-content-horizontal {
text-align: center;
background: white;
padding: 1rem 1.5rem;
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
min-width: 120px;
}
.timeline-year {
font-size: 1.25rem;
font-weight: 700;
color: #667eea;
margin-bottom: 0.25rem;
}
.timeline-content-horizontal .timeline-title {
font-size: 0.875rem;
font-weight: 600;
color: #666;
margin: 0;
}
/* Layout Toggle */
.timeline-layout-toggle {
display: flex;
justify-content: center;
gap: 1rem;
margin-top: 3rem;
}
.layout-btn {
padding: 0.75rem 2rem;
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 25px;
color: white;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
}
.layout-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.layout-btn.active {
background: white;
color: #667eea;
}
/* Responsive Design */
@media (max-width: 768px) {
.timeline-container {
padding: 1rem;
}
.timeline-title {
font-size: 2rem;
}
.timeline-controls {
gap: 0.5rem;
}
.timeline-nav-btn {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.timeline-vertical::before {
left: 30px;
}
.timeline-item {
flex-direction: row !important;
margin-bottom: 2rem;
}
.timeline-marker {
left: 30px;
transform: translateX(-50%);
width: 40px;
height: 40px;
}
.timeline-icon {
width: 20px;
height: 20px;
font-size: 1rem;
}
.timeline-content {
width: calc(100% - 80px);
margin-left: 60px;
margin-right: 0;
}
.timeline-content::before {
left: -15px;
border-right-color: white;
border-left-color: transparent;
}
.timeline-stats {
gap: 1rem;
}
.stat-number {
font-size: 1.5rem;
}
.feature-list {
grid-template-columns: 1fr;
}
.timeline-track {
min-width: 600px;
}
.timeline-marker-horizontal {
width: 40px;
height: 40px;
}
.timeline-content-horizontal {
min-width: 80px;
padding: 0.75rem 1rem;
}
}
@media (max-width: 480px) {
.timeline-title {
font-size: 1.5rem;
}
.timeline-content {
padding: 1.5rem;
}
.timeline-content .timeline-title {
font-size: 1.25rem;
}
.timeline-image {
height: 150px;
}
.team-avatars {
gap: 0.25rem;
}
.team-avatar,
.team-more {
width: 30px;
height: 30px;
font-size: 0.875rem;
}
.timeline-track {
min-width: 400px;
}
}
/* Dark Theme */
.dark .timeline-container {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
}
.dark .timeline-content {
background: #2d3748;
color: #e2e8f0;
border-color: rgba(255, 255, 255, 0.1);
}
.dark .timeline-content .timeline-title {
color: #f7fafc;
}
.dark .timeline-description {
color: #cbd5e0;
}
.dark .timeline-content::before {
border-left-color: #2d3748;
border-right-color: #2d3748;
}
.dark .feature-item {
background: #4a5568;
color: #e2e8f0;
}
.dark .timeline-content-horizontal {
background: #2d3748;
color: #e2e8f0;
}
/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
.timeline-item,
.timeline-marker,
.timeline-content,
.timeline-item-horizontal,
.timeline-marker-horizontal {
transition: none;
animation: none;
}
.timeline-item {
opacity: 1;
transform: none;
}
}
/* High Contrast */
@media (prefers-contrast: high) {
.timeline-vertical::before {
background: #000;
}
.timeline-marker {
box-shadow: 0 0 0 4px #000;
}
.timeline-content {
border: 2px solid #000;
}
}class TimelineComponent {
constructor(options = {}) {
this.options = {
container: '.timeline-container',
animationOffset: 100,
autoProgress: true,
smoothScroll: true,
...options
};
this.container = document.querySelector(this.options.container);
this.timeline = null;
this.timelineItems = [];
this.currentLayout = 'vertical';
this.isAnimating = false;
this.init();
}
init() {
if (!this.container) {
console.error('Timeline container not found');
return;
}
this.timeline = this.container.querySelector('.timeline');
this.timelineItems = Array.from(this.container.querySelectorAll('.timeline-item'));
this.setupEventListeners();
this.setupIntersectionObserver();
this.setupProgressTracking();
this.setupNavigation();
this.setupLayoutToggle();
this.setupAccessibility();
// Initial animation check
this.checkAnimations();
}
setupEventListeners() {
// Scroll events
window.addEventListener('scroll', this.throttle(() => {
this.updateProgress();
this.checkAnimations();
}, 16));
// Resize events
window.addEventListener('resize', this.debounce(() => {
this.handleResize();
}, 250));
// Timeline item hover effects
this.timelineItems.forEach(item => {
item.addEventListener('mouseenter', () => {
this.handleItemHover(item, true);
});
item.addEventListener('mouseleave', () => {
this.handleItemHover(item, false);
});
item.addEventListener('click', () => {
this.handleItemClick(item);
});
});
}
setupIntersectionObserver() {
const observerOptions = {
threshold: 0.1,
rootMargin: `${this.options.animationOffset}px 0px`
};
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.animateItem(entry.target);
}
});
}, observerOptions);
this.timelineItems.forEach(item => {
this.observer.observe(item);
});
}
setupProgressTracking() {
this.progressBar = this.container.querySelector('.timeline-progress-bar');
if (this.progressBar) {
this.updateProgress();
}
}
setupNavigation() {
const navButtons = this.container.querySelectorAll('.timeline-nav-btn');
navButtons.forEach(btn => {
btn.addEventListener('click', () => {
const target = btn.dataset.target;
this.navigateToYear(target);
this.updateActiveNavButton(btn);
});
});
}
setupLayoutToggle() {
const layoutButtons = this.container.querySelectorAll('.layout-btn');
layoutButtons.forEach(btn => {
btn.addEventListener('click', () => {
const layout = btn.dataset.layout;
this.switchLayout(layout);
this.updateActiveLayoutButton(btn);
});
});
}
setupAccessibility() {
// Add ARIA attributes
this.timelineItems.forEach((item, index) => {
item.setAttribute('role', 'article');
item.setAttribute('aria-label', `Timeline item ${index + 1}`);
const content = item.querySelector('.timeline-content');
if (content) {
content.setAttribute('tabindex', '0');
}
});
// Keyboard navigation
this.container.addEventListener('keydown', (e) => {
this.handleKeyboardNavigation(e);
});
}
animateItem(item) {
if (item.classList.contains('animate')) return;
item.classList.add('animate');
// Trigger custom event
this.dispatchEvent('itemAnimated', {
item,
year: item.dataset.year,
date: item.dataset.date
});
}
checkAnimations() {
if (this.isAnimating) return;
this.timelineItems.forEach(item => {
if (this.isInViewport(item) && !item.classList.contains('animate')) {
this.animateItem(item);
}
});
}
isInViewport(element) {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
return (
rect.top <= windowHeight - this.options.animationOffset &&
rect.bottom >= this.options.animationOffset
);
}
updateProgress() {
if (!this.progressBar || !this.options.autoProgress) return;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const documentHeight = document.documentElement.scrollHeight - window.innerHeight;
const progress = Math.min(scrollTop / documentHeight, 1);
this.progressBar.style.width = `${progress * 100}%`;
}
navigateToYear(year) {
const targetItem = this.container.querySelector(`[data-year="${year}"]`);
if (!targetItem) return;
this.isAnimating = true;
if (this.options.smoothScroll) {
targetItem.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
setTimeout(() => {
this.isAnimating = false;
}, 1000);
} else {
targetItem.scrollIntoView({ block: 'center' });
this.isAnimating = false;
}
// Highlight the target item
this.highlightItem(targetItem);
this.dispatchEvent('navigationClicked', {
year,
targetItem
});
}
highlightItem(item) {
// Remove previous highlights
this.timelineItems.forEach(i => i.classList.remove('highlighted'));
// Add highlight to target item
item.classList.add('highlighted');
// Remove highlight after animation
setTimeout(() => {
item.classList.remove('highlighted');
}, 2000);
}
switchLayout(layout) {
if (this.currentLayout === layout) return;
const verticalTimeline = this.container.querySelector('.timeline-vertical');
const horizontalTimeline = this.container.querySelector('.timeline-horizontal');
if (layout === 'vertical') {
verticalTimeline.style.display = 'block';
horizontalTimeline.style.display = 'none';
} else if (layout === 'horizontal') {
verticalTimeline.style.display = 'none';
horizontalTimeline.style.display = 'block';
}
this.currentLayout = layout;
this.dispatchEvent('layoutChanged', {
layout,
previousLayout: this.currentLayout
});
}
updateActiveNavButton(activeBtn) {
const navButtons = this.container.querySelectorAll('.timeline-nav-btn');
navButtons.forEach(btn => btn.classList.remove('active'));
activeBtn.classList.add('active');
}
updateActiveLayoutButton(activeBtn) {
const layoutButtons = this.container.querySelectorAll('.layout-btn');
layoutButtons.forEach(btn => btn.classList.remove('active'));
activeBtn.classList.add('active');
}
handleItemHover(item, isHovering) {
const marker = item.querySelector('.timeline-marker');
const content = item.querySelector('.timeline-content');
if (isHovering) {
marker?.classList.add('hovered');
content?.classList.add('hovered');
} else {
marker?.classList.remove('hovered');
content?.classList.remove('hovered');
}
this.dispatchEvent('itemHover', {
item,
isHovering,
year: item.dataset.year
});
}
handleItemClick(item) {
const year = item.dataset.year;
const date = item.dataset.date;
this.dispatchEvent('itemClicked', {
item,
year,
date
});
}
handleKeyboardNavigation(e) {
const focusedElement = document.activeElement;
const currentIndex = this.timelineItems.indexOf(focusedElement.closest('.timeline-item'));
if (currentIndex === -1) return;
let nextIndex;
switch (e.key) {
case 'ArrowDown':
case 'ArrowRight':
e.preventDefault();
nextIndex = Math.min(currentIndex + 1, this.timelineItems.length - 1);
break;
case 'ArrowUp':
case 'ArrowLeft':
e.preventDefault();
nextIndex = Math.max(currentIndex - 1, 0);
break;
case 'Home':
e.preventDefault();
nextIndex = 0;
break;
case 'End':
e.preventDefault();
nextIndex = this.timelineItems.length - 1;
break;
default:
return;
}
const nextItem = this.timelineItems[nextIndex];
const nextContent = nextItem.querySelector('.timeline-content');
if (nextContent) {
nextContent.focus();
}
}
handleResize() {
// Recalculate positions and animations
this.checkAnimations();
this.updateProgress();
}
// Utility functions
throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
dispatchEvent(eventName, detail) {
const event = new CustomEvent(`timeline:${eventName}`, {
detail,
bubbles: true,
cancelable: true
});
this.container.dispatchEvent(event);
}
// Public API methods
addTimelineItem(itemData) {
const { year, date, title, description, icon, position } = itemData;
const itemHTML = `
<div class="timeline-item" data-year="${year}" data-date="${date}">
<div class="timeline-marker">
<div class="timeline-icon">
${icon || '<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="12" r="10"/></svg>'}
</div>
</div>
<div class="timeline-content">
<div class="timeline-date">${new Date(date).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}</div>
<h3 class="timeline-title">${title}</h3>
<p class="timeline-description">${description}</p>
</div>
</div>
`;
const timeline = this.container.querySelector('.timeline-vertical');
if (position === 'start') {
timeline.insertAdjacentHTML('afterbegin', itemHTML);
} else {
timeline.insertAdjacentHTML('beforeend', itemHTML);
}
// Refresh timeline items
this.timelineItems = Array.from(this.container.querySelectorAll('.timeline-item'));
this.setupEventListeners();
this.dispatchEvent('itemAdded', { itemData });
}
removeTimelineItem(year) {
const item = this.container.querySelector(`[data-year="${year}"]`);
if (item) {
item.remove();
this.timelineItems = Array.from(this.container.querySelectorAll('.timeline-item'));
this.dispatchEvent('itemRemoved', { year });
}
}
updateTimelineItem(year, newData) {
const item = this.container.querySelector(`[data-year="${year}"]`);
if (!item) return;
const content = item.querySelector('.timeline-content');
if (newData.title) {
const titleElement = content.querySelector('.timeline-title');
if (titleElement) titleElement.textContent = newData.title;
}
if (newData.description) {
const descElement = content.querySelector('.timeline-description');
if (descElement) descElement.textContent = newData.description;
}
if (newData.date) {
item.dataset.date = newData.date;
const dateElement = content.querySelector('.timeline-date');
if (dateElement) {
dateElement.textContent = new Date(newData.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
}
this.dispatchEvent('itemUpdated', { year, newData });
}
getTimelineData() {
return this.timelineItems.map(item => ({
year: item.dataset.year,
date: item.dataset.date,
title: item.querySelector('.timeline-title')?.textContent,
description: item.querySelector('.timeline-description')?.textContent
}));
}
setProgress(percentage) {
if (this.progressBar) {
this.progressBar.style.width = `${Math.min(Math.max(percentage, 0), 100)}%`;
}
}
scrollToItem(year) {
this.navigateToYear(year);
}
getCurrentLayout() {
return this.currentLayout;
}
setLayout(layout) {
this.switchLayout(layout);
const layoutBtn = this.container.querySelector(`[data-layout="${layout}"]`);
if (layoutBtn) {
this.updateActiveLayoutButton(layoutBtn);
}
}
destroy() {
// Remove event listeners
window.removeEventListener('scroll', this.updateProgress);
window.removeEventListener('resize', this.handleResize);
// Disconnect observer
if (this.observer) {
this.observer.disconnect();
}
// Clear references
this.timelineItems = [];
this.container = null;
this.timeline = null;
this.dispatchEvent('destroyed', {});
}
}
// Auto-initialize
let timelineComponent;
document.addEventListener('DOMContentLoaded', () => {
timelineComponent = new TimelineComponent();
});
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = TimelineComponent;
}
// Global API
window.TimelineComponent = TimelineComponent;Usage Examples
Basic Timeline
// Initialize with default options
const timeline = new TimelineComponent();
// Listen to events
timeline.container.addEventListener('timeline:itemAnimated', (e) => {
console.log('Item animated:', e.detail.year);
});
timeline.container.addEventListener('timeline:itemClicked', (e) => {
console.log('Item clicked:', e.detail.year, e.detail.date);
});Custom Configuration
const timeline = new TimelineComponent({
container: '#my-timeline',
animationOffset: 150,
autoProgress: false,
smoothScroll: false
});Dynamic Timeline Management
// Add new timeline item
timeline.addTimelineItem({
year: '2024',
date: '2024-01-15',
title: 'New Achievement',
description: 'Description of the new achievement',
icon: '<svg>...</svg>',
position: 'end'
});
// Update existing item
timeline.updateTimelineItem('2023', {
title: 'Updated Title',
description: 'Updated description'
});
// Remove item
timeline.removeTimelineItem('2022');
// Get all timeline data
const data = timeline.getTimelineData();
console.log(data);Layout Switching
// Switch to horizontal layout
timeline.setLayout('horizontal');
// Get current layout
const currentLayout = timeline.getCurrentLayout();
// Listen to layout changes
timeline.container.addEventListener('timeline:layoutChanged', (e) => {
console.log('Layout changed from', e.detail.previousLayout, 'to', e.detail.layout);
});Progress Control
// Set custom progress
timeline.setProgress(75); // 75%
// Navigate to specific year
timeline.scrollToItem('2023');API Reference
Constructor Options
| Option | Type | Default | Description |
|---|---|---|---|
container | string | ’.timeline-container’ | CSS selector for timeline container |
animationOffset | number | 100 | Offset in pixels for animation trigger |
autoProgress | boolean | true | Enable automatic progress tracking |
smoothScroll | boolean | true | Enable smooth scrolling navigation |
Methods
addTimelineItem(itemData)
Adds a new timeline item.
Parameters:
itemData(object): Item configurationyear(string): Year identifierdate(string): ISO date stringtitle(string): Item titledescription(string): Item descriptionicon(string): SVG icon HTMLposition(string): ‘start’ or ‘end’
removeTimelineItem(year)
Removes a timeline item by year.
Parameters:
year(string): Year identifier
updateTimelineItem(year, newData)
Updates an existing timeline item.
Parameters:
year(string): Year identifiernewData(object): Updated data
getTimelineData()
Returns array of all timeline items data.
Returns: Array of timeline item objects
setProgress(percentage)
Sets the progress bar percentage.
Parameters:
percentage(number): Progress percentage (0-100)
scrollToItem(year)
Scrolls to a specific timeline item.
Parameters:
year(string): Year identifier
setLayout(layout)
Switches timeline layout.
Parameters:
layout(string): ‘vertical’ or ‘horizontal’
getCurrentLayout()
Returns current timeline layout.
Returns: String (‘vertical’ or ‘horizontal’)
destroy()
Cleans up the timeline component.
Events
All events are dispatched on the timeline container with the timeline: prefix.
timeline:itemAnimated
Fired when a timeline item is animated into view.
Detail:
item(Element): The animated itemyear(string): Item yeardate(string): Item date
timeline:itemClicked
Fired when a timeline item is clicked.
Detail:
item(Element): The clicked itemyear(string): Item yeardate(string): Item date
timeline:itemHover
Fired when a timeline item is hovered.
Detail:
item(Element): The hovered itemisHovering(boolean): Hover stateyear(string): Item year
timeline:navigationClicked
Fired when navigation button is clicked.
Detail:
year(string): Target yeartargetItem(Element): Target timeline item
timeline:layoutChanged
Fired when layout is changed.
Detail:
layout(string): New layoutpreviousLayout(string): Previous layout
timeline:itemAdded
Fired when a new item is added.
Detail:
itemData(object): Added item data
timeline:itemRemoved
Fired when an item is removed.
Detail:
year(string): Removed item year
timeline:itemUpdated
Fired when an item is updated.
Detail:
year(string): Updated item yearnewData(object): Updated data
timeline:destroyed
Fired when the component is destroyed.
CSS Classes
Layout Classes
.timeline-container- Main container.timeline-vertical- Vertical timeline layout.timeline-horizontal- Horizontal timeline layout.timeline-item- Individual timeline item.timeline-marker- Timeline marker/dot.timeline-content- Item content area
State Classes
.animate- Applied when item is animated.highlighted- Applied when item is highlighted.hovered- Applied when item is hovered.active- Applied to active navigation buttons
Navigation Classes
.timeline-navigation- Navigation container.timeline-nav-btn- Navigation button.layout-controls- Layout control container.layout-btn- Layout toggle button
Customization
CSS Variables
:root {
/* Colors */
--timeline-primary-color: #3b82f6;
--timeline-secondary-color: #64748b;
--timeline-background-color: #ffffff;
--timeline-border-color: #e2e8f0;
--timeline-text-color: #1e293b;
--timeline-muted-color: #64748b;
/* Spacing */
--timeline-item-spacing: 2rem;
--timeline-marker-size: 1rem;
--timeline-line-width: 2px;
--timeline-content-padding: 1.5rem;
/* Animation */
--timeline-animation-duration: 0.6s;
--timeline-animation-easing: cubic-bezier(0.4, 0, 0.2, 1);
--timeline-hover-scale: 1.05;
/* Typography */
--timeline-title-size: 1.25rem;
--timeline-description-size: 0.875rem;
--timeline-date-size: 0.75rem;
}Custom Animations
/* Custom slide-in animation */
@keyframes customSlideIn {
from {
opacity: 0;
transform: translateX(-50px) scale(0.9);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
.timeline-item.animate .timeline-content {
animation: customSlideIn var(--timeline-animation-duration) var(--timeline-animation-easing);
}Theme Integration
/* Dark theme */
[data-theme="dark"] {
--timeline-background-color: #1e293b;
--timeline-text-color: #f1f5f9;
--timeline-border-color: #334155;
--timeline-muted-color: #94a3b8;
}
/* Custom brand colors */
.timeline-brand {
--timeline-primary-color: #8b5cf6;
--timeline-secondary-color: #a78bfa;
}Accessibility
ARIA Support
- Timeline items have
role="article"and descriptivearia-label - Content areas are focusable with
tabindex="0" - Navigation buttons have proper ARIA states
- Progress indicators include
aria-valuenowandaria-valuemax
Keyboard Navigation
- Arrow Keys: Navigate between timeline items
- Home/End: Jump to first/last item
- Enter/Space: Activate focused item
- Tab: Navigate through interactive elements
Screen Reader Support
- Semantic HTML structure with proper headings
- Descriptive text for all interactive elements
- Live regions for dynamic content updates
- Alternative text for icons and graphics
Reduced Motion
@media (prefers-reduced-motion: reduce) {
.timeline-item {
animation: none !important;
transition: none !important;
}
.timeline-item.animate {
opacity: 1;
transform: none;
}
}Browser Support
- Modern Browsers: Full support with all features
- IE 11: Basic functionality with polyfills
- Mobile Browsers: Optimized touch interactions
Required Polyfills for IE 11
<!-- Intersection Observer -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver"></script>
<!-- Custom Events -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=CustomEvent"></script>Performance Considerations
Optimization Tips
- Lazy Loading: Use Intersection Observer for efficient animation triggering
- Throttling: Scroll events are throttled to 60fps
- Debouncing: Resize events are debounced to prevent excessive calculations
- Memory Management: Proper cleanup in destroy method
Large Datasets
// For timelines with many items, consider virtual scrolling
const timeline = new TimelineComponent({
animationOffset: 50, // Smaller offset for better performance
autoProgress: false // Disable auto progress for large datasets
});Integration Examples
React Integration
import React, { useEffect, useRef } from 'react';
import TimelineComponent from './timeline-component';
function Timeline({ data, onItemClick }) {
const timelineRef = useRef(null);
const componentRef = useRef(null);
useEffect(() => {
componentRef.current = new TimelineComponent({
container: timelineRef.current
});
const handleItemClick = (e) => {
onItemClick?.(e.detail);
};
timelineRef.current.addEventListener('timeline:itemClicked', handleItemClick);
return () => {
timelineRef.current?.removeEventListener('timeline:itemClicked', handleItemClick);
componentRef.current?.destroy();
};
}, [onItemClick]);
return (
<div ref={timelineRef} className="timeline-container">
{/* Timeline HTML structure */}
</div>
);
}Vue Integration
<template>
<div ref="timeline" class="timeline-container">
<!-- Timeline HTML structure -->
</div>
</template>
<script>
import TimelineComponent from './timeline-component';
export default {
name: 'Timeline',
props: ['data'],
mounted() {
this.timeline = new TimelineComponent({
container: this.$refs.timeline
});
this.$refs.timeline.addEventListener('timeline:itemClicked', this.handleItemClick);
},
beforeUnmount() {
this.timeline?.destroy();
},
methods: {
handleItemClick(e) {
this.$emit('item-click', e.detail);
}
}
};
</script>Angular Integration
import { Component, ElementRef, OnInit, OnDestroy } from '@angular/core';
import { TimelineComponent } from './timeline-component';
@Component({
selector: 'app-timeline',
template: `
<div class="timeline-container">
<!-- Timeline HTML structure -->
</div>
`
})
export class TimelineComponentWrapper implements OnInit, OnDestroy {
private timeline: TimelineComponent;
constructor(private elementRef: ElementRef) {}
ngOnInit() {
this.timeline = new TimelineComponent({
container: this.elementRef.nativeElement.querySelector('.timeline-container')
});
}
ngOnDestroy() {
this.timeline?.destroy();
}
}HTML
4
lines
CSS
7
lines
<div class="timeline-container">
<h2>Interactive Timeline Component</h2>
<p>Comprehensive timeline with interactive animations</p>
</div>