DEV Community

Cover image for Timeline animation with HTML, CSS and JavaScript
Vineet Kalghatgi
Vineet Kalghatgi

Posted on

Timeline animation with HTML, CSS and JavaScript

A timeline animation is useful when you want to display information with respect to the passage of time. For instance, you can use it in a portfolio website to show your journey as a professional:

2020 - Graduated from XYZ University

2021 - Joined a brilliant startup

2023 - Promoted to Senior Developer

Or you can use it in a landing page to describe an organization's history and culture:

1994 - Amazon was founded in a garage

1997 - Amazon IPOs at $18.00/share

1998 - Expands beyond books

...you get the idea.


HTML

A container div that contains:

  • The central vertical line
  • Cards positioned to the right
  • Cards positioned to the left
<div class="container" id="container">
        <div class="line"></div>
        <div class='left card'>
            <p><b>2023</b></p>
            <p>Add your text here</p>
        </div>
        <div class='right card'>
            <p><b>2022</b></p>
            <p>Add your text here</p>
        </div>
        <div class='left card'>
            <p><b>2021</b></p>
            <p>Add your text here</p>
        </div>
        <div class='right card'>
            <p><b>2020</b></p>
            <p>Add your text here</p>
        </div>
        <div class='left card'>
            <p><b>2020</b></p>
            <p>Add your text here</p>
        </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

CSS

We shall keep the line centrally positioned relative to the container div by using the relative and position:absolute classes. For the cards, the container will behave as a column flexbox using the flex-direction:column property.

.container {
    margin: 50px auto 100px;
    position: relative;
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    justify-content: center;
    align-items: center;
    width: 100%;
    overflow-x: visible;
  }

.line {
    width: 4px;
    background-color: blue;
    height: 100%;
    position: absolute;
    left: 50%;
}
Enter fullscreen mode Exit fullscreen mode

The card class applies a simple border and sets the initial opacity to 0. We will use the transition property to fade it into full opacity and to move it towards the center smoothly.

/* opacity 0 because it should start off invisible */
.card {
    transition: 0.3s;
    border-radius: 10px;
    min-height: 100px;
    width: 250px;
    opacity: 0;
    position: relative;
    border: 5px solid blue;
    transition: all 2s ease;
}
Enter fullscreen mode Exit fullscreen mode

We will need a left and right class that controls the positioning of the card element. These classes have an initial transform of 100% and -100% since we want them to start moving in from the left or right. Using these classes as selectors, we'll display arrow heads with the ::after pseudo elements.

.right {
    padding: 30px;
    margin-left: 400px;
    transform: translateX(100%);
}

.left {
    padding: 30px;
    margin-right: 400px;
    transform: translateX(-100%);
}

/* Arrow heads */
.card.left::after {
    content: '';
    position: absolute;
    right: 0;
    top: 80%;
    width: 0;
    height: 0;
    border: 22px solid transparent;
    border-left-color: blue;
    border-right: 0;
    margin-top: -22px;
    margin-right: -22px;
}

.card.right::after {
    content: '';
    position: absolute;
    left: 0;
    top: 80%;
    width: 0;
    height: 0;
    border: 22px solid transparent;
    border-right-color: blue;
    border-radius: 10px;
    border-left: 0;
    margin-top: -22px;
    margin-left: -22px;
}
Enter fullscreen mode Exit fullscreen mode

JavaScript

The IntersectionObserver API will allow us to fire a callback whenever the cards come into the view of the screen. This callback will kick off the animation of fading the opacity back to 1 and moving the cards in from the left or right by setting the transform to 0.

function initObserver() {
    const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                entry.target.style.transform = "translateX(0)"
                entry.target.style.opacity = 1
            }
        })
    }, {
        threshold: 0.8 // Fire the callback once 80% of the card is visible
    })

    let cards = document.querySelectorAll('.card')

    cards.forEach(card => {
        observer.observe(card)
    })
}

//Attach the interesection observer after the window loads
window.onload = initObserver
Enter fullscreen mode Exit fullscreen mode

Mobile Responsiveness

We can add a couple of media queries in our CSS file for smaller screens to

  • Reduce the width of the card
  • Reduce the margins
  • Bring the initial position of the cards closer to the center
  • Reduce the font size
@media only screen and (max-width: 768px) {
    /* left to right doesn't work on some smaller screens so 
          both cards will have right -> left animation */

    .left {
      transform: translateX(20%);
    }

    .right {
      transform: translateX(20%);
    }

    .card {
      max-width: 25vw;
    }

    .left {
      margin-right: 300px;
      padding: 20px;
    }

    .right {
      margin-left: 300px;
      padding: 15px;
    }
  }

  @media only screen and (max-width: 600px) {
    .card {
      max-width: 19vw;
    }

    .card p {
      font-size: 7px;
    }

    .left {
      margin-right: 200px;
      padding: 20px;
    }

    .right {
      margin-left: 200px;
      padding: 15px;
    }
  }
Enter fullscreen mode Exit fullscreen mode

And that's it! You've got yourself a simple timeline animation that doesn't use any external dependencies. Here's a working demo:

Top comments (1)

Collapse
 
jayt profile image
Jay Tosser • Edited

That's very nice. I tried replacing transition: all 2s ease; with transition: all 2s linear; and I liked it better.