如何使用 Promise 链接 CSS 动画?

How to chain CSS animations using Promise?

我正在尝试弄清楚 Promise 是如何工作的,并制作一个 CSS 动画到 运行 接一个。但是他们 运行 同时...

JS fiddle

let move_boxes = () => {
    return new Promise( (resolve, reject) => {
        resolve ('boxes moved!')
    })
};
let move_box_one = () => {
    return new Promise( (resolve, reject) => {
        resolve (document.getElementById('div_two').style.animation = 'move 3s forwards')
        console.log('box one moved!')   
    })

}
let move_box_two = () => {
    return new Promise( (resolve, reject) => {
        resolve (document.getElementById('div_one').style.animation = 'move 3s forwards')
        console.log('box two moved!')       
    })
}

move_boxes().then(() => {
    return move_box_one();
}).then(() => {
    return move_box_two();
});

您需要等待动画完成,这是一个工作代码,

    let move_boxes = () => {
    return new Promise( (resolve, reject) => {
        resolve ('boxes moved!')
    })
};
let move_box_one = () => {
    return new Promise( (resolve, reject) => {
        document.getElementById('div_two').style.animation = 'move 3s forwards';
        setTimeout(()=> resolve(), 3000);   
    })

}
let move_box_two = () => {
    return new Promise( (resolve, reject) => {
        document.getElementById('div_one').style.animation = 'move 3s forwards';
        setTimeout(()=> resolve(), 3000);
    })
}

move_boxes().then(() => {
    return move_box_one();
}).then(() => {
    return move_box_two();
});

工作fiddle:https://jsfiddle.net/bu96cxak/

与发布的答案相反,我不鼓励使用 window.setTimeout,因为它不能保证计时器与动画结束事件同步,有时这些事件会卸载到 GPU。如果您想更加确定,您应该收听 animationend event,并在回调本身中解决它,即:

let move_box_one = () => {
  return new Promise((resolve, reject) => {
    const el = document.getElementById('div_one');
    const onAnimationEndCb = () => {
      el.removeEventListener('animationend', onAnimationEndCb);
      resolve();
    }

    el.addEventListener('animationend', onAnimationEndCb)
    el.style.animation = 'move 3s forwards';
  });
}

更好的是,由于您为两个框编写了一些重复的逻辑,您可以将所有这些抽象为一个通用函数,returns 一个承诺:

// We can declare a generic helper method for one-time animationend listening
let onceAnimationEnd = (el, animation) => {
  return new Promise(resolve => {
    const onAnimationEndCb = () => {
      el.removeEventListener('animationend', onAnimationEndCb);
      resolve();
    }
    el.addEventListener('animationend', onAnimationEndCb)
    el.style.animation = animation;
  });
}

let move_box_one = async () => {
  const el = document.getElementById('div_one');
  await onceAnimationEnd(el, 'move 3s forwards');
}
let move_box_two = async () => {
  const el = document.getElementById('div_two');
  await onceAnimationEnd(el, 'move 3s forwards');
}

此外,您的 move_boxes 函数有点复杂。如果你想 运行 异步移动单个框的动画,将其声明为异步方法并简单地等待单个框移动函数调用,即:

let move_boxes = async () => {
  await move_box_one();
  await move_box_two();
}
move_boxes().then(() => console.log('boxes moved'));

查看概念验证示例(或者您可以从原始示例的 this JSFiddle that I've forked 中查看):

// We can declare a generic helper method for one-time animationend listening
let onceAnimationEnd = (el, animation) => {
  return new Promise(resolve => {
    const onAnimationEndCb = () => {
      el.removeEventListener('animationend', onAnimationEndCb);
      resolve();
    }
    el.addEventListener('animationend', onAnimationEndCb)
    el.style.animation = animation;
  });
}

let move_box_one = async () => {
  const el = document.getElementById('div_one');
  await onceAnimationEnd(el, 'move 3s forwards');
}
let move_box_two = async () => {
  const el = document.getElementById('div_two');
  await onceAnimationEnd(el, 'move 3s forwards');
}

let move_boxes = async () => {
  await move_box_one();
  await move_box_two();
}
move_boxes().then(() => console.log('boxes moved'));
#div_one {
  width: 50px;
  height: 50px;
  border: 10px solid red;
}

#div_two {
  width: 50px;
  height: 50px;
  border: 10px solid blue;
}

@keyframes move {
  100% {
    transform: translateX(300px);
  }
}

@keyframes down {
  100% {
    transform: translateY(300px);
  }
}
<div id="div_one"></div>
<div id="div_two"></div>

当您知道相应的动画事件已完成时,您将只想解决承诺。

您可以为此使用 animationend 事件,这比使用具有预期持续时间的 setTimeout 更合适。

创建一个用于为其创建承诺的实用函数,然后将其重新用于任何元素和任何动画描述也将很有用:

const promiseAnimation = (elem, animation) => new Promise(resolve => {
    elem.style.animation = animation;
    elem.addEventListener("animationend", resolve);
});

const $ = document.querySelector.bind(document);

const moveBoxOne = () => promiseAnimation($("#div_one"), "move 3s forwards");
const moveBoxTwo = () => promiseAnimation($("#div_two"), "move 3s forwards");


Promise.resolve()
    .then(moveBoxOne)
    .then(() => console.log("one has moved"))
    .then(moveBoxTwo)
    .then(() => console.log("two has moved"))
div { display: inline-block; position: relative; border: 1px solid; }
@keyframes move {
  from {margin-left: 0px;}
  to {margin-left: 100px;}
}
<div id="div_one">div_one</div><br>
<div id="div_two">div_two</div>

Promise 并不意味着它将是异步的。由于解决是即时的,因此他们很可能同时 运行。如果你想延迟,你可以使用 setTimeout 在调用中创建延迟。

const delay = (dl) => new Promise(r => {
    setTimeout(r, dl)
})

let move_box_one = () => {
    document.getElementById('div_two').style.animation = 'move 3s forwards'
    console.log('box one moved!') 

}
let move_box_two = () => {
    document.getElementById('div_one').style.animation = 'move 3s forwards'
    console.log('box two moved!')       
}

(async function() {
    move_box_one()
    await delay(200)
    move_box_two()
})()