"mw.util",
"mw.plugin.*",
"mw.cookie",
- "mw.experiments"
+ "mw.experiments",
+ "mw.viewport"
]
},
{
'position' => 'top', // For $wgPreloadJavaScriptMwUtil
'targets' => [ 'desktop', 'mobile' ],
],
+ 'mediawiki.viewport' => [
+ 'scripts' => 'resources/src/mediawiki/mediawiki.viewport.js',
+ 'position' => 'top',
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
'mediawiki.checkboxtoggle' => [
'scripts' => 'resources/src/mediawiki/mediawiki.checkboxtoggle.js',
'position' => 'top',
--- /dev/null
+( function ( mw, $ ) {
+ 'use strict';
+
+ /**
+ * Utility library for viewport-related functions
+ *
+ * Notable references:
+ * - https://github.com/tuupola/jquery_lazyload
+ * - https://github.com/luis-almeida/unveil
+ *
+ * @class mw.viewport
+ * @singleton
+ */
+ var viewport = {
+
+ /**
+ * This is a private method pulled inside the module for testing purposes.
+ *
+ * @ignore
+ * @private
+ */
+ makeViewportFromWindow: function () {
+ var $window = $( window ),
+ scrollTop = $window.scrollTop(),
+ scrollLeft = $window.scrollLeft();
+
+ return {
+ top: scrollTop,
+ left: scrollLeft,
+ right: scrollLeft + $window.width(),
+ bottom: ( window.innerHeight ? window.innerHeight : $window.height() ) + scrollTop
+ };
+ },
+
+ /**
+ * Check if any part of a given element is in a given viewport
+ *
+ * @method
+ * @param {HTMLElement} el Element that's being tested
+ * @param {Object} [rectangle] Viewport to test against; structured as such:
+ *
+ * var rectangle = {
+ * top: topEdge,
+ * left: leftEdge,
+ * right: rightEdge,
+ * bottom: bottomEdge
+ * }
+ * Defaults to viewport made from `window`.
+ *
+ * @return {boolean}
+ */
+ isElementInViewport: function ( el, rectangle ) {
+ var elRect = el.getBoundingClientRect(),
+ viewport = rectangle || this.makeViewportFromWindow();
+
+ return (
+ ( viewport.bottom >= elRect.top ) &&
+ ( viewport.right >= elRect.left ) &&
+ ( viewport.top <= elRect.top + elRect.height ) &&
+ ( viewport.left <= elRect.left + elRect.width )
+ );
+ },
+
+ /**
+ * Check if an element is a given threshold away in any direction from a given viewport
+ *
+ * @method
+ * @param {HTMLElement} el Element that's being tested
+ * @param {number} [threshold] Pixel distance considered "close". Must be a positive number.
+ * Defaults to 50.
+ * @param {Object} [rectangle] Viewport to test against.
+ * Defaults to viewport made from `window`.
+ * @return {boolean}
+ */
+ isElementCloseToViewport: function ( el, threshold, rectangle ) {
+ var viewport = rectangle ? $.extend( {}, rectangle ) : this.makeViewportFromWindow();
+ threshold = threshold || 50 ;
+
+ viewport.top -= threshold;
+ viewport.left -= threshold;
+ viewport.right += threshold;
+ viewport.bottom += threshold;
+ return this.isElementInViewport( el, viewport );
+ }
+
+ };
+
+ mw.viewport = viewport;
+}( mediaWiki, jQuery ) );
'tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js',
+ 'tests/qunit/suites/resources/mediawiki/mediawiki.viewport.test.js',
'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js',
'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.category.test.js',
'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.messages.test.js',
'mediawiki.template.mustache',
'mediawiki.template',
'mediawiki.util',
+ 'mediawiki.viewport',
'mediawiki.special.recentchanges',
'mediawiki.language',
'mediawiki.cldr',
--- /dev/null
+( function ( mw, $ ) {
+
+ // Simulate square element with 20px long edges placed at (20, 20) on the page
+ var
+ DEFAULT_VIEWPORT = {
+ top: 0,
+ left: 0,
+ right: 100,
+ bottom: 100
+ };
+
+ QUnit.module( 'mediawiki.viewport', QUnit.newMwEnvironment( {
+ setup: function () {
+ this.el = $( '<div />' )
+ .appendTo( '#qunit-fixture' )
+ .width( 20 )
+ .height( 20 )
+ .offset( {
+ top: 20,
+ left: 20
+ } )
+ .get( 0 );
+ this.sandbox.stub( mw.viewport, 'makeViewportFromWindow' )
+ .returns( DEFAULT_VIEWPORT );
+ }
+ } ) );
+
+ QUnit.test( 'isElementInViewport', 6, function ( assert ) {
+ var viewport = $.extend( {}, DEFAULT_VIEWPORT );
+ assert.ok( mw.viewport.isElementInViewport( this.el, viewport ),
+ 'It should return true when the element is fully enclosed in the viewport' );
+
+ viewport.right = 20;
+ viewport.bottom = 20;
+ assert.ok( mw.viewport.isElementInViewport( this.el, viewport ),
+ 'It should return true when only the top-left of the element is within the viewport' );
+
+ viewport.top = 40;
+ viewport.left = 40;
+ viewport.right = 50;
+ viewport.bottom = 50;
+ assert.ok( mw.viewport.isElementInViewport( this.el, viewport ),
+ 'It should return true when only the bottom-right is within the viewport' );
+
+ viewport.top = 30;
+ viewport.left = 30;
+ viewport.right = 35;
+ viewport.bottom = 35;
+ assert.ok( mw.viewport.isElementInViewport( this.el, viewport ),
+ 'It should return true when the element encapsulates the viewport' );
+
+ viewport.top = 0;
+ viewport.left = 0;
+ viewport.right = 19;
+ viewport.bottom = 19;
+ assert.notOk( mw.viewport.isElementInViewport( this.el, viewport ),
+ 'It should return false when the element is not within the viewport' );
+
+ assert.ok( mw.viewport.isElementInViewport( this.el ),
+ 'It should default to the window object if no viewport is given' );
+ } );
+
+ QUnit.test( 'isElementCloseToViewport', 3, function ( assert ) {
+ var
+ viewport = {
+ top: 90,
+ left: 90,
+ right: 100,
+ bottom: 100
+ },
+ distantElement = $( '<div />' )
+ .appendTo( '#qunit-fixture' )
+ .width( 20 )
+ .height( 20 )
+ .offset( {
+ top: 220,
+ left: 20
+ } )
+ .get( 0 );
+
+ assert.ok( mw.viewport.isElementCloseToViewport( this.el, 60, viewport ),
+ 'It should return true when the element is within the given threshold away' );
+ assert.notOk( mw.viewport.isElementCloseToViewport( this.el, 20, viewport ),
+ 'It should return false when the element is further than the given threshold away' );
+ assert.notOk( mw.viewport.isElementCloseToViewport( distantElement ),
+ 'It should default to a threshold of 50px and the window\'s viewport' );
+ } );
+
+}( mediaWiki, jQuery ) );