Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I curve a line in CSS "roller coaster" animation?

I'm trying to create a roller coaster style animation with CSS.

I want to know how to curve the "coaster" when it's in the loop stage.

I'm searching for an all CSS solution but if there's a need for a little bit of JavaScript I'm OK with that.

my code so far:

#container {
  width: 200px;
  height: 300px;
  margin-top: 50px;
  position: relative;
  animation: 10s infinite loop;
  animation-timing-function: linear;
}

#coaster {
  width: 100px;
  height: 10px;
  background: lightblue;
  position: absolute;
  bottom: 0;
  left: 1px;
  right: 1px;
  margin: 0 auto;
}

@keyframes loop {
  from {
    margin-left: -200px;
  }
  30% {
    margin-left: calc(50% - 75px);
    transform: rotate(0deg);
  }
  60% {
    margin-left: calc(50% - 125px);
    transform: rotate(-360deg);
  }
  to {
    transform: rotate(-360deg);
    margin-left: 100%;
  }
}
<div id="container">
  <div id="coaster"></div>
</div>
like image 278
Matan Sanbira Avatar asked Jan 28 '20 15:01

Matan Sanbira


People also ask

Can you animate borders in CSS?

CSS border animation is useful for giving a border image or container element a unique style. To make a website's user interface (UI) more attractive, use a CSS border animation design. CSS border animation is useful for giving a border image or container element a unique style.

How do you animate smoothly in CSS?

The animation-timing-function specifies the speed curve of an animation. The speed curve defines the TIME an animation uses to change from one set of CSS styles to another. The speed curve is used to make the changes smoothly.

Is CSS good for animation?

CSS animations make a website visually attractive and enhance the user experience. We hope you can get inspiration from these 30 top cool CSS animation examples to make a wonderful animation website.


1 Answers

You can consider border-radius. Here is a basic example that you can improve

.box {
  height:100px;
  margin-top:50px;
  border:10px solid transparent;
  border-bottom:10px solid red;
  width:100px;
  animation:
    loop   5s infinite alternate linear,
    radius 5s infinite alternate linear;
}
@keyframes loop{
  0% {
    transform:translateX(0);
  }
  40% {
    transform:translateX(100px) rotate(0deg);
  }
  60% {
    transform:translateX(100px) rotate(-360deg); 
  }
  100% {
    transform:translateX(200px) rotate(-360deg);
  }
}
@keyframes radius{
   0%,28% {
     border-radius:0%
   }
   35%,38% {
     border-bottom-right-radius:50%;
     border-bottom-left-radius:0%;
   }
   45%,55% {
     border-radius:50%
   }
   62%,65% {
     border-bottom-right-radius:0%;
     border-bottom-left-radius:50%;
   }
   70%,100% {
     border-radius:0%
   }
}
<div class="box"></div>

Another crazy idea where you can animate a rectangular shape behind a transparent curved path:

.box {
  height: 165px;
  width: 315px;
  position: relative;
  border-radius: 0 100px 100px 0;
  animation: hide 3s infinite linear alternate;
  overflow:hidden;
}

.box:after {
  content: "";
  display:block;
  height: 100%;
  background: white padding-box;
  border-radius: inherit;
  border: 15px solid transparent;
  border-left:0;
  box-sizing: border-box;
}

.alt {
  margin-top: -165px;
  transform: scaleX(-1);
  transform-origin: 170px 0;
  animation-direction: alternate-reverse;
}

.box:before {
  content: "";
  position: absolute;
  z-index: -1;
  width: 90px;
  height: 90px;
  background: blue;
  bottom: -40px;
  animation: loop 3s infinite linear alternate;
  transform-origin: 50% -20px;
}

.alt:before {
  animation-direction: alternate-reverse;
}

@keyframes loop {
  15% {
    transform: translateX(180px) rotate(0deg);
  }
  40% {
    transform: translateX(180px) rotate(-180deg);
  }
  50%,100% {
    transform: translateX(125px) rotate(-180deg);
  }
}

@keyframes hide {
  0%,50% {
    visibility: visible;
  }
  50.2%,100% {
    visibility: hidden;
  }
}
<div class="box"></div>

<div class="box alt"></div>

An optimized version with less of code and with transparency (will consider mask for this)

.box {
  height: 165px;
  width: 315px;
  position: relative;
  border-radius: 0 100px 100px 0;
  animation: hide 3s infinite linear alternate;
  overflow:hidden;
  -webkit-mask:
    linear-gradient(#fff,#fff) top left   /235px 15px,
    linear-gradient(#fff,#fff) bottom left/235px 15px,
    radial-gradient(farthest-side at left,transparent calc(100% - 15px),#fff 0) right/100px 100%;
   -webkit-mask-repeat:no-repeat;
   mask:
    linear-gradient(#fff,#fff) top    left/235px 15px,
    linear-gradient(#fff,#fff) bottom left/235px 15px,
    radial-gradient(farthest-side at left,transparent calc(100% - 15px),#fff 0) right/100px 100%;
   mask-repeat:no-repeat;  
}
.alt {
  margin-top: -165px;
  transform: scaleX(-1);
  transform-origin: 170px 0;
  animation-direction: alternate-reverse;
}

.box:before {
  content: "";
  position: absolute;
  width: 90px;
  height: 90px;
  background: blue;
  bottom: -40px;
  animation: loop 3s infinite linear alternate;
  transform-origin: 50% -20px;
}

.alt:before {
  animation-direction: alternate-reverse;
}

@keyframes loop {
  15% {
    transform: translateX(180px) rotate(0deg);
  }
  40% {
    transform: translateX(180px) rotate(-180deg);
  }
  50%,100% {
    transform: translateX(125px) rotate(-180deg);
  }
}

@keyframes hide {
  0%,50% {
    visibility: visible;
  }
  50.2%,100% {
    visibility: hidden;
  }
}

body {
  background:linear-gradient(to right,yellow,gray);
}
<div class="box"></div>

<div class="box alt"></div>

Another version with less pixel values and CSS variables where you can easily adjust everything.

Run the snippet on full page and have fun with all the coasters!

.box {
  --w:400px; /* Total width of the coaster */
  --h:180px; /* Height of the coaster */
  --b:90px;  /* width of the small bar */
  --t:15px;  /* height of the small bar */
  --c:blue;  /* Color of the small bar */
  
  width:var(--w);
  height:var(--h);
}

.box > div {
  height: 100%;
  position:relative;
  width: calc(50% + var(--h)/2 + var(--b)/2);
  border-radius: 0 1000px 1000px 0;
  animation: hide 3s infinite linear alternate;
  -webkit-mask:
    linear-gradient(#fff,#fff) top left   /calc(100% - var(--h)/2) var(--t),
    linear-gradient(#fff,#fff) bottom left/calc(100% - var(--h)/2) var(--t),
    radial-gradient(farthest-side at left,transparent calc(100% - var(--t)),#fff 0) right/calc(var(--h)/2) 100%;
   -webkit-mask-repeat:no-repeat; 
   mask:
    linear-gradient(#fff,#fff) top left   /calc(100% - var(--h)/2) var(--t),
    linear-gradient(#fff,#fff) bottom left/calc(100% - var(--h)/2) var(--t),
    radial-gradient(farthest-side at left,transparent calc(100% - var(--t)),#fff 0) right/calc(var(--h)/2) 100%;
   mask-repeat:no-repeat;
}
.box > div:last-child {
  margin-top:calc(-1*var(--h));
  margin-left:auto;
  transform: scaleX(-1);
  animation-direction: alternate-reverse;
}

.box > div:before {
  content: "";
  position: absolute;
  width: var(--b);
  height: 50%;
  background: var(--c);
  bottom: -25%;
  animation: loop 3s infinite linear alternate;
  transform-origin: 50% -50%;
}

.box > div:last-child:before {
  animation-direction: alternate-reverse;
}

@keyframes loop {
  15% {
    transform: translateX(calc(var(--w)/2)) rotate(0deg);
  }
  40% {
    transform: translateX(calc(var(--w)/2)) rotate(-180deg);
  }
  50%,100% {
    transform: translateX(calc(var(--w)/2 - var(--b)/2)) rotate(-180deg);
  }
}

@keyframes hide {
  50% {
    visibility: visible;
  }
  50.1%,100% {
    visibility: hidden;
  }
}

body {
  background:linear-gradient(to right,yellow,gray);
}
<div class="box">
<div></div><div></div>
</div>

<div class="box" style="--w:500px;--h:80px;--b:50px;--c:red;--t:5px">
<div></div><div></div>
</div>

<div class="box" style="--w:90vw;--h:200px;--b:100px;--c:purple;--t:20px">
<div></div><div></div>
</div>

To understand the trick, let's remove the mask and replace it with a simple gradient and remove the hide animation:

.box {
  --w:400px; /* Total width of the coaster */
  --h:180px; /* Height of the coaster */
  --b:90px;  /* width of the small bar */
  --t:15px;  /* height of the small bar */
  --c:blue;  /* Color of the small bar */
  
  width:var(--w);
  height:var(--h);
}

.box > div {
  height: 100%;
  position:relative;
  width: calc(50% + var(--h)/2 + var(--b)/2);
  border-radius: 0 1000px 1000px 0;
  /*animation: hide 3s infinite linear alternate;*/
  background:
    linear-gradient(red,red) top left   /calc(100% - var(--h)/2) var(--t),
    linear-gradient(green,green) bottom left/calc(100% - var(--h)/2) var(--t),
    radial-gradient(farthest-side at left,transparent calc(100% - var(--t)),black 0) right/calc(var(--h)/2) 100%;
  background-repeat:no-repeat; 
}
.box > div:last-child {
  margin-top:calc(-1*var(--h));
  margin-left:auto;
  transform: scaleX(-1);
  animation-direction: alternate-reverse;
}

.box > div:before {
  content: "";
  position: absolute;
  width: var(--b);
  height: 50%;
  background: var(--c);
  bottom: -25%;
  animation: loop 3s infinite linear alternate;
  transform-origin: 50% -50%;
}

.box > div:last-child:before {
  animation-direction: alternate-reverse;
  background:purple;
}

@keyframes loop {
  15% {
    transform: translateX(calc(var(--w)/2)) rotate(0deg);
  }
  40% {
    transform: translateX(calc(var(--w)/2)) rotate(-180deg);
  }
  50%,100% {
    transform: translateX(calc(var(--w)/2 - var(--b)/2)) rotate(-180deg);
  }
}

@keyframes hide {
  0%,50% {
    visibility: visible;
  }
  50.2%,100% {
    visibility: hidden;
  }
}

body {
  background:linear-gradient(to right,yellow,gray);
}
<div class="box">
<div></div><div></div>
</div>

The path created by the gradient is our mask and we will only see that part. Then we make our rectangle to follow the path and the trick is to have two symmetrical element to create the loop effect. The hide animation will allow us to see only one of them and the perfect overlap will create a continuous animation.


Here is a version in case you want a circle for the coaster

.box {
  --w:400px; /* Total width of the coaster */
  --h:180px; /* Height of the coaster */
  --b:90px;  /* width of the small bar */
  --t:15px;  /* height of the small bar */
  --c:blue;  /* Color of the small bar */
  
  width:var(--w);
  height:var(--h);
}

.box > div {
  height: 100%;
  position:relative;
  width: calc(50% + var(--h)/2);
  border-radius: 0 1000px 1000px 0;
  animation: hide 3s infinite linear alternate;
  -webkit-mask:
    radial-gradient(farthest-side at bottom right,transparent calc(100% - var(--t)),#fff 0 100%,transparent 100%) top 0 right calc(var(--h)/2)/calc(var(--h)/2) 50%,
    linear-gradient(#fff,#fff) bottom left/calc(100% - var(--h)/2) var(--t),
    radial-gradient(farthest-side at left,transparent calc(100% - var(--t)),#fff 0) right/calc(var(--h)/2) 100%;
   -webkit-mask-repeat:no-repeat; 
   mask:
    radial-gradient(farthest-side at bottom right,transparent calc(100% - var(--t)),#fff 0 100%,transparent 100%) top 0 right calc(var(--h)/2)/calc(var(--h)/2) 50%,
    linear-gradient(#fff,#fff) bottom left/calc(100% - var(--h)/2) var(--t),
    radial-gradient(farthest-side at left,transparent calc(100% - var(--t)),#fff 0) right/calc(var(--h)/2) 100%;
   mask-repeat:no-repeat;
}
.box > div:last-child {
  margin-top:calc(-1*var(--h));
  margin-left:auto;
  transform: scaleX(-1);
  animation-direction: alternate-reverse;
}

.box > div:before {
  content: "";
  position: absolute;
  width: var(--b);
  height: 50%;
  background: var(--c);
  bottom: -25%;
  animation: loop 3s infinite linear alternate;
  transform-origin: 50% -50%;
}

.box > div:last-child:before {
  animation-direction: alternate-reverse;
}

@keyframes loop {
  15% {
    transform: translateX(calc(var(--w)/2 - var(--b)/2)) rotate(0deg);
  }
  50%,100% {
    transform: translateX(calc(var(--w)/2 - var(--b)/2)) rotate(-180deg);
  }
}

@keyframes hide {
  50% {
    visibility: visible;
  }
  50.1%,100% {
    visibility: hidden;
  }
}

body {
  background:linear-gradient(to right,yellow,gray);
}
<div class="box">
<div></div><div></div>
</div>

<div class="box" style="--w:500px;--h:80px;--b:50px;--c:red;--t:5px">
<div></div><div></div>
</div>

<div class="box" style="--w:90vw;--h:200px;--b:100px;--c:purple;--t:20px">
<div></div><div></div>
</div>
like image 78
Temani Afif Avatar answered Oct 26 '22 00:10

Temani Afif