Promise.then का उपयोग करके CSS संपत्ति की स्थापना वास्तव में तत्कालीन ब्लॉक में क्यों नहीं होती है?


11

कृपया निम्न स्निपेट आज़माएँ और चलाएँ, फिर बॉक्स पर क्लिक करें।

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 तक क्षैतिज रूप से अनुवाद करना शुरू कर देता है (इस क्रिया में दो सेकंड लगते हैं)
  • क्लिक करने पर, एक नया Promiseभी बनाया जाता है। अंदर ने कहा Promise, एक setTimeoutफ़ंक्शन 2 सेकंड के लिए सेट है
  • कार्रवाई पूरी होने के बाद (दो सेकंड बीत चुके हैं), setTimeoutअपने कॉलबैक फ़ंक्शन को चलाता है और transitionकिसी को भी सेट नहीं करता है। ऐसा करने के बाद, अपने मूल मूल्य पर setTimeoutभी भरोसा transformकरता है, इस प्रकार मूल स्थान पर प्रदर्शित होने के लिए बॉक्स को प्रस्तुत करता है।
  • बॉक्स मूल स्थान पर दिखाई देता है जिसमें कोई संक्रमण प्रभाव समस्या नहीं है
  • उन सभी खत्म होने के बाद, transitionबॉक्स के मूल्य को उसके मूल मूल्य पर वापस सेट करें

हालांकि, जैसा कि देखा जा सकता है, चलने के दौरान transitionमान नहीं लगता है none। मुझे पता है कि उपरोक्त तरीकों को प्राप्त करने के लिए अन्य तरीके हैं, जैसे कि कीफ्रेम का उपयोग करना और transitionend, लेकिन ऐसा क्यों होता है? मैंने स्पष्ट रूप से इसके कॉलबैक को समाप्त करने के बादtransition ही इसके मूल मूल्य पर वापस सेट किया है , इस प्रकार प्रोमिस को हल करना।setTimeout

संपादित करें

अनुरोध के अनुसार, यहाँ समस्यात्मक व्यवहार प्रदर्शित करने वाले कोड का gif है: मुसीबत


आप इसे किस ब्राउज़र में देख रहे हैं? क्रोम पर, मैं देख रहा हूं कि इसका क्या उद्देश्य है।
टेरी

विंडोज के लिए @ टेरी फ़ायरफ़ॉक्स 73.0 (64-बिट)।
रिचर्ड

क्या आप अपने प्रश्न को gif संलग्न कर सकते हैं जो इस मुद्दे को दिखाता है? जहां तक ​​मैं बता सकता हूं कि यह फ़ायरफ़ॉक्स पर अपेक्षित रूप से प्रतिपादन / व्यवहार कर रहा है।
टेरी

जब वादा हल हो जाता है, तो मूल संक्रमण बहाल हो जाता है, लेकिन इस बिंदु पर बॉक्स अभी भी रूपांतरित होता है। इसलिए यह वापस संक्रमण करता है। मूल मान में परिवर्तन को रीसेट करने से पहले आपको कम से कम 1 और फ़्रेम की प्रतीक्षा करने की आवश्यकता है: jsfiddle.net/khrismuc/3mjwtack
क्रिस जी

जब कई बार चलाते हैं, तो मैं एक बार समस्या को पुन: पेश करने में कामयाब रहा। संक्रमण निष्पादन कंप्यूटर पर पृष्ठभूमि पर क्या कर रहा है पर निर्भर करता है। वादे को हल करने से पहले देरी में कुछ और समय जोड़ने से मदद मिल सकती है।
तीमु

जवाबों:


4

इवेंट लूप बैचेज़ शैली बदलता है। यदि आप किसी तत्व की शैली को एक पंक्ति में बदलते हैं, तो ब्राउज़र उस परिवर्तन को तुरंत नहीं दिखाता है; यह अगले एनीमेशन फ्रेम तक इंतजार करेंगे। यह है, उदाहरण के लिए

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

चंचलता में परिणाम नहीं करता है; ब्राउज़र केवल सभी जावास्क्रिप्ट के पूरा होने के बाद सेट किए गए शैली मूल्यों के बारे में परवाह करता है।

सभी जावास्क्रिप्ट पूरा होने के बाद रेंडरिंग होता है , जिसमें माइक्रोटेस भी शामिल हैं.thenएक वादा के एक microtask में होता है (जो प्रभावी रूप से ही चला जाएगा के रूप में अन्य सभी जावास्क्रिप्ट समाप्त हो गया है, लेकिन कुछ और करने से पहले - रन का मौका मिला है - जैसे रेंडरिंग के रूप में)।

आप जो कर रहे हैं, वह 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>


यह वह उत्तर है जिसकी मुझे तलाश थी। धन्यवाद। क्या आप, शायद, एक लिंक प्रदान कर सकते हैं जो इन माइक्रोटस्क और रिपेंट मैकेनिक्स का वर्णन करता है ?
रिचर्ड

यह एक अच्छे सारांश की तरह दिखता है: javascript.info/event-loop
SurePerformance

2
यह बिल्कुल सच नहीं है। इवेंट लूप शैली में बदलाव के बारे में कुछ नहीं कहता है। अधिकांश ब्राउज़र अगले पेंटिंग फ्रेम के लिए इंतजार करने की कोशिश करेंगे जब वे कर सकते हैं, लेकिन यह इसके बारे में है। अधिक जानकारी ;-)
काइदो

3

यदि तत्व छिपी हुई समस्या शुरू करते हैं , लेकिन आप सीधे transitionसंपत्ति पर हैं, तो आप संक्रमण के बदलाव का सामना नहीं कर रहे हैं।

आप यह समझने के लिए इस उत्तर को संदर्भित कर सकते हैं कि कैसे सीएसएसओएम और डोम "रिड्रा" प्रक्रिया के लिए जुड़े हुए हैं।
मूल रूप से, ब्राउज़र आमतौर पर सभी नए बॉक्स पदों को पुनर्गणना करने के लिए और इस प्रकार CSSOM के सीएसएस नियमों को लागू करने के लिए अगले पेंटिंग फ्रेम तक इंतजार करेंगे ।

तो अपने प्रॉमिस हैंडलर में, जब आप रीसेट करते transitionहैं "", तो transform: ""अभी भी गणना नहीं की गई है। जब इसकी गणना की transitionजाएगी , तो पहले से ही रीसेट कर दिया ""जाएगा और CSSOM ट्रांस्फ़ॉर्म अपडेट के लिए संक्रमण को ट्रिगर करेगा।

हालाँकि, हम ब्राउज़र को "reflow" ट्रिगर करने के लिए बाध्य कर सकते हैं और इस प्रकार हम संक्रमण को रीसेट करने से पहले इसे आपके तत्व की स्थिति को पुनर्गणना कर सकते हैं ""

जो वादा के उपयोग को काफी अनावश्यक बनाता है:

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 वां चरण । तो आपके मामले में, यह बहुत पसंद है अगर इसे सिंक्रोनाइज़ किया गया।

वैसे, माइक्रो-फ़ंक्शंस से सावधान रहना एक समय लूप के रूप में अवरुद्ध हो सकता है:

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

हां ठीक है, लेकिन transitionendघटना का जवाब संक्रमण के अंत से मेल खाने के लिए कड़ी मेहनत करने से बचना होगा। ट्रांज़िशनटॉप्रोमाइस.जेएस आपको लिखने की अनुमति देने वाले संक्रमण को बढ़ावा देगा transitionToPromise(box, 'transform', 'translateX(100px)').then(() => /* four lines as per answer above */)
रोमर -1888

दो फायदे: (1) संक्रमण की अवधि को जावास्क्रिप्ट को संशोधित करने की आवश्यकता के बिना CSS में संशोधित किया जा सकता है; (2) ट्रांज़िशनटॉप्रोमाइज़.जेएस पुन: प्रयोज्य है। BTW, मैंने इसकी कोशिश की और यह अच्छी तरह से काम करता है।
रोमर -1888

@ Roamer-1888 हाँ आप कर सकते हैं, हालांकि मैं व्यक्तिगत रूप (evt)=>if(evt.propertyName === "transform"){ ...से झूठी सकारात्मक से बचने के लिए एक चेक जोड़ूंगा और मुझे वास्तव में इस तरह के आयोजनों को बढ़ावा देना पसंद नहीं है, क्योंकि आप कभी नहीं जान पाएंगे कि क्या यह आग लग जाएगी (एक मामले के बारे में सोचें someAncestor.hide() जब संक्रमण चलता है, आपका वादा कभी आग नहीं देगा और आपका संक्रमण निष्फल हो जाएगा। इसलिए यह वास्तव में उनके लिए सबसे अच्छा है यह निर्धारित करने के लिए ओपी पर निर्भर है, लेकिन व्यक्तिगत रूप से और अनुभव से, मैं अब संक्रमण की घटनाओं की तुलना में टाइमआउट पसंद करता हूं।
काइडो

1
पेशेवरों और विपक्ष, मुझे लगता है। किसी भी मामले में, प्रॉमिस के साथ या उसके बिना, यह जवाब दो सेटटाइमआउट्स और एक रिक्वेस्ट एनिमीनेशनफैम को शामिल करने की तुलना में बहुत साफ है।
रोमेर -1888

हालांकि मैंरे पास भी एक सवाल है। आपने कहा था कि अगले ब्राउज़र के रिप्रेजेंट से ठीक पहलेrequestAnimationFrame() ट्रिगर किया जाएगा । आपने यह भी उल्लेख किया है कि ब्राउज़र आमतौर पर अगले नए फ्रेम के सभी नए बॉक्स पदों को पुनर्गणना करने तक इंतजार करेंगे । फिर भी, आपको अभी भी एक मजबूर रिफ्लो (पहले लिंक पर आपका जवाब) को मैन्युअल रूप से ट्रिगर करने की आवश्यकता है। इसलिए, मैं यह निष्कर्ष निकालता हूं कि जब भी दमन से ठीक पहले ऐसा होता है, तब भी ब्राउज़र ने नवीनतम गणना की गई शैली की गणना नहीं की है; इस प्रकार, मैन्युअल रूप से शैलियों की पुनर्गणना करने की आवश्यकता है । सही बात? requestAnimationFrame()
रिचर्ड

0

मैं इसकी सराहना करता हूं कि आप जो खोज रहे हैं, वह पूरी तरह से नहीं है, लेकिन - जिज्ञासा से बाहर और पूर्णता के लिए - मैं यह देखना चाहता था कि क्या मैं इस संबंध में CSS-only दृष्टिकोण लिख सकता हूं।

लगभग ... लेकिन यह पता चला है कि मुझे अभी भी जावास्क्रिप्ट की एक पंक्ति शामिल करनी थी।

काम करने का उदाहरण:

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

मेरा मानना है कि आपकी समस्या सिर्फ यह है कि अपने में .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>


1
नहीं, ओपी इसे ''फिर से वर्ग नियम लागू करने के लिए सेट कर रहा है (जो कि तत्व नियम द्वारा अधिलेखित किया गया था) आपका कोड बस इसे 'none'दो बार सेट करता है , जो बॉक्स को वापस संक्रमण से रोकता है लेकिन इसके मूल संक्रमण (कक्षा के) को पुनर्स्थापित नहीं करता है
क्रिस जी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.