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">
<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>
<div class="timeline-progress">
<div class="timeline-progress-bar"></div>
</div>
<div class="timeline timeline-vertical" id="main-timeline">
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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 {
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 {
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;
}.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;
}.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;
}.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;
}@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 .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;
}@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;
}
}@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();
this.checkAnimations();
}
setupEventListeners() {
window.addEventListener('scroll', this.throttle(() => {
this.updateProgress();
this.checkAnimations();
}, 16));
window.addEventListener('resize', this.debounce(() => {
this.handleResize();
}, 250));
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() {
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');
}
});
this.container.addEventListener('keydown', (e) => {
this.handleKeyboardNavigation(e);
});
}
animateItem(item) {
if (item.classList.contains('animate')) return;
item.classList.add('animate');
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;
}
this.highlightItem(targetItem);
this.dispatchEvent('navigationClicked', {
year,
targetItem
});
}
highlightItem(item) {
this.timelineItems.forEach(i => i.classList.remove('highlighted'));
item.classList.add('highlighted');
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() {
this.checkAnimations();
this.updateProgress();
}
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);
}
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);
}
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() {
window.removeEventListener('scroll', this.updateProgress);
window.removeEventListener('resize', this.handleResize);
if (this.observer) {
this.observer.disconnect();
}
this.timelineItems = [];
this.container = null;
this.timeline = null;
this.dispatchEvent('destroyed', {});
}
}
let timelineComponent;
document.addEventListener('DOMContentLoaded', () => {
timelineComponent = new TimelineComponent();
});
if (typeof module !== 'undefined' && module.exports) {
module.exports = TimelineComponent;
}
window.TimelineComponent = TimelineComponent;
Usage Examples
Basic Timeline
const timeline = new TimelineComponent();
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
timeline.addTimelineItem({
year: '2024',
date: '2024-01-15',
title: 'New Achievement',
description: 'Description of the new achievement',
icon: '<svg>...</svg>',
position: 'end'
});
timeline.updateTimelineItem('2023', {
title: 'Updated Title',
description: 'Updated description'
});
timeline.removeTimelineItem('2022');
const data = timeline.getTimelineData();
console.log(data);
Layout Switching
timeline.setLayout('horizontal');
const currentLayout = timeline.getCurrentLayout();
timeline.container.addEventListener('timeline:layoutChanged', (e) => {
console.log('Layout changed from', e.detail.previousLayout, 'to', e.detail.layout);
});
Progress Control
timeline.setProgress(75); // 75%
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 --timeline-primary-color: #3b82f6;
--timeline-secondary-color: #64748b;
--timeline-background-color: #ffffff;
--timeline-border-color: #e2e8f0;
--timeline-text-color: #1e293b;
--timeline-muted-color: #64748b;--timeline-item-spacing: 2rem;
--timeline-marker-size: 1rem;
--timeline-line-width: 2px;
--timeline-content-padding: 1.5rem;--timeline-animation-duration: 0.6s;
--timeline-animation-easing: cubic-bezier(0.4, 0, 0.2, 1);
--timeline-hover-scale: 1.05;--timeline-title-size: 1.25rem;
--timeline-description-size: 0.875rem;
--timeline-date-size: 0.75rem;
}
Custom Animations
@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
[data-theme="dark"] {
--timeline-background-color: #1e293b;
--timeline-text-color: #f1f5f9;
--timeline-border-color: #334155;
--timeline-muted-color: #94a3b8;
}.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
<script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver"></script>
<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
const timeline = new TimelineComponent({
animationOffset: 50, // Smaller offset for better performance
autoProgress: false // Disable auto progress for large datasets
});
Integration Examples
React Integration
x
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">
</div>
);
}
Vue Integration
<template>
<div ref="timeline" class="timeline-container">
</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">
</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>