A smooth, responsive timeline that reveals items as you scroll
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco.
<div class="timeline-container">
<div class="timeline">
<div class="timeline-line"></div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-content">
<div class="timeline-date">2023</div>
<h3>First Event</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-content">
<div class="timeline-date">2024</div>
<h3>Second Event</h3>
<p>Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-content">
<div class="timeline-date">2025</div>
<h3>Third Event</h3>
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco.</p>
</div>
</div>
</div>
</div>
.timeline-container {
max-width: 800px;
margin: 0 auto;
padding: 40px 20px;
}
.timeline {
position: relative;
}
.timeline-line {
position: absolute;
top: 0;
bottom: 0;
left: 20px;
width: 4px;
background: linear-gradient(to bottom, #6a11cb, #2575fc);
transform-origin: top center;
transform: scaleY(0);
animation: grow-line 1.5s ease forwards;
}
.timeline-item {
position: relative;
padding-left: 60px;
margin-bottom: 50px;
opacity: 0;
transform: translateY(20px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.timeline-item.active {
opacity: 1;
transform: translateY(0);
}
.timeline-dot {
position: absolute;
left: 12px;
top: 5px;
width: 20px;
height: 20px;
border-radius: 50%;
background: white;
border: 4px solid #6a11cb;
box-shadow: 0 0 0 4px rgba(106, 17, 203, 0.2);
transform: scale(0);
transition: transform 0.3s ease 0.5s;
}
.timeline-item.active .timeline-dot {
transform: scale(1);
}
.timeline-content {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
position: relative;
}
.timeline-content::before {
content: '';
position: absolute;
left: -10px;
top: 15px;
width: 0;
height: 0;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-right: 10px solid #fff;
}
.timeline-date {
display: inline-block;
padding: 4px 12px;
background: linear-gradient(135deg, #6a11cb, #2575fc);
color: white;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
margin-bottom: 10px;
}
.timeline-content h3 {
margin: 0 0 10px;
color: #333;
font-size: 18px;
}
.timeline-content p {
margin: 0;
color: #666;
line-height: 1.5;
}
@keyframes grow-line {
from {
transform: scaleY(0);
}
to {
transform: scaleY(1);
}
}
/* Optional control buttons */
.timeline-controls {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.timeline-btn {
padding: 8px 16px;
background: linear-gradient(135deg, #6a11cb, #2575fc);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: all 0.2s ease;
}
.timeline-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
/* Responsive styles */
@media (min-width: 768px) {
.timeline-line {
left: 50%;
margin-left: -2px;
}
.timeline-item {
padding-left: 0;
width: 50%;
}
.timeline-item:nth-child(odd) {
padding-right: 40px;
margin-left: 0;
margin-right: auto;
text-align: right;
}
.timeline-item:nth-child(even) {
padding-left: 40px;
margin-left: auto;
margin-right: 0;
}
.timeline-dot {
left: auto;
right: -14px;
}
.timeline-item:nth-child(even) .timeline-dot {
right: auto;
left: -14px;
}
.timeline-item:nth-child(odd) .timeline-content::before {
left: auto;
right: -10px;
border-right: none;
border-left: 10px solid #fff;
}
}
/* Dark mode styles */
@media (prefers-color-scheme: dark) {
.timeline-content {
background-color: #2a2a2a;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.timeline-content::before {
border-right-color: #2a2a2a;
}
.timeline-item:nth-child(odd) .timeline-content::before {
border-left-color: #2a2a2a;
}
.timeline-dot {
background: #333;
border-color: #8a3eff;
box-shadow: 0 0 0 4px rgba(138, 62, 255, 0.2);
}
.timeline-date {
background: linear-gradient(135deg, #8a3eff, #3a7bd5);
}
.timeline-content h3 {
color: #eee;
}
.timeline-content p {
color: #bbb;
}
.timeline-line {
background: linear-gradient(to bottom, #8a3eff, #3a7bd5);
}
.timeline-btn {
background: linear-gradient(135deg, #8a3eff, #3a7bd5);
}
}
/**
* Initialize the scrolling timeline
*/
function initTimeline() {
// All timeline items
const timelineItems = document.querySelectorAll('.timeline-item');
// Optional control buttons
const showAllBtn = document.querySelector('.show-all-btn');
const resetBtn = document.querySelector('.reset-btn');
/**
* Check if an element is in the viewport
* @param {HTMLElement} element - The element to check
* @returns {boolean} - Whether the element is in viewport
*/
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top <= (window.innerHeight || document.documentElement.clientHeight) * 0.8 &&
rect.bottom >= 0
);
}
/**
* Handle scroll event to reveal timeline items
*/
function onScroll() {
timelineItems.forEach(item => {
if (isInViewport(item)) {
item.classList.add('active');
}
});
}
/**
* Show all timeline items immediately
*/
function showAllItems() {
timelineItems.forEach((item, index) => {
// Stagger the animations slightly
setTimeout(() => {
item.classList.add('active');
}, index * 300);
});
}
/**
* Reset all timeline items to hidden state
*/
function resetItems() {
timelineItems.forEach(item => {
item.classList.remove('active');
});
// Activate the first item after a short delay
setTimeout(() => {
if (timelineItems.length > 0) {
timelineItems[0].classList.add('active');
}
}, 300);
}
// Add scroll event listener
window.addEventListener('scroll', onScroll);
// Initial check on page load
window.addEventListener('load', onScroll);
// Add event listeners to control buttons if they exist
if (showAllBtn) {
showAllBtn.addEventListener('click', showAllItems);
}
if (resetBtn) {
resetBtn.addEventListener('click', resetItems);
}
}
// Initialize when the DOM is ready
document.addEventListener('DOMContentLoaded', initTimeline);