Make the panel animation more subtle

Also turns it into a CSS animation, which allows us
to clean a lot of code

Change-Id: Id6705b63b26d8be341e77352924cad6ac4b73da1
This commit is contained in:
Gilles Dubuc 2014-02-25 14:23:08 +01:00
parent d2f7e785ea
commit c6683b7e3f
6 changed files with 145 additions and 114 deletions

View file

@ -26,14 +26,6 @@
* @constructor
*/
function MultimediaViewer() {
/**
* Whether we've fired an animation for the metadata div.
* @property {boolean}
* @private
*/
this.hasAnimatedMetadata = window.localStorage !== undefined &&
localStorage.getItem( 'mmv.hasOpenedMetadata' );
/**
* @property {mw.mmv.provider.Image}
* @private
@ -295,10 +287,7 @@
return;
}
viewer.stopListeningToScroll();
viewer.animateMetadataDivOnce()
// We need to wait until the animation is finished before we listen to scroll
.then( function() { viewer.startListeningToScroll(); } );
viewer.ui.panel.animateMetadataOnce();
} );
this.comingFromHashChange = false;
@ -502,55 +491,6 @@
);
};
/**
* Animates the metadata area when the viewer is first opened.
* @return {jQuery.Promise} an empty promise which resolves when the animation is finished
*/
MMVP.animateMetadataDivOnce = function () {
if ( !this.hasAnimatedMetadata ) {
this.hasAnimatedMetadata = true;
$.scrollTo( 20, 300 )
.scrollTo( 0, 300 );
}
return $.scrollTo.window().promise();
};
/**
* Stop listening to the page's scroll events
*/
MMVP.stopListeningToScroll = function () {
$.scrollTo().off( 'scroll.mmvp' );
};
/**
* Start listening to the page's scroll events
* Will call MMVP.scroll(), throttled so it is not triggered on every pixel.
*/
MMVP.startListeningToScroll = function () {
var viewer = this;
$.scrollTo().on( 'scroll.mmvp', $.throttle( 250, function() { viewer.scroll(); } ) );
// Trigger a check in case the user scrolled manually during the animation
viewer.scroll();
};
/**
* Receives the window's scroll events and flips the chevron if necessary.
*/
MMVP.scroll = function () {
var scrolled = !!$.scrollTo().scrollTop();
this.ui.panel.$dragIcon.toggleClass( 'pointing-down', scrolled );
if ( !this.savedHasOpenedMetadata &&
scrolled &&
window.localStorage !== undefined ) {
localStorage.setItem( 'mmv.hasOpenedMetadata', true );
this.savedHasOpenedMetadata = true;
}
};
/**
* Loads all the size-independent information needed by the lightbox (image metadata, repo
* information, file usage, uploader data).
@ -695,7 +635,6 @@
*/
MMVP.cleanupEventHandlers = function () {
$( document ).off( 'mmv-close.mmvp mmv-next.mmvp mmv-prev.mmvp mmv-resize.mmvp' );
this.stopListeningToScroll();
};
mw.mmv.MultimediaViewer = MultimediaViewer;

View file

@ -83,6 +83,50 @@
background-color: @metadata-background;
position: absolute;
min-height: (@bottom-height + 1);
opacity: 0;
&.invite {
-webkit-animation-name: invite-animation;
-webkit-animation-duration: 0.5s;
-webkit-animation-fill-mode: forwards;
animation-name: invite-animation;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
}
.mlb-post-image.invited {
opacity: 1;
}
@-webkit-keyframes invite-animation {
0% {
opacity: 0.6;
margin-top: 5px;
}
50% {
opacity: 0.9;
margin-top: -3px;
}
100% {
opacity: 1;
margin-top: 0;
}
}
@keyframes invite-animation {
0% {
opacity: 0.6;
margin-top: 5px;
}
50% {
opacity: 0.9;
margin-top: -3px;
}
100% {
opacity: 1;
margin-top: 0;
}
}
.mlb-controls {

View file

@ -31,6 +31,14 @@
mw.mmv.ui.Element.call( this, $container );
this.$controlBar = $controlBar;
/**
* Whether we've fired an animation for the metadata div.
* @property {boolean}
* @private
*/
this.hasAnimatedMetadata = window.localStorage !== undefined &&
localStorage.getItem( 'mmv.hasOpenedMetadata' );
this.initializeHeader();
this.initializeImageMetadata();
this.initializeAboutLinks();
@ -45,10 +53,16 @@
this.handleEvent( 'keydown', function ( e ) {
panel.keydown( e );
} );
$.scrollTo().on( 'scroll.mmvp', $.throttle( 250, function() {
panel.scroll();
} ) );
};
MPP.unattach = function() {
this.clearEvents();
$.scrollTo().off( 'scroll.mmvp' );
};
MPP.empty = function () {
@ -79,6 +93,9 @@
this.$progress.addClass( 'empty' );
// need to remove this to avoid animating again when reopening lightbox on same page
this.$container.removeClass( 'invite' );
this.fileReuse.empty();
};
@ -675,6 +692,18 @@
return date.format( 'LL' );
};
/**
* Animates the metadata area when the viewer is first opened.
*/
MPP.animateMetadataOnce = function () {
if ( !this.hasAnimatedMetadata ) {
this.hasAnimatedMetadata = true;
this.$container.addClass( 'invite' );
} else {
this.$container.addClass( 'invited' );
}
};
// ********************************
// ******** Action methods ********
// ********************************
@ -766,5 +795,23 @@
}
};
/**
* Receives the window's scroll events and flips the chevron if necessary.
*/
MPP.scroll = function () {
var scrolled = !!$.scrollTo().scrollTop();
this.$dragIcon.toggleClass( 'pointing-down', scrolled );
if (
!this.savedHasOpenedMetadata &&
scrolled &&
window.localStorage !== undefined
) {
localStorage.setItem( 'mmv.hasOpenedMetadata', true );
this.savedHasOpenedMetadata = true;
}
};
mw.mmv.ui.MetadataPanel = MetadataPanel;
}( mediaWiki, jQuery, OO, moment ) );

View file

@ -218,8 +218,7 @@
} );
QUnit.test( 'Metadata scrolling', 15, function ( assert ) {
var lightbox = new mw.mmv.LightboxInterface(),
viewer = new mw.mmv.MultimediaViewer(),
var ui = new mw.mmv.LightboxInterface(),
keydown = $.Event( 'keydown' ),
$document = $( document ),
scrollTopBeforeOpeningLightbox,
@ -227,9 +226,6 @@
memorizedScrollToScroll = 0,
originalJQueryScrollTo = $.scrollTo;
// Pretend that we have things hooked up
viewer.ui = lightbox;
// We need to set up a proxy on the jQuery scrollTop function
// that will let us pretend that the document really scrolled
// and that will return values as if the scroll happened
@ -259,7 +255,7 @@
if ( scrollTo !== undefined ) {
// Trigger event manually
viewer.scroll();
ui.panel.scroll();
}
return $element;
@ -267,17 +263,14 @@
// First phase of the test: up and down arrows
viewer.hasAnimatedMetadata = false;
ui.panel.hasAnimatedMetadata = false;
localStorage.removeItem( 'mmv.hasOpenedMetadata' );
// Attach lightbox to testing fixture to avoid interference with other tests.
lightbox.attach( '#qunit-fixture' );
// Pretend that we have things hooked up
viewer.currentIndex = 0;
ui.attach( '#qunit-fixture' );
assert.strictEqual( $.scrollTo().scrollTop(), 0, 'scrollTo scrollTop should be set to 0' );
assert.ok( !lightbox.panel.$dragIcon.hasClass( 'pointing-down' ),
assert.ok( !ui.panel.$dragIcon.hasClass( 'pointing-down' ),
'Chevron pointing up' );
assert.ok( !localStorage.getItem( 'mmv.hasOpenedMetadata' ),
@ -287,9 +280,9 @@
$document.trigger( keydown );
assert.strictEqual( Math.round( $.scrollTo().scrollTop() ),
lightbox.panel.$imageMetadata.outerHeight(),
ui.panel.$imageMetadata.outerHeight(),
'scrollTo scrollTop should be set to the metadata height after pressing up arrow' );
assert.ok( lightbox.panel.$dragIcon.hasClass( 'pointing-down' ),
assert.ok( ui.panel.$dragIcon.hasClass( 'pointing-down' ),
'Chevron pointing down after pressing up arrow' );
assert.ok( localStorage.getItem( 'mmv.hasOpenedMetadata' ),
'localStorage knows that the metadata has been open' );
@ -299,26 +292,26 @@
assert.strictEqual( $.scrollTo().scrollTop(), 0,
'scrollTo scrollTop should be set to 0 after pressing down arrow' );
assert.ok( !lightbox.panel.$dragIcon.hasClass( 'pointing-down' ),
assert.ok( !ui.panel.$dragIcon.hasClass( 'pointing-down' ),
'Chevron pointing up after pressing down arrow' );
lightbox.panel.$dragIcon.click();
ui.panel.$dragIcon.click();
assert.strictEqual( Math.round( $.scrollTo().scrollTop() ),
lightbox.panel.$imageMetadata.outerHeight(),
ui.panel.$imageMetadata.outerHeight(),
'scrollTo scrollTop should be set to the metadata height after clicking the chevron once' );
assert.ok( lightbox.panel.$dragIcon.hasClass( 'pointing-down' ),
assert.ok( ui.panel.$dragIcon.hasClass( 'pointing-down' ),
'Chevron pointing down after clicking the chevron once' );
lightbox.panel.$dragIcon.click();
ui.panel.$dragIcon.click();
assert.strictEqual( $.scrollTo().scrollTop(), 0,
'scrollTo scrollTop should be set to 0 after clicking the chevron twice' );
assert.ok( !lightbox.panel.$dragIcon.hasClass( 'pointing-down' ),
assert.ok( !ui.panel.$dragIcon.hasClass( 'pointing-down' ),
'Chevron pointing up after clicking the chevron twice' );
// Unattach lightbox from document
lightbox.unattach();
ui.unattach();
// Second phase of the test: scroll memory
@ -329,7 +322,7 @@
scrollTopBeforeOpeningLightbox = $.scrollTo().scrollTop();
// Attach lightbox to testing fixture to avoid interference with other tests.
lightbox.attach( '#qunit-fixture' );
ui.attach( '#qunit-fixture' );
// To make sure that the details are out of view, the lightbox is supposed to scroll to the top when open
assert.strictEqual( $.scrollTo().scrollTop(), 0, 'Page scrollTop should be set to 0' );
@ -338,13 +331,13 @@
$.scrollTo( 20, 0 );
// This extra attach() call simulates the effect of prev/next seen in bug 59861
lightbox.attach( '#qunit-fixture' );
ui.attach( '#qunit-fixture' );
// The lightbox was already open at this point, the scrollTop should be left untouched
assert.strictEqual( $.scrollTo().scrollTop(), 20, 'Page scrollTop should be set to 20' );
// Unattach lightbox from document
lightbox.unattach();
ui.unattach();
// Lightbox is supposed to restore the document scrollTop value that was set prior to opening it
assert.strictEqual( $.scrollTo().scrollTop(), scrollTopBeforeOpeningLightbox, 'document scrollTop value has been restored correctly' );

View file

@ -1,30 +1,6 @@
( function ( mw, $ ) {
QUnit.module( 'mmv', QUnit.newMwEnvironment() );
QUnit.test( 'Metadata div is only animated once', 4, function ( assert ) {
localStorage.removeItem( 'mmv.hasOpenedMetadata' );
var viewer = new mw.mmv.MultimediaViewer(),
backupAnimation = $.fn.animate,
animationRan = false;
$.fn.animate = function () {
animationRan = true;
return this;
};
viewer.animateMetadataDivOnce();
assert.strictEqual( viewer.hasAnimatedMetadata, true, 'The first call to animateMetadataDivOnce set hasAnimatedMetadata to true' );
assert.strictEqual( animationRan, true, 'The first call to animateMetadataDivOnce led to an animation' );
animationRan = false;
viewer.animateMetadataDivOnce();
assert.strictEqual( viewer.hasAnimatedMetadata, true, 'The second call to animateMetadataDivOnce did not change the value of hasAnimatedMetadata' );
assert.strictEqual( animationRan, false, 'The second call to animateMetadataDivOnce did not lead to an animation' );
$.fn.animate = backupAnimation;
} );
QUnit.test( 'eachPrealoadableLightboxIndex()', 11, function ( assert ) {
var viewer = new mw.mmv.MultimediaViewer(),
expectedIndices,
@ -139,7 +115,9 @@
setupForLoad : $.noop,
canvas : { set : $.noop,
getCurrentImageWidths : function () { return { real : 0 }; } },
panel : { setImageInfo : $.noop,
panel : {
setImageInfo : $.noop,
animateMetadataOnce : $.noop,
percent : function ( percent ) {
if ( i === 0 ) {
assert.strictEqual( percent, 0,
@ -265,7 +243,8 @@
assert.ok( false, 'Progress of the first image should not be shown' );
}
},
empty: $.noop
empty: $.noop,
animateMetadataOnce: $.noop
},
open : $.noop,
empty: $.noop };

View file

@ -21,7 +21,7 @@
QUnit.module( 'mmv.ui.metadataPanel', QUnit.newMwEnvironment() );
QUnit.test( 'The panel is emptied properly when necessary', thingsShouldBeEmptied.length + thingsShouldHaveEmptyClass.length + 1, function ( assert ) {
QUnit.test( 'The panel is emptied properly when necessary', thingsShouldBeEmptied.length + thingsShouldHaveEmptyClass.length + 2, function ( assert ) {
var i,
$qf = $( '#qunit-fixture' ),
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ) );
@ -36,7 +36,8 @@
assert.strictEqual( panel[thingsShouldHaveEmptyClass[i]].hasClass( 'empty' ), true, 'We successfully applied the empty class to the ' + thingsShouldHaveEmptyClass[i] + ' element' );
}
assert.strictEqual( panel.$dragIcon.hasClass( 'pointing-down' ), false, 'We successfully reset the chevron' );
assert.ok( !panel.$dragIcon.hasClass( 'pointing-down' ), 'We successfully reset the chevron' );
assert.ok( !panel.$container.hasClass( 'invite' ), 'We successfully reset the invite' );
} );
QUnit.test( 'Setting repository information in the UI works as expected', 3, function ( assert ) {
@ -240,4 +241,32 @@
$.fn.animate = oldAnimate;
} );
QUnit.test( 'Metadata div is only animated once', 6, function ( assert ) {
localStorage.removeItem( 'mmv.hasOpenedMetadata' );
var $qf = $( '#qunit-fixture' ),
panel = new mw.mmv.ui.MetadataPanel( $qf, $( '<div>' ).appendTo( $qf ) );
panel.animateMetadataOnce();
assert.ok( panel.hasAnimatedMetadata,
'The first call to animateMetadataOnce set hasAnimatedMetadata to true' );
assert.ok( !$qf.hasClass( 'invited' ),
'After the first call to animateMetadataOnce led to an animation' );
assert.ok( $qf.hasClass( 'invite' ),
'The first call to animateMetadataOnce led to an animation' );
$qf.removeClass( 'invite' );
panel.animateMetadataOnce();
assert.strictEqual( panel.hasAnimatedMetadata, true, 'The second call to animateMetadataOnce did not change the value of hasAnimatedMetadata' );
assert.ok( $qf.hasClass( 'invited' ),
'After the second call to animateMetadataOnce the div is shown right away' );
assert.ok( !$qf.hasClass( 'invite' ),
'The second call to animateMetadataOnce did not lead to an animation' );
$qf.removeClass( 'invited' );
} );
}( mediaWiki, jQuery ) );