【问题标题】:How can I curve a line in CSS "roller coaster" animation?如何在 CSS“过山车”动画中弯曲一条线?
【发布时间】:2020-01-29 17:02:07
【问题描述】:

我正在尝试使用 CSS 创建一个过山车风格的动画。

我想知道当“过山车”处于循环阶段时如何弯曲它。

我正在寻找一个全 CSS 解决方案,但如果需要一点 JavaScript,我可以接受。

到目前为止我的代码:

#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>

【问题讨论】:

    标签: html css css-animations css-transforms


    【解决方案1】:

    你可以考虑border-radius。这是一个您可以改进的基本示例

    .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%
       }
    }
    &lt;div class="box"&gt;&lt;/div&gt;

    另一个疯狂的想法,您可以在透明弯曲路径后面为矩形设置动画:

    .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>

    代码更少且具有透明度的优化版本(为此考虑mask

    .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>

    另一个具有更少像素值和 CSS 变量的版本,您可以轻松调整所有内容。

    在整页上运行 sn-p,享受所有杯垫的乐趣!

    .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>

    为了理解这个技巧,让我们移除遮罩并用一个简单的渐变替换它并移除隐藏动画:

    .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>

    渐变创建的路径是我们的蒙版,我们只会看到那部分。然后我们让我们的矩形跟随路径,诀窍是有两个对称的元素来创建循环效果。 hide 动画将允许我们只看到其中一个,并且完美的重叠将创建一个连续的动画。


    如果你想要一个圆形的过山车,这是一个版本

    .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>

    【讨论】:

      【解决方案2】:

      谢谢大家,特别是 Temani Afif 的灵感:]

      我最终使用border-radius、隐藏元素和更多 HTML 结合了你的很多答案,我认为我为动画创建了一个很好的解决方案。

      * {
        box-sizing: border-box;
      }
      
      #container {
        width: 100px;
        height: 100px;
        margin-top: 50px;
        position: relative;
        animation: 5s infinite loop linear;
      }
      
      #coasterLine {
        height: 10px;
        background: lightblue;
        position: absolute;
        z-index: 20;
        bottom: 0;
        animation: 5s infinite c-line linear;
      }
      
      #coasterRound {
        width: 100%;
        height: 100%;
        border-radius: 50%;
        border: solid transparent 10px;
        border-bottom: solid lightblue 10px;
        position: relative;
        animation: 5s infinite c-round linear;
      }
      
      #whiteScreen {
        width: 100%;
        background: white;
        position: absolute;
        z-index: 10;
        top: 0;
        animation: 5s infinite white-screen linear;
      }
      
      @keyframes loop {
        0% {
          margin-left: -200px;
        }
        38%,
        43% {
          margin-left: calc(50% - 50px);
        }
        58%,
        63% {
          margin-left: calc(50% - 50px);
        }
        100% {
          margin-left: 100%;
        }
      }
      
      @keyframes c-round {
        0%,
        43% {
          transform: rotate(-45deg);
        }
        58%,
        100% {
          transform: rotate(-315deg);
        }
      }
      
      @keyframes c-line {
        0%,
        38% {
          left: 0;
          width: 60px;
        }
        43% {
          left: 50px;
          width: 0;
        }
        58% {
          left: 40px;
          width: 0;
        }
        63%,
        100% {
          left: 40px;
          width: 60px;
        }
      }
      
      @keyframes white-screen {
        0%,
        38% {
          height: 100%;
          transform: rotate(0deg);
        }
        43% {
          height: 50%;
          transform: rotate(0deg);
        }
        44%,
        57% {
          height: 0;
          transform: rotate(0deg);
        }
        58% {
          height: 50%;
          transform: rotate(180deg);
        }
        63%,
        100% {
          height: 100%;
          transform: rotate(180deg);
        }
      }
      <div id="container">
        <div id="coasterLine"></div>
        <div id="coasterRound"></div>
        <div id="whiteScreen"></div>
      </div>

      太棒了!

      【讨论】:

        【解决方案3】:

        看起来不太自然,但border-radius 似乎是一个不错的开始方式:

        #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;
          animation: 10s infinite coasterAnimation;
        }
        
        @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%;
          }
        }
        
        @keyframes coasterAnimation {
        
          29% {
            border-radius: 0;
          }
        
          30% {
            border-radius: 0 0 50% 50%;
          }
        
          59% {
            border-radius: 0 0 50% 50%;
          }
        
          60% {
            border-radius: 0;
          }
        
          70% {
            border-radius: 0;
          }
        }
        <div id="container">
          <div id="coaster"></div>
        </div>

        【讨论】:

          【解决方案4】:

          我认为下面的方法或多或少是合理的(尽管我是第一个同意这种匆忙实施并不完美的人。

          过山车不是由&lt;div&gt; 表示,而是由border-bottom&lt;div&gt; 表示。

          在该边框自己的同步动画中,border-bottom-left-radiusborder-bottom-left-radius 会随着时间的推移弯曲到 50%,然后再弯曲回 0

          工作示例:

          #container {
            width: 180px;
            height: 180px;
            position: relative;
            animation: 10s loop-container linear infinite;
          }
          
          #coaster {
            width: 180px;
            height: 180px;
            border-bottom: 10px solid lightblue;
            position: absolute;
            bottom: 0;
            left: 1px;
            right: 1px;
            margin: 0 auto;
            animation: 10s loop-coaster linear infinite;
          }
          
          @keyframes loop-container {
            0% {
              margin-left: -200px;
            }
            30% {
              margin-left: calc(50% - 75px);
              transform: rotate(0deg);
            }
            60% {
              margin-left: calc(50% - 125px);
              transform: rotate(-360deg);
            }
            100% {
              transform: rotate(-360deg);
              margin-left: 100%;
            }
          }
          
          @keyframes loop-coaster {
            30% {
              border-bottom-left-radius: 0;
              border-bottom-right-radius: 0;
            }
            31% {
              border-bottom-left-radius: 0;
              border-bottom-right-radius: 25%;
            }
            35%, 55% {
              border-bottom-left-radius: 50%;
              border-bottom-right-radius: 50%;
            }
            59% {
              border-bottom-left-radius: 25%;
              border-bottom-right-radius: 0;
            }
            60% {
              border-bottom-left-radius: 0;
              border-bottom-right-radius: 0;
            }
          }
          <div id="container">
            <div id="coaster"></div>
          </div>

          【讨论】:

            猜你喜欢
            • 2017-02-23
            • 1970-01-01
            • 1970-01-01
            • 2014-01-15
            • 1970-01-01
            • 1970-01-01
            • 2011-07-17
            • 1970-01-01
            • 2011-12-20
            相关资源
            最近更新 更多