Neden Promise.then kullanarak CSS özelliği ayarlama then bloğunda aslında gerçekleşmiyor?


11

Lütfen aşağıdaki kod parçasını çalıştırmayı deneyin ve ardından kutuyu tıklayın.

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>

Ne olmasını bekliyorum:

  • Tıklama olur
  • Kutu yatay olarak 100 piksel çevirmeye başlar (bu işlem iki saniye sürer)
  • Tıklandığında yeni Promisebir de oluşturulur. İçeride Promise, bir setTimeoutfonksiyon 2 saniyeye ayarlandı
  • Eylem tamamlandıktan sonra (iki saniye geçtikten sonra), setTimeoutgeri arama işlevini çalıştırır ve transitionyok olarak ayarlar . Bunu yaptıktan sonra , orijinal değerine setTimeoutgeri döner transform, böylece kutunun orijinal konumunda görünmesini sağlar.
  • Kutu, orijinal konumda burada geçiş etkisi sorunu olmadan görünür
  • Bunların hepsi bittikten sonra transition, kutunun değerini orijinal değerine geri ayarlayın

Ancak, görülebileceği gibi, transitiondeğer noneçalışırken görünmüyor . Yukarıdakileri başarmak için başka yöntemler olduğunu biliyorum, örneğin anahtar kare kullanma ve transitionendneden bu oluyor? Ben açıkça ayarlanmış transitionsadece orijinal değerine geri sonrasındasetTimeout böylece Promise çözme, onun geri arama bitirir.

DÜZENLE

İsteğe bağlı olarak, sorunlu davranışı gösteren kodun bir gif: Sorun


Bunu hangi tarayıcıda görüyorsunuz? Chrome'da ne amaçlandığını görüyorum.
Terry

@Terry Firefox 73.0 (64 bit) Windows için.
Richard

Sorunuza sorunu gösteren bir gif ekleyebilir misiniz? Bildiğim kadarıyla Firefox'ta beklendiği gibi render / davranış.
Terry

Promise çözüldüğünde, orijinal geçiş geri yüklenir, ancak bu noktada kutu hala dönüştürülür. Bu nedenle geri döner. Orijinal değere geçişi sıfırlamadan önce en az 1 kare daha beklemeniz gerekir: jsfiddle.net/khrismuc/3mjwtack
Chris G

Birden çok kez çalıştırdığımda, sorunu bir kez yeniden oluşturmayı başardım. Geçiş yürütme, bilgisayarın arka planda ne yaptığına bağlıdır. Sözü çözmeden önce gecikmeye biraz daha zaman eklemek yardımcı olabilir.
Teemu

Yanıtlar:


4

Olay döngüsü toplu tarzı değişir. Bir satırdaki bir öğenin stilini değiştirirseniz, tarayıcı bu değişikliği hemen göstermez; bir sonraki animasyon karesine kadar bekleyecek. Bu yüzden, örneğin

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

titremeye yol açmaz; tarayıcı yalnızca tüm Javascript tamamlandıktan sonra ayarlanan stil değerlerine önem verir.

Oluşturma , mikro görevler dahil tüm Javascript tamamlandıktan sonra gerçekleşir . Bir Promise'in bir MicroTask oluşur (diğer tüm JavaScript bittikten kadar etkili olan en kısa sürede çalışır, ancak her şeyden önce - vadede şansı etti - Böyle rendering gibi)..then

Yaptığınız şey , tarayıcıdan kaynaklanan değişikliği gerçekleştirmeye başlamadan önce transitionözelliği ''mikro görevde ayarlamanızdır style.transform = ''.

Boş dizeye geçişi a requestAnimationFrame(sonraki yeniden boyamadan hemen önce çalışacak) ve sonra da setTimeout(sonraki yeniden boyamadan hemen sonra çalışacak ) sonra sıfırlarsanız, beklendiği gibi çalışır:

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>


Aradığım cevap buydu. Teşekkürler. Belki de bu mikro işi ve yeniden boyama mekaniğini tanımlayan bir bağlantı sağlayabilir misiniz ?
Richard

Bu iyi bir özet gibi görünüyor: javascript.info/event-loop
CertainPerformance

2
Bu tam olarak doğru değil hayır. Olay döngüsü stil değişiklikleri hakkında hiçbir şey söylemiyor. Çoğu tarayıcı, mümkün olduğunda bir sonraki resim çerçevesini beklemeye çalışır, ancak hepsi bu kadar. daha fazla bilgi ;-)
Kaiido

3

Öğe gizli sorunu başlatırsa , doğrudan transitionözellikte geçişin bir varyasyonuyla karşılaşıyorsunuz .

CSSOM ve DOM'un "yeniden çizme" işlemi için nasıl bağlandığını anlamak için bu cevaba başvurabilirsiniz .
Temel olarak, tarayıcılar genellikle tüm yeni kutu konumlarını yeniden hesaplamak ve böylece CSS kurallarını CSSOM'a uygulamak için bir sonraki boyama çerçevesine kadar bekleyecektir.

Senin Promise işleyicisinde Yani, sıfırlamak zaman transitioniçin "", transform: ""hala henüz hesaplanmış değil. Hesaplanacağı zaman transition, zaten sıfırlanmış olacak ""ve CSSOM dönüşüm güncellemesi için geçişi tetikleyecektir.

Ancak, tarayıcıyı bir "yeniden akış" tetiklemeye zorlayabiliriz ve böylece geçişi sıfırlamadan önce öğenizin konumunu yeniden hesaplayabiliriz "".

Bu, Sözün kullanımını gereksiz kılar:

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>


Ve mikro görevler gibi üzerine bir açıklama Promise.resolve()veya MutationEvents ya queueMicrotask(), onlar yakında şimdiki görev, yapılır gibi ran alırsınız anlamamız gerekir Olay döngü işleme modelinin 7 adımı öncesinde, render adımlar .
Yani sizin durumunuzda, senkronize olarak çalıştırılmış gibi.

Bu arada, mikro görevlerin bir süre döngüsü kadar engelleme olabileceğine dikkat edin:

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

Evet, tam olarak, ancak transitionendetkinliğe yanıt vermek , geçişin sonuna uyacak bir zaman aşımını kodlamak zorunda kalmaz . transitionToPromise.js , yazmanıza izin veren geçişe söz verecektir transitionToPromise(box, 'transform', 'translateX(100px)').then(() => /* four lines as per answer above */).
Roamer-1888

İki avantaj: (1) geçişin süresi daha sonra javascript üzerinde değişiklik yapmak zorunda kalmadan CSS'de değiştirilebilir; (2) transitionToPromise.js yeniden kullanılabilir. BTW, denedim ve iyi çalışıyor.
Roamer-1888

@ Roamer-1888 evet, (evt)=>if(evt.propertyName === "transform"){ ...yanlış pozitiflerden kaçınmak için kişisel olarak bir kontrol ekleyeceğim ve bu tür olayları vaat etmekten gerçekten hoşlanmıyorum, çünkü hiç ateş edip etmeyeceğini asla bilemezsiniz ( someAncestor.hide() geçiş çalıştığında olduğu gibi bir durum düşünün , Sözünüz asla ateş etmeyecek ve geçişiniz devre dışı bırakılacak, bu yüzden onlar için en iyi olanı belirlemek
OP'ye kalmış

1
Artıları ve eksileri, sanırım. Her durumda, vaatle veya vaatsiz olarak, bu cevap iki setTimeout ve bir requestAnimationFrame içeren bir cevaptan çok daha temizdir.
Roamer-1888

Yine de bir sorum var. Bunun bir sonraki tarayıcı boyamadan hemen öncerequestAnimationFrame() tetikleneceğini söylediniz . Ayrıca, tarayıcıların tüm yeni kutu konumlarını yeniden hesaplamak için genellikle bir sonraki resim çerçevesine kadar bekleyeceğini de belirttiniz . Yine de, zorunlu olarak yeniden düzenlemeyi manuel olarak tetiklemeniz gerekiyordu (ilk bağlantıdaki cevabınız). Ben, bu nedenle, zaman bile o sonuca varmak sadece rötuş önce olur, tarayıcı hala gelmiştir değil yeni bilgisayarlı tarzı hesaplanan; Böylece, gerek için elle stiller yeniden hesaplamaya. Doğru? requestAnimationFrame()
Richard

0

Aradığın şey bu değil, ama - meraktan ve bütünlük uğruna - bu etkiye sadece CSS yaklaşımını yazıp yazamayacağımı görmek istedim.

Neredeyse ... ama yine de tek bir javascript satırı eklemem gerekti.

Çalışma Örneği:

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>


-1

Ben senin sorunu sadece olduğuna inandığınız senin içinde .thensen ayarlıyorsunuz transitionTo ''sen ayarlayarak gerekirken, noneZamanlayıcı geri aramasında olduğu gibi.

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>


1
Hayır, OP, ''sınıf kuralını (element kuralı tarafından geçersiz kılınmıştır) tekrar uygulamak için ayarlıyor, kodunuz 'none'iki kez ayarlıyor , bu da kutunun geri geçişini önlüyor ancak orijinal geçişini (sınıfın) geri yüklemiyor
Chris G
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.