Skip to content

Commit

Permalink
Fix synchronization of CSS animation
Browse files Browse the repository at this point in the history
  • Loading branch information
vodkabears committed May 11, 2015
1 parent 3d31e97 commit 460ec56
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 65 deletions.
173 changes: 122 additions & 51 deletions src/jquery.remodal.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,50 @@
'use strict';

/**
* Animationend event with vendor prefixes
* Name of the plugin
* @private
* @const
* @type {String}
*/
var ANIMATIONEND_EVENTS = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
var PLUGIN_NAME = 'remodal';

/**
* Animationstart event with vendor prefixes
* Namespace for CSS and events
* @private
* @const
* @type {String}
*/
var ANIMATIONSTART_EVENTS = 'animationstart webkitAnimationStart MSAnimationStart oAnimationStart';
var NAMESPACE = global.REMODAL_GLOBALS && global.REMODAL_GLOBALS.NAMESPACE || PLUGIN_NAME;

/**
* Name of the plugin
* Animationstart event with vendor prefixes
* @private
* @const
* @type {String}
*/
var PLUGIN_NAME = 'remodal';
var ANIMATIONSTART_EVENTS = $.map(
['animationstart', 'webkitAnimationStart', 'MSAnimationStart', 'oAnimationStart'],

function(eventName) {
return eventName + '.' + NAMESPACE;
}

).join(' ');

/**
* Namespace for CSS and events
* Animationend event with vendor prefixes
* @private
* @const
* @type {String}
*/
var NAMESPACE = global.REMODAL_GLOBALS && global.REMODAL_GLOBALS.NAMESPACE || PLUGIN_NAME;
var ANIMATIONEND_EVENTS = $.map(
['animationend', 'webkitAnimationEnd', 'MSAnimationEnd', 'oAnimationEnd'],

function(eventName) {
return eventName + '.' + NAMESPACE;
}

).join(' ');

/**
* Default settings
Expand Down Expand Up @@ -83,6 +97,22 @@
CANCELLATION: 'cancellation'
};

/**
* Is animation supported?
* @private
* @const
* @type {Boolean}
*/
var IS_ANIMATION = (function() {
var style = document.createElement('div').style;

return style.animationName !== undefined ||
style.WebkitAnimationName !== undefined ||
style.MozAnimationName !== undefined ||
style.msAnimationName !== undefined ||
style.OAnimationName !== undefined;
})();

/**
* Current modal
* @private
Expand All @@ -101,9 +131,20 @@
* Returns an animation duration
* @private
* @param {jQuery} $elem
* @return {Number}
* @returns {Number}
*/
function getAnimationDuration($elem) {
if (
IS_ANIMATION &&
$elem.css('animation-name') === 'none' &&
$elem.css('-webkit-animation-name') === 'none' &&
$elem.css('-moz-animation-name') === 'none' &&
$elem.css('-o-animation-name') === 'none' &&
$elem.css('-ms-animation-name') === 'none'
) {
return 0;
}

var duration = $elem.css('animation-duration') ||
$elem.css('-webkit-animation-duration') ||
$elem.css('-moz-animation-duration') ||
Expand Down Expand Up @@ -136,7 +177,7 @@

// The 'duration' size is the same as the 'delay' size
for (i = 0, len = duration.length, max = Number.NEGATIVE_INFINITY; i < len; i++) {
num = parseFloat(duration[i]) * parseInt(iterationCount[i]) + parseFloat(delay[i]);
num = parseFloat(duration[i]) * parseInt(iterationCount[i], 10) + parseFloat(delay[i]);

if (num > max) {
max = num;
Expand All @@ -149,7 +190,7 @@
/**
* Returns a scrollbar width
* @private
* @return {Number}
* @returns {Number}
*/
function getScrollbarWidth() {
if ($(document.body).height() <= $(window).height()) {
Expand Down Expand Up @@ -265,42 +306,60 @@
}

/**
* Calls a function after the animation
* @private
* @param {Function} fn
* Synchronizes with the animation
* @param {Function} doBeforeAnimation
* @param {Function} doAfterAnimation
* @param {Remodal} instance
*/
function callAfterAnimation(fn, instance) {
function syncWithAnimation(doBeforeAnimation, doAfterAnimation, instance) {
var runningAnimationsCount = 0;

// Element with the longest animation
var $observableElement = getAnimationDuration(instance.$modal) > getAnimationDuration(instance.$overlay) ?
instance.$modal : instance.$overlay;
var isAnimationStarted = false;
var handleAnimationStart = function(e) {
if (e.target !== this) {
return;
}

$observableElement.one(ANIMATIONSTART_EVENTS, function() {
isAnimationStarted = true;
runningAnimationsCount++;
};

$observableElement.one(ANIMATIONEND_EVENTS, function(e) {
var handleAnimationEnd = function(e) {
if (e.target !== this) {
return;
}

// Ignore child nodes
if (e.target !== this) {
return;
}
if (--runningAnimationsCount === 0) {

fn();
});
});
// Remove event listeners
$.each(['$bg', '$overlay', '$modal'], function(index, elemName) {
instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
});

// Check after some time if the animation is started
setTimeout(function() {
if (isAnimationStarted) {
return;
doAfterAnimation();
}
};

// If the 'animationstart' event wasn't triggered
$observableElement.off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
fn();
}, 25);
$.each(['$bg', '$overlay', '$modal'], function(index, elemName) {
instance[elemName]
.on(ANIMATIONSTART_EVENTS, handleAnimationStart)
.on(ANIMATIONEND_EVENTS, handleAnimationEnd);
});

doBeforeAnimation();

// If the animation is not supported by a browser or its duration is 0
if (
getAnimationDuration(instance.$bg) === 0 &&
getAnimationDuration(instance.$overlay) === 0 &&
getAnimationDuration(instance.$modal) === 0
) {

// Remove event listeners
$.each(['$bg', '$overlay', '$modal'], function(index, elemName) {
instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
});

doAfterAnimation();
}
}

/**
Expand All @@ -313,8 +372,10 @@
return;
}

instance.$overlay.off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
instance.$modal.off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
$.each(['$bg', '$overlay', '$modal'], function(index, elemName) {
instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
});

instance.$bg.removeClass(instance.settings.modifier);
instance.$overlay.removeClass(instance.settings.modifier).hide();
instance.$wrapper.hide();
Expand Down Expand Up @@ -500,11 +561,16 @@
remodal.$overlay.addClass(remodal.settings.modifier).show();
remodal.$wrapper.show().scrollTop(0);

callAfterAnimation(function() {
setState(remodal, STATES.OPENED);
}, remodal);
syncWithAnimation(
function() {
setState(remodal, STATES.OPENING);
},

function() {
setState(remodal, STATES.OPENED);
},

setState(remodal, STATES.OPENING);
remodal);
};

/**
Expand All @@ -528,16 +594,21 @@
$(window).scrollTop(scrollTop);
}

callAfterAnimation(function() {
remodal.$bg.removeClass(remodal.settings.modifier);
remodal.$overlay.removeClass(remodal.settings.modifier).hide();
remodal.$wrapper.hide();
unlockScreen();
syncWithAnimation(
function() {
setState(remodal, STATES.CLOSING, false, reason);
},

function() {
remodal.$bg.removeClass(remodal.settings.modifier);
remodal.$overlay.removeClass(remodal.settings.modifier).hide();
remodal.$wrapper.hide();
unlockScreen();

setState(remodal, STATES.CLOSED, false, reason);
}, remodal);
setState(remodal, STATES.CLOSED, false, reason);
},

setState(remodal, STATES.CLOSING, false, reason);
remodal);
};

/**
Expand Down
14 changes: 7 additions & 7 deletions test/remodal.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
<script src="../src/jquery.remodal.js"></script>
<script src="remodal_test.js"></script>
<style>
.remodal-overlay.remodal-is-opening,
.remodal-overlay.remodal-is-closing,
.remodal.remodal-is-opening,
.remodal.remodal-is-closing,
.remodal-bg.remodal-is-opening,
.remodal-bg.remodal-is-closing {
.remodal-overlay.without-animation.remodal-is-opening,
.remodal-overlay.without-animation.remodal-is-closing,
.remodal.without-animation.remodal-is-opening,
.remodal.without-animation.remodal-is-closing,
.remodal-bg.without-animation.remodal-is-opening,
.remodal-bg.without-animation.remodal-is-closing {
animation: none;
}
</style>
Expand All @@ -43,7 +43,7 @@
<div class="remodal" data-remodal-id="modal2"
data-remodal-options="hashTracking: false,
closeOnConfirm:false,closeOnCancel: false, closeOnEscape: false , closeOnAnyClick: false,
modifier : with-test-class1 with-test-class2">
modifier : without-animation with-test-class">

<a data-remodal-action="close" class="remodal-close"></a>
<a data-remodal-action="cancel" class="remodal-cancel" href="#">Cancel</a>
Expand Down
14 changes: 7 additions & 7 deletions test/remodal_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@
closeOnCancel: false,
closeOnEscape: false,
closeOnAnyClick: false,
modifier: 'with-test-class1 with-test-class2'
modifier: 'without-animation with-test-class'
}, 'options are correctly parsed');
});

Expand Down Expand Up @@ -398,17 +398,17 @@
var remodal = $modal.remodal();

$document.one('opened', '[data-remodal-id=modal2]', function() {
assert.ok($bg.hasClass('with-test-class1 with-test-class2'), 'bg has the modifier');
assert.ok($overlay.hasClass('with-test-class1 with-test-class2'), 'overlay has the modifier');
assert.ok($modal.hasClass('with-test-class1 with-test-class2'), 'modal has the modifier');
assert.ok($bg.hasClass('without-animation with-test-class'), 'bg has the modifier');
assert.ok($overlay.hasClass('without-animation with-test-class'), 'overlay has the modifier');
assert.ok($modal.hasClass('without-animation with-test-class'), 'modal has the modifier');

remodal.close();
});

$document.one('closed', '[data-remodal-id=modal2]', function() {
assert.notOk($bg.hasClass('with-test-class1 with-test-class2'), 'bg hasn\'t the modifier');
assert.notOk($overlay.hasClass('with-test-class1 with-test-class2'), 'overlay has\'t the modifier');
assert.ok($modal.hasClass('with-test-class1 with-test-class2'), 'modal still has the modifier');
assert.notOk($bg.hasClass('without-animation with-test-class'), 'bg hasn\'t the modifier');
assert.notOk($overlay.hasClass('without-animation with-test-class'), 'overlay has\'t the modifier');
assert.ok($modal.hasClass('without-animation with-test-class'), 'modal still has the modifier');

QUnit.start();
});
Expand Down

0 comments on commit 460ec56

Please sign in to comment.