最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to chain CSS animations using Promise? - Stack Overflow

programmeradmin3浏览0评论

I'm trying to figure out how Promise works and to make one CSS animation to run after another. But they run simultaneously...

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();
});

I'm trying to figure out how Promise works and to make one CSS animation to run after another. But they run simultaneously...

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();
});
Share Improve this question edited Mar 14, 2020 at 14:30 Kate Orlova 3,2835 gold badges14 silver badges36 bronze badges asked Mar 14, 2020 at 14:00 DraxDrax 1691 silver badge9 bronze badges 3
  • 4 You are chaining your promise-returning function calls together correctly. But the functions resolve their returned promises immediately, without waiting for the animation to finish! – Bergi Commented Mar 14, 2020 at 14:03
  • stackoverflow./questions/54316852/… – Bergi Commented Mar 14, 2020 at 14:06
  • 3 Promises don't make anything asynchronous. They're for managing asynchronous behavior in other APIs. – Pointy Commented Mar 14, 2020 at 14:06
Add a ment  | 

4 Answers 4

Reset to default 15

In contrary to the answers posted, I would discourage using window.setTimeout, because it does not guarantee that the timers are in sync with the animation end events, which sometimes are offloaded to the GPU. If you want to be extra sure, you should be listening to the animationend event, and resolving it in the callback itself, i.e.:

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';
  });
}

Even better, is that since you're writing somewhat duplicated logic for both boxes, you can abstract all that into a generic function that returns a promise:

// 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');
}

Also, your move_boxes function is a little convoluted. If you want to run the individual box-moving animations asynchronous, declare it as an async method and simply await individual box moving function calls, i.e.:

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

See proof-of-concept example (or you can see it from this JSFiddle that I've forked from your original one):

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

You'll want to only resolve the promise when you know that the corresponding animation event pleted.

You can use the animationend event for that, which is more appropriate than using setTimeout with the expected duration.

It will also be useful to create one utility function for creating a promise for that, and then to reuse that for whichever element and whichever animation description:

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>

You need to wait for animation to plete, Here is a working code,

    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();
});

Working fiddle : https://jsfiddle/bu96cxak/

Promise does not mean it will be asynchronous. Since the resolve is instant, so most likely they run in same time. If you want to delay, u can create a delay in a call using 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()
})()
发布评论

评论列表(0)

  1. 暂无评论