Javascript添加/删除动画类,仅动画一次

时间:2019-09-30 15:22:12

标签: javascript css animation

试图构建一个时间选择器,该时间选择器应该可以动画化输入的变化。我以为可以通过在CSS中添加和删除动画类来做到这一点。问题是,它只能在第一次使用。

我尝试设置一个开始的时间间隔,该时间间隔在一定时间后删除了该类,略高于动画持续时间,但是每次单击以及第三次或第四次单击都减少了动画的动画时间,它不再具有动画效果。

然后我只是选择检查该类是否存在以开始,将其删除以开始,然后添加它。确保每次都从头开始。但是现在它只是第一次动画。

Codepen

HTML:


    <div id="wrapper">
      <div id="uphour"></div>
      <div id="upminute"></div>
      <div id="upampm"></div>
      <div class="animated">
        <div id="hour" data-hour="1">2</div>
      </div>
      <div class="animated">
        <div id="minute" data-minute="1">50</div>
      </div>
      <div class="animated">
        <div id="ampm" data-minute="AM">AM</div>
      </div>
      <div id="downhour"></div>
      <div id="downminute"></div>
      <div id="downampm"></div>
      </div>

(此处的数据属性当前未使用)

SCSS:


    $finger: 2cm;
    $smallfinger: .3cm;

    #wrapper {
      display: grid;
      grid-template-columns: $finger $finger $finger;
      grid-template-rows: $finger $finger $finger;
      grid-column-gap: $smallfinger;
      grid-row-gap: $smallfinger;
      position:absolute;
      width: 100vw;
      background: #FF00FF;
      height: calc( (#{$finger} * 3) + (#{$smallfinger} * 4) );
      box-sizing: border-box;
      bottom:$finger;
      left:0;
      padding:0;
      margin:0;
      align-content: center;
      justify-content: center;
      }

    #uphour,
    #upminute,
    #upampm,
    #downhour,
    #downminute,
    #downampm {
      display: flex;
      background: rgba(255,0,0,.4);
      width: $finger;
      height: $finger;
      margin-left: 1vw;
      margin-right: 1vw;
      border: 1px solid black;
    }

    .animated {
      display: flex;
      width: $finger;
      height: $finger;
      margin-left: 1vw;
      margin-right: 1vw;
      border: 1px solid black;
      overflow:hidden;
    }


    #minute, #hour, #ampm {
      display: flex;
      /*background: rgba(255,0,0,.4); for testing of animation only*/
      width: $finger;
      height: $finger;
      border: 0;
      position: relative;
      align-items: center;
      justify-content: center;
      font-size: .8cm;
    }

    .animateStart {
      -webkit-animation-name: downandout; /* Safari 4.0 - 8.0 */
      animation-name: downandout;
      -webkit-animation-duration: 250ms; /* Safari 4.0 - 8.0 */
      animation-duration: 250ms;
      /*-webkit-animation-iteration-count: infinite;
      animation-iteration-count: infinite;*/
      transition-timing-function: cubic-bezier(1,0,0,1);
    }

    /* Safari 4.0 - 8.0 */
    @-webkit-keyframes downandout {
      0%   {top: 0; left: 0;}
      10%  {top: 100%; left:0;}
      50%  {top: 100%; left: 100%;}
      90% {top: -100%; left:100%;}
    }

    /* Standard syntax */
    @keyframes downandout {
      0%   {top: 0; left: 0;}
      40%  {top: 100%; left:0;}
      42%  {top: 100%; left: calc(#{$finger} * 2);}
      48% {top: -110%; left: calc(#{$finger} * 2);}
      50% {top: -110%; left:0;}
      100% {top: 0; left:0;}
    }

Javascript:


    var uphourEl = document.getElementById("uphour");
    var downhourEl = document.getElementById("downhour");
    var upminuteEl = document.getElementById("upminute");
    var downminuteEl = document.getElementById("downminute");
    var upampmEl = document.getElementById("upampm");
    var downampmEl = document.getElementById("downampm");

    uphourEl.addEventListener("click", increaseHour);
    downhourEl.addEventListener("click", decreaseHour);
    upminuteEl.addEventListener("click", increaseMinute);
    downminuteEl.addEventListener("click", decreaseMinute);
    upampmEl.addEventListener("click", swapAMPM);
    downampmEl.addEventListener("click", swapAMPM);

    var hourEl = document.getElementById("hour"); 
    var minuteEl = document.getElementById("minute"); 
    var ampmEl = document.getElementById("ampm"); 

    function increaseHour() {
      let value = hourEl.innerHTML;
      if (value > 11) {
          value = 1
      } else  {
        value++;
      }
      hourEl.innerHTML = value;
      console.log(value);
    }

    function decreaseHour() {
      let value = hourEl.innerHTML;
      if (value < 2) {
          value = 12
      } else  {
        value--;
      }
      hourEl.innerHTML = value;
      console.log(value);
    }

    function increaseMinute() {
      let value = minuteEl.innerHTML;
      if (value > 58) {
          value = 0
      } else  {
        value++;
      }
      minuteEl.innerHTML = value;
      console.log(value);
    }

    function decreaseMinute() {
      let value = minuteEl.innerHTML;
      if (value < 1) {
          value = 59
      } else  {
        value--;
      }
      minuteEl.innerHTML = value;
      console.log(value);
    }

    function swapAMPM() {
      let value = ampmEl.innerHTML;
      if (ampmEl.hasAttribute("class")) {
        ampmEl.removeAttribute("class");
      }
      ampmEl.setAttribute("class", "animateStart");
      if (value === "AM") {
          value = "PM";
          ampmEl.innerHTML = value;
          console.log("Changed from AM");
      } else  {
        value = "AM";
        ampmEl.innerHTML = value;
        console.log("Changed from PM");
      }
    }

..这是swapAMPM函数(在Javascript末尾),我目前无法正常运行。

关于我为什么会遇到这种行为的任何建议?我缺少一些最佳实践吗?

4 个答案:

答案 0 :(得分:2)

我想这就是在香草中做的方式

gugateider是对的,您需要一个setTimout,有AnimationEvent API,但尚未完全支持。

下面是一种简化的方法:

不需要事件列表器或收集ID

已更新:

  • 包含了一个防跳动功能,可以停止多次单击同一按钮
  • 调整了超时(添加了超时,以便更改值的范围以给出数字实际上正在旋转的错觉,AnimationEvent无法检测到动画是否在中间。)< / li>
<div class="wrapper">

  <div id="hour" class="unit">
    <div class="plus button" onClick="pressedButton(event);">+</div>
    <div class="value"><div>10</div></div>
    <div class="minus button" onClick="pressedButton(event);">-</div>
  </div>

    <div id="minute" class="unit">
    <div class="plus button"  onClick="pressedButton(event);">+</div>
      <div class="value"><div>36</div></div>
    <div class="minus button"  onClick="pressedButton(event);">-</div>
  </div>

    <div id="meridiem" class="unit">
    <div class="plus button"  onClick="pressedButton(event);">+</div>
      <div class="value"><div>AM</div></div>
    <div class="minus button"  onClick="pressedButton(event);">-</div>
  </div>



</div>

.wrapper {
  display: flex;
  flex-flow: row no-wrap;
  justify-content: space-between;
  align-items: center;
  width: 200px;
  height: 200px;
  margin: 100px auto;
  background: red;
  padding: 20px;
}

.unit {
  display: flex;
  flex-flow: column;
  justify-content: space-between;
  align-items: space-between;
  height: 100%;
  position: relative;
}

.button {
  border: 2px solid black;
  height: 50px;
  width: 50px;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  background: grey;

  &:hover {
    opacity: 0.8;
  }

}


.value {
  border: 2px solid black;
  height: 50px;
  width: 50px;
  font-family: sans-serif;
  font-size: 20px;
  display: flex;
  justify-content: center;
  align-items: center;
  background: lightgrey;
    overflow: hidden;
}

.animate {
  top: 0;
  position: relative;
  overflow: hidden;
  animation-name: downandout;
  animation-duration: 1s;
  animation-iteration-count: 1;
  transition-timing-function: ease-in-out;

  &--reverse {
    animation-direction: reverse;
  }
}




 @keyframes downandout {
   0%  {top: 0}
   50% {top: 50px}
   51% {top: -50px}
   100%  {top: 0}
}
debounce = function(func, wait, immediate) {
  var timeout;
  return function() {
    var context = this,
      args = arguments;
    var later = function() {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };
    var callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func.apply(context, args);
  };
}

pressedButton = function($event) {
  $event.preventDefault();

  // debouncedFn to stop multiple clicks to the same button
  var debouncedFn = debounce(function() {
    // All the taxing stuff you do
    const target = $event.target;
    const elm = target.parentNode.children[1];

    let direction;

    // clone the element to restart the animation
    const newone = elm.cloneNode(true);
    // add the first animate
    newone.children[0].classList.add("animate");

    // What button was pressed
    if (target.classList.contains("minus")) {
      direction = "down";
    } else {
      direction = "up";
    }

    // direction of animation
    if (direction === "down") {
      newone.children[0].classList.add("animate--reverse");
    } else {
      newone.children[0].classList.remove("animate--reverse");
    }

    // add the new element to the DOM
    elm.parentNode.replaceChild(newone, elm);

    // change value after half of the animation has completed
    setTimeout(function() {
      switch (target.parentNode.id) {
        case "hour":
        case "minute":
          if (direction === "down") {
            newone.children[0].innerText--;
          } else {
            newone.children[0].innerText++;
          }
          break;

        case "meridiem":
          if (newone.children[0].innerText === "PM") {
            newone.children[0].innerText = "AM";
          } else {
            newone.children[0].innerText = "PM";
          }
      }
    }, 100);
  }, 250);

  // Call my function
  debouncedFn();

};

https://codepen.io/eddy14u/pen/VwZJmdW

答案 1 :(得分:1)

我知道setTimeout并不是在其中使用的最佳选择,但是由于您已经完成了很多工作,因此可以尝试使用以下代码替换swapAMPM函数:

function swapAMPM() {
 let value = ampmEl.innerHTML;
 if (ampmEl.hasAttribute("class")) {
     ampmEl.removeAttribute("class");
 }
 setTimeout( () => { 
     ampmEl.setAttribute("class", "animateStart");
     if (value === "AM") {
      value = "PM";
      ampmEl.innerHTML = value;
      console.log("Changed from AM");
     } else  {
        value = "AM";
        ampmEl.innerHTML = value;
        console.log("Changed from PM");
    } 
 }, 150);
}

答案 2 :(得分:1)

您可以使用动画迭代事件侦听器来检测动画何时结束,并正确删除类。为此,动画迭代必须设置为无限。在更改中,我还利用了元素的classList属性来从元素中添加/删除类。

注意:没有至少1次迭代,动画迭代事件将永远不会触发,因此代码将无法正常工作!

编辑:浏览器对classList的支持才刚刚起步,因此,如果您需要支持较旧的浏览器,则可以改用其他解决方案,或者添加classList polyfill < / p>

首先,我们需要一个函数来检测浏览器支持哪个动画迭代事件:

function whichAnimationEvent(){
  var el = document.createElement("fakeelement");

  var animations = {
    "animation"      : "animationiteration",
    "OAnimation"     : "oAnimationIteration",
    "MozAnimation"   : "animationiteration",
    "WebkitAnimation": "webkitAnimationIteration"
  };

  for (let t in animations){
    if (el.style[t] !== undefined){
      return animations[t];
    }
  }
}

下一步,我们需要为该事件添加一个事件侦听器,并且在迭代触发时,我们从元素的classList中删除该类,如下所示:

ampmEl.addEventListener(whichAnimationEvent(),function(){
  console.log('ampmEl event listener fired')
  ampmEl.classList.remove('animateStart');
});

接下来,我们将swapAMPM函数更改为使用元素add的{​​{1}}方法在执行交换之前添加类,以便对其进行动画处理。 / strong>

classList

最后,我们需要更新css以使其具有function swapAMPM() { let value = ampmEl.innerHTML; ampmEl.classList.add('animateStart'); if (value === "AM") { value = "PM"; ampmEl.innerHTML = value; console.log("Changed from AM"); } else { value = "AM"; ampmEl.innerHTML = value; console.log("Changed from PM"); } } 动画迭代,以便触发事件。

infinite

完整的示例

codepen

答案 3 :(得分:0)

swapAMPM中的函数if statement中,您尝试删除一个没有太大意义的属性,因为您立即将其重新设置。

if (ampmEl.hasAttribute("class")) {
  ampmEl.removeAttribute("class");
}
ampmEl.setAttribute("class", "animateStart")

一种快速解决方法是设置超时,以便在动画制作完成后立即删除该类:

function swapAMPM() {
  let value = ampmEl.innerHTML;
  ampmEl.classList.add("animateStart");
  setTimeout(() => { 
    ampmEl.classList.remove("animateStart") 
  }, 260)
  if (value === "AM") {
      value = "PM";
      ampmEl.innerHTML = value;
      console.log("Changed from AM");
  } else  {
    value = "AM";
    ampmEl.innerHTML = value;
    console.log("Changed from PM");
  }
}