首页 > 解决方案 > Ordering jQuery functions or rotating an element at different speeds in the same program

问题描述

I am trying to write a flashcards program. The user should have the ability to go to the next/previous card using the arrow keys and flip the card (by clicking or pressing the spacebar). The flip animation is done by css styling the card with:

transition: transform 0.8s;

Then, the flip animation is performed as follows:

if (flipped) {
  $('.card').css('transform', 'none');
} else {
  $('.card').css('transform', 'rotateY(180deg)');
}

The issue is that if the user is on the back of the card and presses the arrow key to go to the next card, I would like the user to see the FRONT of the next card. Essentially, if the card is flipped, I would like the card to flip over in zero seconds instead of 0.8s and then go to the next card.

So far I have tried something like:

function next_card() {
  if(flipped) {
      $('.card').css('transition', 'transform 0s')
                .css('transform', 'none')
                .css('transition', 'transform 0.8s');
  }
  // Go to next card
}

However, the program executes css('transition', 'transform 0.8s') before css('transform', 'none') making the code useless. I have tried to look into deferreds and promises as a way to ensure that the functions execute in the correct order but I am not a very experienced programmer and got confused about how to apply them to my situation.

Edit: make a jsfiddle here: https://jsfiddle.net/bjfy3tmc/28/ to illustrate the issue. The problem is that when the user is on the back of a card and then goes to the next or previous card, the card should shown the FRONT of the next card and not see a flip animation at all.

标签: javascriptjquerycss

解决方案


You should allow the paint cycle to execute with the first CSS changes, so you will only reset the CSS to the 0.8 sec transformation after that has happened.

You can use requestAnimationFrame for that. The callback you pass will be executed just before the next repaint, which is still too early. But if you call requestAnimationFrame again within that callback, and pass it yet another callback, that callback will be scheduled to execute after the current repaint, and before the next, which is what you want.

So:

$('.card-inner').css('transition', 'transform 0s')
                .css('transform', 'none');
requestAnimationFrame(function () {
  requestAnimationFrame(function () {
    $('.card-inner').css('transition', 'transform 0.8s');
  })
});

This still means that there is one frame where the next card is shown (with the back side). To also prevent that, put the rest of the code (which sets the front/end content) in an else block, and call iter a second time when it is sure that the new CSS is in effect:

function iter(x) {
  if($('.card-inner').css('transform') != 'none') {
    $('.card-inner').css('transition', 'transform 0s')
                    .css('transform', 'none');
    requestAnimationFrame(function () {
      requestAnimationFrame(function () {
        iter(x);
        $('.card-inner').css('transition', 'transform 0.8s');
      })
    })
  } else if(index+x < front_cards.length && index+x >= 0) {
    $('.front').html(front_cards[index+x]);
    $('.back').html(back_cards[index+x]);
    index += x;
  }
}

Or, alternatively, always use the callback system to go to the next/prev card:

function iter(x) {
  if($('.card-inner').css('transform') != 'none') {
    $('.card-inner').css('transition', 'transform 0s')
                    .css('transform', 'none');
  }
  requestAnimationFrame(function () {
    requestAnimationFrame(function () {
      if(index+x < front_cards.length && index+x >= 0) {
        $('.front').html(front_cards[index+x]);
        $('.back').html(back_cards[index+x]);
        index += x;
      }
      $('.card-inner').css('transition', 'transform 0.8s');
    })
  });
}

推荐阅读