Пожалуйста, попробуйте запустить следующий фрагмент, затем нажмите на поле.
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>
Что я ожидаю, что произойдет:
- Клик происходит
- Box начинает перевод по горизонтали на 100 пикселей (это действие занимает две секунды)
- При нажатии новая
Promise
также создается. Внутри сказалPromise
,setTimeout
функция установлена на 2 секунды - После того, как действие завершено (прошло две секунды),
setTimeout
запускается функция обратного вызова и устанавливаетсяtransition
значение none. После этогоsetTimeout
также возвращаетсяtransform
к своему первоначальному значению, таким образом отображая поле, чтобы появиться в исходном местоположении. - Поле появляется в исходном местоположении без проблем с эффектом перехода здесь
- После того, как все это закончится, установите
transition
значение окна обратно в исходное значение
Тем не менее, как можно видеть, transition
значение, по-видимому, не none
при запуске. Я знаю, что есть другие методы для достижения вышеизложенного, например, использование ключевого кадра и т.д. transitionend
, но почему это происходит? Я явно установить transition
обратно в исходное значение только послеsetTimeout
того, как заканчивает свой обратный вызов, таким образом разрешения Promise.
РЕДАКТИРОВАТЬ
В соответствии с запросом, вот gif кода, отображающего проблемное поведение:
javascript
css
promise
settimeout
Ричард
источник
источник
Ответы:
Цикл событий объединяет изменения стиля. Если вы измените стиль элемента в одной строке, браузер не покажет это изменение немедленно; это будет ждать до следующего кадра анимации. Вот почему, например,
не приводит к мерцанию; браузер заботится только о значениях стиля, установленных после завершения всего Javascript.
Рендеринг происходит после того, как завершен весь Javascript, включая микротрубы .
.then
Из Promise происходит в microtask (который будет работать эффективно , как только все другие Javascript закончил, но прежде чем что - либо другое - такие как рендеринг - имел шанс бежать).Что вы делаете, вы устанавливаете
transition
свойство''
в микрозадаче, прежде чем браузер начнет рендеринг изменений, вызванныхstyle.transform = ''
.Если вы сбрасываете переход к пустой строке после a
requestAnimationFrame
(который будет запущен непосредственно перед следующей перерисовкой), а затем после asetTimeout
(которая будет выполняться сразу после следующей перерисовки), он будет работать как ожидалось:источник
Вы сталкиваетесь с изменением перехода, который не работает, если элемент запускает скрытую проблему, но непосредственно в
transition
свойстве.Вы можете обратиться к этому ответу, чтобы понять, как CSSOM и DOM связаны для процесса «перерисовки».
По сути, браузеры обычно ждут следующего кадра рисования, чтобы пересчитать все новые позиции блока и, таким образом, применить правила CSS к CSSOM.
Таким образом, в вашем обработчике Promise, когда вы сбрасываете значение
transition
до""
, значениеtransform: ""
еще не было рассчитано. Когда он будет рассчитан,transition
он уже будет сброшен,""
и CSSOM запустит переход для обновления преобразования.Тем не менее, мы можем заставить браузер вызвать «перекомпоновку», и, таким образом, мы можем заставить его пересчитать положение вашего элемента, прежде чем мы сбросим переход к
""
.Показать фрагмент кода
Что делает использование Обещания совершенно ненужным:
И для объяснения микро-задач, таких как
Promise.resolve()
или MutationEvents , илиqueueMicrotask()
вы должны понимать, что они будут запускаться, как только текущая задача будет выполнена, 7-й шаг модели обработки цикла событий , перед этапами рендеринга .Так что в вашем случае это очень похоже на синхронный запуск.
Кстати, остерегайтесь микро-задач, которые могут блокировать цикл while:
источник
transitionend
событие можно было бы избежать жесткого кодирования тайм-аута, соответствующего концу перехода. transitionToPromise.js обещает переход, позволяющий писатьtransitionToPromise(box, 'transform', 'translateX(100px)').then(() => /* four lines as per answer above */)
.(evt)=>if(evt.propertyName === "transform"){ ...
чтобы избежать ложных срабатываний, и мне не очень нравится обещать такие события, потому что вы никогда не знаете, произойдет ли это когда-нибудь (вспомните случай, например,someAncestor.hide()
когда происходит переход, ваше обещание никогда не будет огня , и ваш переход будет застревать отключен , так что это действительно до ОП , чтобы определить , что лучше для них, но лично и по опыту, теперь я предпочитаю таймаута , чем transitionend событий..requestAnimationFrame()
это будет запущено как раз перед следующей перерисовкой браузера. Вы также упомянули, что браузеры обычно ждут следующего кадра рисования, чтобы пересчитать все новые позиции блока . Тем не менее, вам все равно нужно было вручную инициировать принудительное перерасход (ваш ответ по первой ссылке). Я, следовательно, делаю вывод, что даже когда этоrequestAnimationFrame()
происходит непосредственно перед перерисовкой, браузер все еще не рассчитал новейший вычисленный стиль; Таким образом, необходимо вручную принудительно пересчитывать стили. Правильный?Я ценю, что это не совсем то, что вы ищете, но - из любопытства и ради полноты - я хотел посмотреть, смогу ли я написать подход, основанный только на CSS, для этого эффекта.
Почти ... но, оказывается, мне все равно пришлось включить одну строчку javascript.
Рабочий пример:
источник
Я полагаю, что ваша проблема заключается только в том, что
.then
вы устанавливаетеtransition
для''
, когда вы должны установить его,none
как это было в обратном вызове таймера.источник
''
снова применять правило класса (которое было отменено правилом элемента), ваш код просто устанавливает его'none'
дважды, что предотвращает переход блока обратно, но не восстанавливает его первоначальный переход (класса)