开发者

Is there a way for animation-timing-function to apply to the entire animation instead of each keyframe?

开发者 https://www.devze.com 2023-02-01 10:42 出处:网络
I\'m a bit new to animation, so forgive me if I\'m missing a huge concept here. I need to animate an arrow that is pointing to a point on a curve, let\'s just say it\'开发者_如何学运维s a cubic curve

I'm a bit new to animation, so forgive me if I'm missing a huge concept here. I need to animate an arrow that is pointing to a point on a curve, let's just say it'开发者_如何学运维s a cubic curve for the sake of this post. The arrow moves along the curve's line, always pointing a few pixels below it.

So what I did, is I setup keyframes along the curve's line using CSS3:

 @-webkit-keyframes ftch {
     0% {
         opacity: 0;
         left: -10px;
         bottom: 12px;
     }
     
    25% {
        opacity: 0.25;
        left: 56.5px;
        bottom: -7px;
     }
     
     50% {
        opacity: 0.5;         
        left: 143px;
        bottom: -20px;
     }
     
     75% {
        opacity: 0.75;
        left: 209.5px;
        bottom: -24.5px;
     }
     
     100% {
         opacity: 1;
         left: 266px;
         bottom: -26px;
     }
}

However, when I run this animation using -webkit-animation-timing-function: ease-in, it applies that easing to each individual keyframe, which is definitely not what I want. I want the easing to apply to the entire animation.

Is there a different way that I should be doing this? Is there some property to apply the easing to the entire sequence rather than each keyframe?


You can't apply an easing function over a series of keyframes because you're specifically telling the object to be at a specific point at a specific time. If you applied, say, an ease-in over a series of keyframes, then at 25% the object would behind it's required "checkpoint", eventually accelerating until catching up at 100%.

If your points are more or less equidistant, you can apply:

.animatedobject {
  -webkit-animation-timing-function: linear;
}

and your animation will look more more less good, if a little robotic.

A more natural approach would be to accelerate, maintain speed, and then "brake":

 @-webkit-keyframes ftch {
 0% {
     opacity: 0;
     left: -10px;
     bottom: 12px;
    -webkit-animation-timing-function: ease-in;
 }

25% {
    opacity: 0.25;
    left: 56.5px;
    bottom: -7px;
    -webkit-animation-timing-function: linear;
 }

 50% {
    opacity: 0.5;         
    left: 143px;
    bottom: -20px;
    -webkit-animation-timing-function: linear;
 }

 75% {
    opacity: 0.75;
    left: 209.5px;
    bottom: -24.5px;
    -webkit-animation-timing-function: linear;
 }

 100% {
     opacity: 1;
     left: 266px;
     bottom: -26px;
    -webkit-animation-timing-function: ease-out;
 }
}

If webkit supported animations along a path you wouldn't need these keyframes and you would have no trouble applying the easing to only two keyframes.


When you want different easing functions to apply to different aspects of an animation you should separate out your animations by nesting your content in two divs.

In this case, you should create a parent div to apply a movement animation to, and a child div to apply the opacity animation to. The opacity animation should have an easing curve: linear, and the movement animation should have whatever easing function looks best to you. However, I would repeat what Duopixel says about mixing easing curves and fixed checkpoints - in this case you shouldn't actually need animations, just two 0%:100% animations - one for the parent and one for the child div.

Having done a lot of CSS3 animation, I wrote this guide for our Sencha Animator product - it has some helpful general information on how to get complex animations working with CSS3 - even if you don't want to use the tool.


You can't do it with CSS Animations but if you don't mind converting it to JavaScript then you can use Web Animations API instead.

The equivalent of such CSS Animation:

.css-animation-ease {
  animation-name: move-animation;
  animation-duration: 3s;
}

@keyframes move-animation {
  25% {
    transform: translate(100px, 0);
  }
  50% {
    transform: translate(100px, 100px);
    background-color: green;
  }
  75% {
    transform: translate(0, 100px);
  }
  100% {
    transform: translate(0, 200px);
    background-color: blue;
  }
}

would be this JS code:

box.animate(
  [
    { easing: "ease" },
    { transform: "translate(100px, 0)", offset: 0.25, easing: "ease" },
    { transform: "translate(100px, 100px)", backgroundColor: "green", 
      offset: 0.5, easing: "ease" },
    { transform: "translate(0, 100px)", offset: 0.75, easing: "ease" },
    { transform: "translate(0, 200px)", backgroundColor: "blue", easing: "ease" },
  ],
  { duration: 3000 }
);

However, you don't need to pass offset and easing parameters. If you omit them then the keyframes would be evenly spaced and you can define timing function with the easing option which will be applied to the whole animation.

    box.animate(
        [
            {},
            { transform: "translate(100px, 0)" },
            { transform: "translate(100px, 100px)", backgroundColor: "green" },
            { transform: "translate(0, 100px)" },
            { transform: "translate(0, 200px)", backgroundColor: "blue" },
        ],
        { duration: 3000, easing: "ease" }
    );

Here is example that compares this two approaches (CSS Animation vs Web Animations API)

document.getElementById("animateAll").addEventListener("click", () => {
  for (const btn of document.querySelectorAll(".examples button")) {
    btn.click();
  }
});

document
  .getElementById("cssAnimationLinearBtn")
  .addEventListener("click", () => {
    const box = document.getElementById("cssAnimationLinear");
    box.classList.add("css-animation-linear");
    box.addEventListener("animationend", () => {
      box.classList.remove("css-animation-linear");
    });
  });

document.getElementById("cssAnimationEaseBtn").addEventListener("click", () => {
  const box = document.getElementById("cssAnimationEase");
  box.classList.add("css-animation-ease");
  box.addEventListener("animationend", () => {
    box.classList.remove("css-animation-ease");
  });
});

document
  .getElementById("webAnimationLinearBtn")
  .addEventListener("click", () => {
    const box = document.getElementById("webAnimationLinear");
    box.animate(
      [
        {},
        { transform: "translate(100px, 0)" },
        { transform: "translate(100px, 100px)", backgroundColor: "green" },
        { transform: "translate(0, 100px)" },
        { transform: "translate(0, 200px)", backgroundColor: "blue" },
      ],
      { duration: 3000 }
    );
  });

document
  .getElementById("webAnimationEaseOffsetBtn")
  .addEventListener("click", () => {
    const box = document.getElementById("webAnimationEaseOffset");
    box.animate(
      [
        { easing: "ease" },
        { transform: "translate(100px, 0)", offset: 0.25, easing: "ease" },
        {
          transform: "translate(100px, 100px)",
          backgroundColor: "green",
          offset: 0.5,
          easing: "ease",
        },
        { transform: "translate(0, 100px)", offset: 0.75, easing: "ease" },
        {
          transform: "translate(0, 200px)",
          backgroundColor: "blue",
          easing: "ease",
        },
      ],
      { duration: 3000 }
    );
  });

document.getElementById("webAnimationEaseBtn").addEventListener("click", () => {
  const box = document.getElementById("webAnimationEase");
  box.animate(
    [
      {},
      { transform: "translate(100px, 0)" },
      { transform: "translate(100px, 100px)", backgroundColor: "green" },
      { transform: "translate(0, 100px)" },
      { transform: "translate(0, 200px)", backgroundColor: "blue" },
    ],
    { duration: 3000, easing: "ease" }
  );
});
.examples {
  display: grid;
  grid-template-columns: repeat(5, 130px);
}

.box {
  width: 30px;
  height: 30px;
  background-color: red;
}

.box.css-animation-linear {
  animation: move-animation 3s linear 1;
}

.box.css-animation-ease {
  animation-name: move-animation;
  animation-duration: 3s;
}

@keyframes move-animation {
  25% {
    transform: translate(100px, 0);
  }
  50% {
    transform: translate(100px, 100px);
    background-color: green;
  }
  75% {
    transform: translate(0, 100px);
  }
  100% {
    transform: translate(0, 200px);
    background-color: blue;
  }
}
<button id="animateAll">Animate all</button>
<div class="examples">
    <div>
        <span style="color:brown">CSS Animation API (linear)</span>
        <button id="cssAnimationLinearBtn">Animate</button>
        <div id="cssAnimationLinear" class="box"></div>
    </div>
    <div>
        <span style="color:green">CSS Animation API (ease)</span>
        
        <button id="cssAnimationEaseBtn">Animate</button>
        <div id="cssAnimationEase" class="box"></div>
    </div>
    <div>
        <span style="color:brown">Web Animation API (linear)</span>
        <button id="webAnimationLinearBtn">Animate</button>
        <div id="webAnimationLinear" class="box"></div>
    </div>
    <div>
        <span style="color:green">Web Animation API (ease, offset)</span>
        <button id="webAnimationEaseOffsetBtn">Animate</button>
        <div id="webAnimationEaseOffset" class="box"></div>
    </div>
    <div>
        <strong>Web Animation API (ease)</strong>
        <button id="webAnimationEaseBtn">Animate</button>
        <div id="webAnimationEase" class="box"></div>
    </div>
</div>


Some reference love for you: http://www.w3.org/TR/css3-animations/

More specifically: http://www.w3.org/TR/css3-animations/#timing-functions-for-keyframes-

You may want to do animation stacking, such as move to one location, then another, then another in multiple keyframe animations rather than just one.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号