4

What i am trying to build is a vertical timeline component with some animation. The animation which am trying is that it starts from the first circle, whichever item has status true the vertical line will draw from top to bottom meanwhile whichever steps gets completed will change from round circle to completed checkmark when the line crosses it.

How can I achieve the above animation on this, I have tried so far but didn't know how to achieve the above.

What am i trying to achieve this

sandbox

Any help is appreciated.

dev
  • 814
  • 10
  • 27

3 Answers3

2

I've modified your sandbox to make it work: https://codesandbox.io/s/animable-timeline-reactjs-tiofz

  1. For animation I used following CSS:

    div {
      height: 200px;
      width: 10px;
    }
    
    .green-progress {
      background: linear-gradient(0, #00a36c, #00a36c) no-repeat, #ccc;
      background-size: 100% 0;
      animation: progressAnim 3s linear infinite forwards;
    }
    
    @keyframes progressAnim {
      0% {
        background-size: 100% 0;
      }
      100% {
        background-size: 100% 100%;
      }
    }
    <div class="green-progress"></div>

    To animate actual time line we'll remove vertical bar from first entry and there will be only checked circle. From second entry onwards we'll have a vertical bar and checked circle. To make them consistent they've been shifted upwards. To show progress, the bar will fill and then circle will be checked.
  2. Converted App to stateful component so that we can maintain animation states. In constructor, for each entry added id, startAnim, and checked state. Here, we'll set startAnim flag to start animation on corresponding TimelineConnector. and checked is used to control checkmarking the circle.

  3. In TimelineConnector set class to green-progress if this.props.startAnim is true. Also added onAnimationEnd handler as {() => this.props.onAnimDone(this.props.id)}. This tells App component that animation is done on this component with id.

  4. In TimelineDot used props.event.checked to set the checked status.

  5. In App added a lifecycle hook componentDidMount which will get called when all components gets added to actual DOM. In the hook you checkmark the first circle and start animation on first TimelineConnector.

  6. When TimelineConnector is done with the animation, it calls startNextAnim in the App. In the method you first complete the checkmark on last entry. And start next animation if the entry has status:true.


We could've added delays to each animation and ran them at once. But parent controlling each component and each component notifying when animation is done makes it more flexible to update the code. You can have different animations for each entry, based on their state.

We can use react-spring animation library but things will get complicated. CSS animation is the simplest solution.
the Hutt
  • 16,980
  • 2
  • 14
  • 44
  • 1
    Thanks for the detailed info, i will go through this and will update if in case anything is there, thanks for the help and info :) – dev Nov 29 '21 at 16:21
  • @ onkar, any help on this https://stackoverflow.com/questions/70157795/setting-the-text-width-on-dynamically – dev Nov 29 '21 at 17:38
1

You can try this

/* timeline css */
@keyframes fill-color {
  0% {
    height: 0;
  }
  100% {
    height: 100%;
  }
}

@keyframes fill-color1 {
  0% {
    height: 0;
  }
  100% {
    height: 50%;
  }
}
@keyframes scaleup {
  0% {
    transform: scale(0);
  }
  100% {
    transform: scale(1);
  }
}

@keyframes fade {
  0% {
    color: rgba(black, 0.4);
  }
  100% {
    color: rgba(black, 1);
  }
}
body {
  margin: 0;
  padding: 0;
}
.timeline {
  padding: 0;
  list-style: none;
  margin: 32px;
  overflow: hidden;
  position: relative;
}
.details {
  margin-left: 48px;
  border-bottom: 1px solid #f2f2f2;
  min-height: 85px;
  display: flex;
  justify-content: center;
  flex-direction: column;
}
.list,
.list-content {
  position: relative;
  width: 100%;
}
.list-content::before,
.list-content::after {
  content: "";
  display: block;
  position: absolute;
  left: 0;
  transition: 0.2s all linear;
  width: 0.714rem;
  height: 0.714rem;
  border-radius: 50%;
  background-color: gray;
  top: 50%;
  z-index: 3;
  margin-left: 0.35rem;
  margin-top: rem(-8px);
}

.list-content::after {
  z-index: 2;
}
.list {
  position: relative;
  width: 100%;
}

.list.active .list-content:before {
  transform: scale(0);
  width: 17px;
  height: 17px;
  border: 2px solid white;
  background-color: red;
  background-image: url("https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/White_check.svg/2048px-White_check.svg.png");
  background-position: center;
  background-repeat: no-repeat;
  background-size: 9px 7px;
  margin-left: 0;
  margin-top: -8px;
  animation: scaleup 0.4s forwards;
}
.list:before,
.list:after {
  content: "";
  display: block;
  position: absolute;
  left: 0;
  transition: 0.2s all linear;
  width: 0.214rem;
  margin-left: 0.6rem;
}
.list:before {
  background: #f2f2f2;
  height: 100%;
}
.list:after {
  background: red;
  height: 0;
  z-index: 1;
}
.list:before {
  top: -50%;
}
.list.active:after {
  top: 0;
  animation: fill-color 0.4s forwards;
}
.list:last-child:after {
  display: none;
}
.list:last-child.active:after {
  display: block;
  bottom: 50%;
  animation: fill-color1 0.4s forwards;
}
.list:last-child .details {
  border-bottom: none;
}
.list:first-child:before {
  display: none;
}
.list:first-child.active:after {
  animation: fill-color1 0.4s forwards;
  top: 50%;
}
.list:first-child.active:after {
  animation-delay: 1s;
}
.list:first-child.active .list-content:before {
  animation-delay: 0.5s;
}

.list:nth-child(2).active:after {
  animation-delay: 2s;
}
.list:nth-child(2).active .list-content:before {
  animation-delay: 2s;
}

.list:nth-child(3).active:after {
  animation-delay: 3s;
}
.list:nth-child(3).active .list-content:before {
  animation-delay: 3s;
}

.list:nth-child(4).active:after {
  animation-delay: 4s;
}
.list:nth-child(4).active .list-content:before {
  animation-delay: 4.15s;
}
<body>
    <ul class="timeline">
        <li class="list active">
            <div class="list-content">
                <div class="details">
                 <h4 class="status-title">Step 1</h4>
                </div>
            </div>
        </li>
        <li class="list active">
            <div class="list-content">
                <div class="details">
                 <h4 class="status-title">Step 2</h4>
                </div>
            </div>
        </li>
        <li class="list active">
            <div class="list-content">
                <div class="details">
                 <h4 class="status-title">Step 3</h4>
                </div>
            </div>
        </li>
        <li class="list active">
            <div class="list-content">
                <div class="details">
                 <h4 class="status-title">Step 4</h4>
                </div>
            </div>
        </li>
    </ul>
</body>

you can also check on Sandbox

here I have added active class in all li elements but if you want to two steps active so apply only first two li (i.e that class is conditional)

Janki Gandhi
  • 331
  • 1
  • 6
0

It's possible to animate a background. Please check the example below

.bg {
  min-height: 300px;
  width: 10px;
  background: linear-gradient(0, red, red) no-repeat;
  background-size: 100% 0;
  animation: gradient 15s ease infinite;
}

@keyframes gradient {
    0% {
        background-size: 100% 0;
    }
    100% {
        background-size: 100% 100%;
    }
}
<div class="bg"></div>

Your TimelineConnector could be updated with include of that CSS class.

aleks korovin
  • 724
  • 4
  • 6
  • Thanks, here i have tried making the line animated but the problem was i don't know how to animate one by one as shown in the gif, since as per the data only three are completed so how should draw the line till the completed steps that's where i am not able to move ahead – dev Nov 22 '21 at 09:23
  • Sure, you could add an additional parameter for an animation as a CSS variable defined from your React script with number of item * timeout, something like that: animation: gradient 15s ease infinite var(--timeout); – aleks korovin Nov 22 '21 at 09:25
  • To define CSS variable in a scope of element you could use such method: element.style.setProperty('--timeout', '0.5s'); – aleks korovin Nov 22 '21 at 09:28
  • sorry may be am newbie to this, if you can help can you share the codesandbox which you were able to solve as like the Gif – dev Nov 22 '21 at 09:29
  • It could be made as a loop through items and a set of different timeouts. Example: https://codepen.io/alekskorovin/pen/ExvJOrz – aleks korovin Nov 22 '21 at 09:40
  • https://codesandbox.io/s/loving-wave-1bjqv how to achieve on this ? – dev Nov 22 '21 at 09:59
  • Can you point me or if you are aware of any kind of react component library which gives of this kind of timeline with animation mentioned in the question – dev Nov 23 '21 at 03:55
  • In terms of your example these edits could help you to start - https://codesandbox.io/s/sad-phoebe-ip749 – aleks korovin Nov 23 '21 at 06:08
  • Thanks for the help, i have added the changes but still the problem i am facing is the animation is starting after few seconds and as per the GIF, first should start once it reaches the rounded changes to completed and also a grey line will be present, the animation which we add should be over through that one I was checking for this kind of component because most of the e-commerce site have it, but not getting the right component library – dev Nov 23 '21 at 11:05
  • @dev I think if you modify this, it might fit your requirements, https://codesandbox.io/embed/brave-kepler-fdbzv?file=/src/App.js:0-1097&codemirror=1 – Shujath Nov 24 '21 at 12:49
  • @Shujath i have referred this also but I don't know how to remove that mutation observer part and implement it. – dev Nov 24 '21 at 14:42