Skip to content

Commit

Permalink
fix(ripple): Remove fg deactivation class when animation finishes
Browse files Browse the repository at this point in the history
- Remove foreground deactivation class after fg-opacity-out animation
  finishes such that additional repaints for the element don't retrigger
  animations.
  • Loading branch information
traviskaufman committed Jun 15, 2017
1 parent 7d0394b commit 4985b4b
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/mdc-ripple/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ export const numbers = {
PADDING: 10,
INITIAL_ORIGIN_SCALE: 0.6,
DEACTIVATION_TIMEOUT_MS: 300,
FG_DEACTIVATION_MS: 83,
};
6 changes: 5 additions & 1 deletion packages/mdc-ripple/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export default class MDCRippleFoundation extends MDCFoundation {
};
this.fgScale_ = 0;
this.activationTimer_ = 0;
this.fgDeactivationRemovalTimer_ = 0;
this.activationAnimationHasEnded_ = false;
this.activationTimerCallback_ = () => {
this.activationAnimationHasEnded_ = true;
Expand Down Expand Up @@ -203,6 +204,7 @@ export default class MDCRippleFoundation extends MDCFoundation {
this.adapter_.updateCssVariable(VAR_FG_TRANSLATE_END, translateEnd);
// Cancel any ongoing activation/deactivation animations
clearTimeout(this.activationTimer_);
clearTimeout(this.fgDeactivationRemovalTimer_);
this.rmBoundedActivationClasses_();
this.adapter_.removeClass(FG_DEACTIVATION);

Expand Down Expand Up @@ -248,8 +250,10 @@ export default class MDCRippleFoundation extends MDCFoundation {
const activationHasEnded = hasDeactivationUXRun || !isActivated;
if (activationHasEnded && this.activationAnimationHasEnded_) {
this.rmBoundedActivationClasses_();
// Note that we don't need to remove this here since it's removed on re-activation.
this.adapter_.addClass(FG_DEACTIVATION);
this.fgDeactivationRemovalTimer_ = setTimeout(() => {
this.adapter_.removeClass(FG_DEACTIVATION);
}, numbers.FG_DEACTIVATION_MS);
}
}

Expand Down
59 changes: 59 additions & 0 deletions test/unit/mdc-ripple/foundation-deactivation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ testFoundation('runs deactivation UX on touchend after touchstart', ({foundation
td.verify(adapter.removeClass(cssClasses.BG_ACTIVE_FILL), {times: 2});
td.verify(adapter.removeClass(cssClasses.FG_ACTIVATION), {times: 2});
td.verify(adapter.addClass(cssClasses.FG_DEACTIVATION));

clock.tick(numbers.FG_DEACTIVATION_MS);
td.verify(adapter.removeClass(cssClasses.FG_DEACTIVATION));

clock.uninstall();
});

Expand All @@ -64,6 +68,10 @@ testFoundation('runs deactivation UX on pointerup after pointerdown', ({foundati
td.verify(adapter.removeClass(cssClasses.BG_ACTIVE_FILL), {times: 2});
td.verify(adapter.removeClass(cssClasses.FG_ACTIVATION), {times: 2});
td.verify(adapter.addClass(cssClasses.FG_DEACTIVATION));

clock.tick(numbers.FG_DEACTIVATION_MS);
td.verify(adapter.removeClass(cssClasses.FG_DEACTIVATION));

clock.uninstall();
});

Expand All @@ -84,6 +92,10 @@ testFoundation('runs deactivation UX on mouseup after mousedown', ({foundation,
td.verify(adapter.removeClass(cssClasses.BG_ACTIVE_FILL), {times: 2});
td.verify(adapter.removeClass(cssClasses.FG_ACTIVATION), {times: 2});
td.verify(adapter.addClass(cssClasses.FG_DEACTIVATION));

clock.tick(numbers.FG_DEACTIVATION_MS);
td.verify(adapter.removeClass(cssClasses.FG_DEACTIVATION));

clock.uninstall();
});

Expand All @@ -107,6 +119,10 @@ testFoundation('runs deactivation on keyup after keydown when keydown makes surf
td.verify(adapter.removeClass(cssClasses.BG_ACTIVE_FILL), {times: 2});
td.verify(adapter.removeClass(cssClasses.FG_ACTIVATION), {times: 2});
td.verify(adapter.addClass(cssClasses.FG_DEACTIVATION));

clock.tick(numbers.FG_DEACTIVATION_MS);
td.verify(adapter.removeClass(cssClasses.FG_DEACTIVATION));

clock.uninstall();
});

Expand Down Expand Up @@ -152,6 +168,10 @@ testFoundation('runs deactivation UX on public deactivate() call', ({foundation,
td.verify(adapter.removeClass(cssClasses.BG_ACTIVE_FILL), {times: 2});
td.verify(adapter.removeClass(cssClasses.FG_ACTIVATION), {times: 2});
td.verify(adapter.addClass(cssClasses.FG_DEACTIVATION));

clock.tick(numbers.FG_DEACTIVATION_MS);
td.verify(adapter.removeClass(cssClasses.FG_DEACTIVATION));

clock.uninstall();
});

Expand All @@ -173,6 +193,10 @@ testFoundation('runs deactivation UX when activation UX timer finishes first (ac
td.verify(adapter.removeClass(cssClasses.BG_ACTIVE_FILL), {times: 2});
td.verify(adapter.removeClass(cssClasses.FG_ACTIVATION), {times: 2});
td.verify(adapter.addClass(cssClasses.FG_DEACTIVATION));

clock.tick(numbers.FG_DEACTIVATION_MS);
td.verify(adapter.removeClass(cssClasses.FG_DEACTIVATION));

clock.uninstall();
});

Expand All @@ -195,6 +219,7 @@ testFoundation('clears any pending deactivation UX timers when re-triggered', ({
mockRaf.flush();
handlers.mouseup();
mockRaf.flush();

clock.tick(DEACTIVATION_TIMEOUT_MS);

// Verify that BG_FOCUSED was removed both times
Expand All @@ -206,9 +231,43 @@ testFoundation('clears any pending deactivation UX timers when re-triggered', ({
td.verify(adapter.removeClass(cssClasses.BG_ACTIVE_FILL), {times: 3});
td.verify(adapter.removeClass(cssClasses.FG_ACTIVATION), {times: 3});
td.verify(adapter.addClass(cssClasses.FG_DEACTIVATION), {times: 1});

clock.uninstall();
});

testFoundation('clears any pending foreground deactivation class removal timers when re-triggered',
({foundation, adapter, mockRaf}) => {
const handlers = captureHandlers(adapter);
const clock = lolex.install();
foundation.init();
mockRaf.flush();

// Trigger the first interaction
handlers.mousedown({pageX: 0, pageY: 0});
mockRaf.flush();
handlers.mouseup();
mockRaf.flush();

// Tick the clock such that the deactivation UX gets run, but _not_ so the foreground deactivation removal
// timer gets run
clock.tick(DEACTIVATION_TIMEOUT_MS);

// Sanity check that the foreground deactivation class removal was only called once within
// the activation code.
td.verify(adapter.removeClass(cssClasses.FG_DEACTIVATION), {times: 1});

// Trigger another activation
handlers.mousedown({pageX: 0, pageY: 0});
mockRaf.flush();

// Tick the clock past the time when the initial foreground deactivation timer would have ran.
clock.tick(numbers.FG_DEACTIVATION_MS);

// Verify that the foreground deactivation class removal was only called twice: once within the
// original activation, and again within this subsequent activation; NOT by means of any timers firing.
td.verify(adapter.removeClass(cssClasses.FG_DEACTIVATION), {times: 2});
});

testFoundation('waits until activation UX timer runs before removing active fill classes',
({foundation, adapter, mockRaf}) => {
const handlers = captureHandlers(adapter);
Expand Down

0 comments on commit 4985b4b

Please sign in to comment.