[javascript] Promise.를 사용하여 CSS 속성을 설정하면 실제로 then 블록에서 발생하지 않는 이유는 무엇입니까?

다음 스 니펫을 실행 한 다음 상자를 클릭하십시오.

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>

내가 일어날 것으로 예상되는 것 :

  • 클릭이 발생합니다
  • 상자가 가로로 100px 번역을 시작합니다 (2 초 소요)
  • 클릭하면 새로운 Promise것도 생성됩니다. 내부 Promise에서 setTimeout기능이 2 초로 설정되었습니다.
  • 조치가 완료된 후 (2 초가 경과 한 후) setTimeout콜백 기능을 실행하고 transitionnone으로 설정하십시오 . 그런 다음 원래 값으로 setTimeout되돌려 transform서 상자를 원래 위치에 표시합니다.
  • 상자는 전환 효과 문제 없이 원래 위치에 나타납니다.
  • 모든 작업이 끝나면 transition상자 값을 원래 값으로 다시 설정하십시오.

그러나 알 수 있듯이 transition값을 실행할 none때 보이지 않는 것 같습니다 . 위와 같은 다른 방법, 예를 들어 keyframe 및를 사용하는 방법 transitionend이 있지만 왜 이런 일이 발생합니까? 콜백 이 완료된 후에transition 만 명시 적으로 원래 값으로 되돌려 서 약속을 해결합니다.setTimeout

편집하다

요청에 따라 문제가되는 동작을 표시하는 코드는 다음과 같습니다.
문제



답변

이벤트 루프 배치 스타일이 변경됩니다. 한 줄에서 요소의 스타일을 변경하면 브라우저에 해당 변경 사항이 즉시 표시되지 않습니다. 다음 애니메이션 프레임까지 기다립니다. 이것이 예를 들어

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

깜박 거리지 않습니다. 브라우저는 모든 Javascript가 완료된 후에 설정 한 스타일 값만 고려합니다.

렌더링은 마이크로 태스크를 포함하여 모든 Javascript가 완료된 후에 발생합니다 . 약속의는 microtask 발생 (다른 모든 자바 스크립트가 끝난 효과적으로 빨리으로 실행되지만 다른 어떤 전에 – 실행에 기회가있다 – 같은 렌더링 등)..then

브라우저가에 의한 변경 사항을 렌더링하기 전에 마이크로 작업에서 transition속성을 설정하고 있습니다 .''style.transform = ''

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


답변

요소가 숨겨진 문제를 시작 하지만 transition속성에서 직접 시작하면 변형의 변형이 작동하지 않습니다 .

CSSOM과 DOM이 “다시 그리기”프로세스를 위해 어떻게 연결되는지 이해 하려면 이 답변 을 참조하십시오 .
기본적으로 브라우저는 일반적으로 다음 페인팅 프레임 이 모든 새 상자 위치를 다시 계산하고 CSSOM에 CSS 규칙을 적용 할 때까지 기다립니다 .

당신의 약속 처리기 그래서, 당신이 다시 때 transition하는 ""의는 transform: ""여전히 아직 계산되지 않았습니다. 계산이 완료되면이 값 transition은 이미 재설정되었으며 ""CSSOM은 변환 업데이트를위한 전환을 트리거합니다.

그러나 브라우저가 “리플 로우”를 트리거하도록 할 수 있으므로 전환을로 재설정하기 전에 요소의 위치를 ​​다시 계산할 수 있습니다 "".

약속을 사용하지 않아도됩니다.

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 단계를 하기 전에, 렌더링 단계 .
따라서 귀하의 경우 동 기적으로 실행되는 것과 매우 유사합니다.

그건 그렇고, 마이크로 작업은 while 루프만큼 차단 될 수 있습니다.

// this will freeze your page just like a while(1) loop
const makeProm = ()=> Promise.resolve().then( makeProm );


답변

나는 이것이 당신이 찾고있는 것이 아니라고 생각하지만 호기심과 완전성을 위해이 효과에 대한 CSS 전용 접근법을 작성할 수 있는지 알고 싶었습니다.

거의 …하지만 여전히 한 줄의 자바 스크립트를 포함해야한다는 것이 밝혀졌습니다.

작업 예 :

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>


답변

나는 당신의 문제는 그냥 믿는 당신이에서 .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>


답변