Почему установка свойства CSS с помощью Promise.then на самом деле не происходит в блоке then? - PullRequest
11 голосов
/ 12 февраля 2020

Пожалуйста, попробуйте запустить следующий фрагмент, затем нажмите на поле.

const box = document.querySelector('.box')
box.addEventListener('click', e => {
  if (!box.style.transform) {
    box.style.transform = 'translateX(100px)'
    new Promise(resolve => {
      setTimeout(() => {
        box.style.transition = 'none'
        box.style.transform = ''
        resolve('Transition complete')
      }, 2000)
    }).then(() => {
      box.style.transition = ''
    })
  }
})
.box {
  width: 100px;
  height: 100px;
  border-radius: 5px;
  background-color: #121212;
  transition: all 2s ease;
}
<div class = "box"></div>

То, что я ожидаю, произойдет:

  • Клик произойдет
  • Коробка начнет перевод горизонтально на 100 пикселей (это действие занимает две секунды)
  • При нажатии также создается новый Promise. Внутри указанного Promise функция setTimeout установлена ​​на 2 секунды
  • После того, как действие завершено (прошло две секунды), setTimeout запускает свою функцию обратного вызова и устанавливает transition на ноль. После этого setTimeout также возвращает transform к своему первоначальному значению, таким образом визуализируя поле, чтобы оно появилось в исходном местоположении.
  • Поле появляется в исходном местоположении без эффекта перехода проблема здесь
  • После всех этих финалов sh установите значение transition для коробки обратно к его первоначальному значению

Однако, как видно, * Значение 1036 * при запуске не похоже на none. Я знаю, что есть другие методы для достижения вышеизложенного, например, использование ключевого кадра и transitionend, но почему это происходит? Я явно устанавливаю transition обратно в исходное значение только после того, как setTimeout завершит свой обратный вызов, разрешив тем самым Обещание.

EDIT

В соответствии с запросом приведен код, отображающий проблемное поведение c: Problem

Ответы [ 4 ]

4 голосов
/ 12 февраля 2020

Событие l oop партиями изменения стиля. Если вы измените стиль элемента в одной строке, браузер не покажет это изменение немедленно; это будет ждать до следующего кадра анимации. Вот почему, например,

elm.style.width = '10px';
elm.style.width = '100px';

не приводит к мерцанию; браузер заботится только о значениях стиля, установленных после всех Javascript.

Рендеринг происходит после всех Javascript, , включая микрозадачи . .then Обещания происходит в микрозадаче (которая будет эффективно выполняться, как только все остальные Javascript закончатся, но прежде чем что-либо еще - например, рендеринг - сможет запустить).

Что вы делаете, вы устанавливаете для свойства transition значение '' в микрозадаче, прежде чем браузер начнет рендеринг изменения, вызванного style.transform = ''.

Если вы сбросите переход к пустая строка после requestAnimationFrame (которая запустится непосредственно перед следующей перерисовкой), а затем после setTimeout (которая запустится сразу после следующей перерисовки) будет работать как положено:

const box = document.querySelector('.box')
box.addEventListener('click', e => {
  if (!box.style.transform) {
    box.style.transform = 'translateX(100px)'
    setTimeout(() => {
      box.style.transition = 'none'
      box.style.transform = ''
      // resolve('Transition complete')
      requestAnimationFrame(() => {
        setTimeout(() => {
          box.style.transition = ''
        });
      });
    }, 2000)
  }
})
.box {
  width: 100px;
  height: 100px;
  border-radius: 5px;
  background-color: #121212;
  transition: all 2s ease;
}
<div class="box"></div>
3 голосов
/ 13 февраля 2020

Вы сталкиваетесь с изменением перехода , которое не работает, если скрывается элемент , но непосредственно в свойстве transition.

Вы можете обратитесь к этому ответу , чтобы понять, как CSSOM и DOM связаны для процесса "перерисовки".
По сути, браузеры обычно ждут следующего кадра рисования , чтобы пересчитать все новые позиции в ящиках и, следовательно, для применения правил CSS к CSSOM.

Так что в вашем обработчике Promise, когда вы сбрасываете transition в "", transform: "" еще не был вычислен , Когда он будет рассчитан, transition уже будет сброшен на "", и CSSOM запустит переход для обновления преобразования.

Однако мы можем заставить браузер вызвать «reflow» и, таким образом, мы можем сделать так, чтобы он пересчитал положение вашего элемента, прежде чем сбросить переход на "".

const box = document.querySelector('.box')
box.addEventListener('click', e => {
  if (!box.style.transform) {
    box.style.transform = 'translateX(100px)'
    new Promise(resolve => {
      setTimeout(() => {
        box.style.transition = 'none'
        box.style.transform = ''
        box.offsetWidth; // this triggers a reflow
        resolve('Transition complete')
      }, 2000)
    }).then(() => {
      box.style.transition = ''
    })
  }
})
.box {
  width: 100px;
  height: 100px;
  border-radius: 5px;
  background-color: #121212;
  transition: all 2s ease;
}
<div class = "box"></div>

Что делает использование Обещания совершенно ненужным:

const box = document.querySelector('.box')
box.addEventListener('click', e => {
  if (!box.style.transform) {
    box.style.transform = 'translateX(100px)'
    setTimeout(() => {
      box.style.transition = 'none'
      box.style.transform = ''
      box.offsetWidth; // this triggers a reflow
      // even synchronously
      box.style.transition = ''
    }, 2000)
  }
})
.box {
  width: 100px;
  height: 100px;
  border-radius: 5px;
  background-color: #121212;
  transition: all 2s ease;
}
<div class = "box"></div>

И для объяснения микро-задач, таких как Promise.resolve() или MutationEvents или queueMicrotask() , вы должны понимать, что они будут запущены, как только будет выполнено текущее задание, 7-й шаг модели обработки Event-l oop , перед этапами рендеринга .
Так что в вашем случае это очень похоже на синхронный запуск.

Кстати, остерегайтесь микро-задач, они могут быть блокированы на некоторое время. L oop:

// this will freeze your page just like a while(1) loop
const makeProm = ()=> Promise.resolve().then( makeProm );
0 голосов
/ 12 февраля 2020

Я ценю, что это не совсем то, что вы ищете, но - из любопытства и ради полноты - я хотел посмотреть, смогу ли я написать CSS -подход только к этому эффекту.

Почти ... но оказывается, что мне все равно пришлось включить одну строку javascript.

Рабочий пример:

document.querySelector('.box').addEventListener('animationend', (e) => e.target.blur());
.box {
  width: 100px;
  height: 100px;
  border-radius: 5px;
  background-color: #121212;
  cursor: pointer;
}

.box:focus {
 animation: boxAnimation 2s ease;
}

@keyframes boxAnimation {
  100% {transform: translateX(100px);}
}
<div class="box" tabindex="0"></div>
0 голосов
/ 12 февраля 2020

Я полагаю, что ваша проблема в том, что в .then вы устанавливаете transition на '', тогда как вы должны установить none, как вы это делали при обратном вызове таймера.

const box = document.querySelector('.box');
box.addEventListener('click', e => {
  if (!box.style.transform) {
    box.style.transform = 'translateX(100px)';
    new Promise(resolve => {
      setTimeout(() => {
        box.style.transition = 'none';
        box.style.transform = '';
        resolve('Transition complete');
      }, 2000)
    }).then(() => {
     box.style.transition = 'none'; // <<----
    })
  }
})
.box {
  width: 100px;
  height: 100px;
  border-radius: 5px;
  background-color: #121212;
  transition: all 2s ease;
}
<div class = "box"></div>
...