From 456b263968cc7a2b87ca2cb847b4ec630db21872 Mon Sep 17 00:00:00 2001 From: Alex Riviere Date: Sat, 26 Feb 2022 22:51:41 +0000 Subject: [PATCH] Make a FLIP animation handler --- src/scripts/handleChat.mjs | 8 +++- src/scripts/utilities.mjs | 77 ++++++++++++++++++++++++++++++++------ src/themes/alex-party.css | 12 ++++++ 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/src/scripts/handleChat.mjs b/src/scripts/handleChat.mjs index 36f3fd1..f472b85 100644 --- a/src/scripts/handleChat.mjs +++ b/src/scripts/handleChat.mjs @@ -9,7 +9,11 @@ // } from './bttvIntegration.mjs'; import {isLightOrDark} from './colorContrast.mjs'; -import {removeAllMessagesFromUser, removeMessage} from './utilities.mjs'; +import { + removeAllMessagesFromUser, + removeMessage, + createAnimationHandler, +} from './utilities.mjs'; const chatbox = document.querySelector('[data-twitch-chat]'); const watchedChannels = chatbox.getAttribute('data-twitch-chat'); @@ -238,6 +242,8 @@ ComfyJS.onChat = function (user, messageContents, flags, self, extra) { chatbox.appendChild(newMessage); + createAnimationHandler('created', newMessage); + if (window.CONFIG.clearMessageAfter) { setTimeout(() => { removeMessage(extra.id); diff --git a/src/scripts/utilities.mjs b/src/scripts/utilities.mjs index f48d53b..13ed987 100644 --- a/src/scripts/utilities.mjs +++ b/src/scripts/utilities.mjs @@ -1,3 +1,65 @@ +/** + * Adds animation/transition listeners for a given event. Will immediately abort if there is no animation or transition. + * + * @param {string} eventName - the name of the event to be used. There will be data-attributes added to the element with the event name for the animation/transition event. + * @param {HTMLElement} element - the element to apply listeners/attributes to. + * @param {Function?} [callback] - The callback function at the end of the animation/transition. Optional. + */ +export function createAnimationHandler(eventName, element, callback) { + const attrPrefix = (e) => `data-${eventName}-${e}`; + element.setAttribute(attrPrefix('from'), true); + + const trackedProperties = new Set(); + + const __finishAnimation = () => { + if (trackedProperties.size === 0) { + element.removeEventListener('transitionstart', __transitionStart); + element.removeEventListener('transitionend', __transitionEnd); + element.removeEventListener('animationstart', __animationStart); + element.removeEventListener('animationend', __animationEnd); + element.removeEventListener('animationiteration', __animationEnd); + element.removeAttribute(attrPrefix('active')); + element.removeAttribute(attrPrefix('to')); + if (typeof callback === 'function') callback(); + } + }; + + const __transitionStart = (e) => { + clearTimeout(abortCode); + trackedProperties.add('transition:' + e.propertyName); + }; + const __transitionEnd = (e) => { + trackedProperties.delete('transition:' + e.propertyName); + __finishAnimation(e.target); + }; + const __animationStart = (e) => { + clearTimeout(abortCode); + trackedProperties.add('animation:' + e.propertyName); + }; + + const __animationEnd = (e) => { + trackedProperties.delete('animation:' + e.propertyName); + __finishAnimation(e.target); + }; + + const abortCode = setTimeout(() => { + console.log('Aborting...'); + __finishAnimation(element); + }, 100); + + element.addEventListener('transitionstart', __transitionStart); + element.addEventListener('transitionend', __transitionEnd); + element.addEventListener('animationstart', __animationStart); + element.addEventListener('animationend', __animationEnd); + element.addEventListener('animationiteration', __animationEnd); + + requestIdleCallback(() => { + element.setAttribute(attrPrefix('active'), true); + element.setAttribute(attrPrefix('to'), true); + element.removeAttribute(attrPrefix('from')); + }); +} + /** * Removes all messages sent by a given user from the overlay. * Called when a user is banned. @@ -70,21 +132,12 @@ export function removeMessage(messageId) { ); if (remainingMessageToDelete) { _removeMessageFromDomAndShiftOthers(messageToDelete); - removeEventListener('transitionend', _callbackRemoveMessageFromDom); - removeEventListener('animationend', _callbackRemoveMessageFromDom); } } - // Give animation a chance - messageToDelete.addEventListener( - 'transitionend', - _callbackRemoveMessageFromDom - ); - messageToDelete.addEventListener( - 'animationend', + createAnimationHandler( + 'delete', + messageToDelete, _callbackRemoveMessageFromDom ); - - // If no animation, delete anyways - setTimeout(_callbackRemoveMessageFromDom, 1200); } diff --git a/src/themes/alex-party.css b/src/themes/alex-party.css index 185c7f6..02c9867 100644 --- a/src/themes/alex-party.css +++ b/src/themes/alex-party.css @@ -99,3 +99,15 @@ body { font-weight: 600; padding: 0 0.1em; } + +[data-created-from] { + max-height: 0; +} + +[data-created-active] { + transition: max-height 2s ease; +} + +[data-created-to] { + max-height: 300px; +}