TML001

Scrolling Timeline

A smooth, responsive timeline that reveals items as you scroll

Animations

Live Preview

2023

First Event

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

2024

Second Event

Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

2025

Third Event

Ut enim ad minim veniam, quis nostrud exercitation ullamco.

Code

HTML
<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>
CSS
.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);
  }
}
JavaScript
/**
 * 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);