diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index b54e38a04..9b8b1b091 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -152,7 +152,7 @@ async function visualNovelSetCharacterSprites(container, name, expression) { const path = currentSpritePath || defaultSpritePath || ''; const img = expressionImage.find('img'); - setImage(img, path); + await setImage(img, path); } expressionImage.toggleClass('hidden', noSprites); } else { @@ -163,7 +163,7 @@ async function visualNovelSetCharacterSprites(container, name, expression) { $('#visual-novel-wrapper').append(template); dragElement($(template[0])); template.toggleClass('hidden', noSprites); - setImage(template.find('img'), defaultSpritePath || ''); + await setImage(template.find('img'), defaultSpritePath || ''); const fadeInPromise = new Promise(resolve => { template.fadeIn(250, () => resolve()); }); @@ -284,7 +284,9 @@ async function setLastMessageSprite(img, avatar, labels) { } } -function setImage(img, path) { +async function setImage(img, path) { + // Cohee: If something goes wrong, uncomment this to return to the old behavior + /* img.attr('src', path); img.removeClass('default'); img.off('error'); @@ -293,6 +295,66 @@ function setImage(img, path) { $(this).off('error'); $(this).attr('src', ''); }); + */ + + return new Promise(resolve => { + const prevExpressionSrc = img.attr('src'); + const expressionClone = img.clone(); + const originalId = img.attr('id'); + + //only swap expressions when necessary + if (prevExpressionSrc !== path && !img.hasClass('expression-animating')) { + //clone expression + expressionClone.addClass('expression-clone') + //make invisible and remove id to prevent double ids + //must be made invisible to start because they share the same Z-index + expressionClone.attr('id', '').css({ opacity: 0 }); + //add new sprite path to clone src + expressionClone.attr('src', path); + //add invisible clone to html + expressionClone.appendTo(img.parent()); + + const duration = 200; + + //add animation flags to both images + //to prevent multiple expression changes happening simultaneously + img.addClass('expression-animating') + //position absolute prevent the original from jumping around during transition + img.css('position', 'absolute') + expressionClone.addClass('expression-animating') + //fade the clone in + expressionClone.css({ + opacity: 0 + }).animate({ + opacity: 1 + }, duration) + //when finshed fading in clone, fade out the original + .promise().done(function () { + img.animate({ + opacity: 0 + }, duration); + //remove old expression + img.remove(); + //replace ID so it becomes the new 'original' expression for next change + expressionClone.attr('id', originalId); + expressionClone.removeClass('expression-animating'); + resolve(); + }); + + expressionClone.removeClass('expression-clone'); + + expressionClone.removeClass('default'); + expressionClone.off('error'); + expressionClone.on('error', function () { + console.debug('Expression image error', sprite.path); + $(this).attr('src', ''); + $(this).off('error'); + resolve(); + }); + } else { + resolve(); + } + }); } function onExpressionsShowDefaultInput() { @@ -640,7 +702,7 @@ async function setExpression(character, expression, force) { } if (groupMember.name == character) { - setImage($(`.expression-holder[data-avatar="${member}"] img`), sprite.path); + await setImage($(`.expression-holder[data-avatar="${member}"] img`), sprite.path); return; } }