diff --git a/js/dom.js b/js/dom.js index b95627d5..a3db3148 100644 --- a/js/dom.js +++ b/js/dom.js @@ -134,27 +134,31 @@ function scrollElementIntoView(element, {invalidMarginRatio = 0} = {}) { } -function animateElement( - element, { - className = 'highlight', - removeExtraClasses = [], - onComplete, - } = {}) { - return element && new Promise(resolve => { - element.addEventListener('animationend', () => { - element.classList.remove( - className, - // In Firefox, `resolve()` might be called one frame later. - // This is helpful to clean-up on the same frame - ...removeExtraClasses - ); - // TODO: investigate why animation restarts for 'display' modification in .then() - if (typeof onComplete === 'function') { - onComplete.call(element); - } +/** + * @param {HTMLElement} el + * @param {string} [cls] - class name that defines or starts an animation + * @param [removeExtraClasses] - class names to remove at animation end in the *same* paint frame, + * which is needed in e.g. Firefox as it may call resolve() in the next frame + * @returns {Promise} + */ +function animateElement(el, cls = 'highlight', ...removeExtraClasses) { + return !el ? Promise.resolve(el) : new Promise(resolve => { + let onDone = () => { + el.classList.remove(cls, ...removeExtraClasses); + onDone = null; resolve(); - }, {once: true}); - element.classList.add(className); + }; + requestAnimationFrame(() => { + if (onDone) { + const style = getComputedStyle(el); + if (style.animationName === 'none' || !parseFloat(style.animationDuration)) { + el.removeEventListener('animationend', onDone); + onDone(); + } + } + }); + el.addEventListener('animationend', onDone, {once: true}); + el.classList.add(cls); }); } diff --git a/manage/config-dialog.js b/manage/config-dialog.js index 9bb3bbd6..756a12ce 100644 --- a/manage/config-dialog.js +++ b/manage/config-dialog.js @@ -70,7 +70,6 @@ function configDialog(style) { if (isPopup) { adjustSizeForPopup(box); - box.style.animationDuration = '0s'; } box.addEventListener('change', onchange); diff --git a/manage/import-export.js b/manage/import-export.js index 15261baf..5c19b140 100644 --- a/manage/import-export.js +++ b/manage/import-export.js @@ -21,10 +21,9 @@ onDOMready().then(() => { this.classList.remove('fadeout'); } }, - ondragend() { - animateElement(this, {className: 'fadeout', removeExtraClasses: ['dropzone']}).then(() => { - this.style.animationDuration = ''; - }); + async ondragend() { + await animateElement(this, 'fadeout', 'dropzone'); + this.style.animationDuration = ''; }, ondragleave(event) { try { diff --git a/manage/incremental-search.js b/manage/incremental-search.js index 99416784..791cdd2c 100644 --- a/manage/incremental-search.js +++ b/manage/incremental-search.js @@ -69,7 +69,7 @@ onDOMready().then(() => { focusedLink = $('.style-name-link', found); focusedName = found.styleNameLowerCase; scrollElementIntoView(found, {invalidMarginRatio: .25}); - animateElement(found, {className: 'highlight-quick'}); + animateElement(found, 'highlight-quick'); replaceInlineStyle({ width: focusedLink.offsetWidth + 'px', height: focusedLink.offsetHeight + 'px', diff --git a/manage/manage.js b/manage/manage.js index 888da2e8..972c659b 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -728,15 +728,13 @@ function embedOptions() { options.focus(); } -function unembedOptions() { +async function unembedOptions() { const options = $('#stylus-embedded-options'); if (options) { options.contentWindow.document.body.classList.add('scaleout'); options.classList.add('fadeout'); - animateElement(options, { - className: 'fadeout', - onComplete: () => options.remove(), - }); + await animateElement(options, 'fadeout'); + options.remove(); } } diff --git a/msgbox/msgbox.css b/msgbox/msgbox.css index 4f349613..1f5058f0 100644 --- a/msgbox/msgbox.css +++ b/msgbox/msgbox.css @@ -12,6 +12,10 @@ -moz-user-select: none; } +#stylus-popup #message-box { + animation: none; +} + #message-box > div { top: 3rem; right: 3rem; diff --git a/msgbox/msgbox.js b/msgbox/msgbox.js index 6a409f9f..3daff5d5 100644 --- a/msgbox/msgbox.js +++ b/msgbox/msgbox.js @@ -94,10 +94,8 @@ function messageBox({ function resolveWith(value) { unbindGlobalListeners(); setTimeout(messageBox.resolve, 0, value); - animateElement(messageBox.element, { - className: 'fadeout', - onComplete: removeSelf, - }); + animateElement(messageBox.element, 'fadeout') + .then(removeSelf); if (messageBox.element.contains(document.activeElement)) { messageBox.originalFocus.focus(); } diff --git a/popup/popup.js b/popup/popup.js index 979fdd4c..bc9df1b2 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -495,10 +495,8 @@ Object.assign(handleEvent, { const menuActive = $('.menu[data-display=true]'); if (menuActive) { // fade-out style menu - animateElement(menu, { - className: 'lights-on', - onComplete: () => (menu.dataset.display = false), - }); + animateElement(menu, 'lights-on') + .then(() => (menu.dataset.display = false)); window.onkeydown = null; } else { $('.menu-title', entry).textContent = $('.style-name', entry).textContent; @@ -567,10 +565,8 @@ Object.assign(handleEvent, { function confirm(ok) { if (ok) { // fade-out deletion confirmation dialog - animateElement(box, { - className: 'lights-on', - onComplete: () => (box.dataset.display = false), - }); + animateElement(box, 'lights-on') + .then(() => (box.dataset.display = false)); window.onkeydown = null; API.deleteStyle(id); } else {