jenkins-bot has submitted this change and it was merged.
Change subject: Improve fixed header in overlays on iOS
......................................................................
Improve fixed header in overlays on iOS
Fake fixed header by wrapping overlay content in a div and scrolling
within that div instead (on iOS only). Modify content div's height
when virtual keyboard is open/closed or window resized.
This also improves scrolling in notifications overlay on iOS.
Tested on iOS 7, recent Chrome for Android and Android Browser 2.3.
Bug: 67390
Change-Id: I05f11246ca90a1ce3f741fd93194a827528cd597
---
M includes/Resources.php
M javascripts/common/Overlay.js
M javascripts/common/application.js
M javascripts/modules/editor/VisualEditorOverlay.js
M javascripts/modules/notifications/NotificationsOverlay.js
M javascripts/modules/search/SearchOverlay.js
M less/common/Overlay.less
M less/modules/NotificationsOverlay.less
M less/modules/search/SearchOverlay.less
M templates/Overlay.html
M templates/modules/editor/EditorOverlayBase.html
R templates/modules/notifications/NotificationsOverlayContent.html
A templates/modules/notifications/NotificationsOverlayFooter.html
M templates/modules/search/SearchOverlay.html
14 files changed, 183 insertions(+), 112 deletions(-)
Approvals:
Kaldari: Looks good to me, approved
jenkins-bot: Verified
diff --git a/includes/Resources.php b/includes/Resources.php
index c0b463f..f4b4a8f 100644
--- a/includes/Resources.php
+++ b/includes/Resources.php
@@ -899,7 +899,8 @@
'less/modules/NotificationsOverlay.less',
),
'templates' => array(
- 'modules/notifications/NotificationsOverlay',
+ 'modules/notifications/NotificationsOverlayContent',
+ 'modules/notifications/NotificationsOverlayFooter',
),
'messages' => array(
// defined in Echo
diff --git a/javascripts/common/Overlay.js b/javascripts/common/Overlay.js
index 20873d2..052c962 100644
--- a/javascripts/common/Overlay.js
+++ b/javascripts/common/Overlay.js
@@ -3,8 +3,7 @@
var
View = M.require( 'View' ),
- // how long it takes for iOS keyboard to open/close
- IOS_KEYBOARD_DELAY = 300,
+ $window = $( window ),
Overlay;
/**
@@ -45,7 +44,11 @@
closeOnContentTap: false,
postRender: function( options ) {
- var self = this;
+ var
+ self = this,
+ $overlayContent = this.$overlayContent =
this.$( '.overlay-content' ),
+ startY;
+
// Truncate any text inside in the overlay header.
this.$( '.overlay-header h2 span' ).addClass(
'truncated-text' );
// FIXME change when micro.tap.js in stable
@@ -64,7 +67,31 @@
ev.stopPropagation();
} );
- this._fixIosHeader( 'textarea, input' );
+ if ( M.isIos ) {
+ $overlayContent
+ .on( 'touchstart', function( ev ) {
+ startY =
ev.originalEvent.touches[0].pageY;
+ } )
+ .on( 'touchmove', function( ev ) {
+ var
+ y =
ev.originalEvent.touches[0].pageY,
+ contentLenght =
$overlayContent.prop( 'scrollHeight' ) - $overlayContent.outerHeight();
+
+ ev.stopPropagation();
+ // prevent scrolling and bouncing
outside of .overlay-content
+ if (
+ ( $overlayContent.scrollTop()
=== 0 && startY < y ) ||
+ ( $overlayContent.scrollTop()
=== contentLenght && startY > y )
+ ) {
+ ev.preventDefault();
+ }
+ } );
+
+ // wait for things to render before doing any
calculations
+ setTimeout( function() {
+ self._fixIosHeader( 'textarea, input' );
+ }, 0 );
+ }
},
// FIXME: remove when OverlayManager used everywhere
@@ -82,6 +109,8 @@
* @method
*/
show: function() {
+ var self = this;
+
// FIXME: remove when OverlayManager used everywhere
if ( this.closeOnBack ) {
this._hideOnRoute();
@@ -98,6 +127,17 @@
if ( this.closeOnContentTap ) {
$( '#mw-mf-page-center' ).one( M.tapEvent(
'click' ), $.proxy( this, 'hide' ) );
+ }
+
+ // prevent scrolling and bouncing outside of
.overlay-content
+ if ( M.isIos ) {
+ $window
+ .on( 'touchmove.ios', function( ev ) {
+ ev.preventDefault();
+ } )
+ .on( 'resize.ios', function() {
+ self._resizeContent(
$window.height() );
+ } );
}
this.$el.addClass( 'visible' );
@@ -129,43 +169,53 @@
self.$el.detach();
}, 1000 );
+ if ( M.isIos ) {
+ $window.off( '.ios' );
+ }
+
this.emit( 'hide' );
return true;
},
+ _resizeContent: function( windowHeight ) {
+ this.$overlayContent.height( windowHeight - this.$(
'.overlay-header-container' ).outerHeight() - this.$(
'.overlay-footer-container' ).outerHeight() );
+ },
+
+ /**
+ * Resize .overlay-content to occupy 100% of screen space when
virtual
+ * keyboard is shown/hidden on iOS.
+ *
+ * This function supplements the custom styles for Overlays on
iOS.
+ * On iOS we scroll the content inside of .overlay-content div
instead
+ * of scrolling the whole page to achieve a consistent sticky
header
+ * effect (position: fixed doesn't work on iOS when the virtual
keyboard
+ * is open).
+ *
+ * @method
+ * @param {string} el CSS selector for elements that may
trigger virtual
+ * keyboard (usually inputs, textareas, contenteditables).
+ */
_fixIosHeader: function( el ) {
- var $header = this.$( '.overlay-header-container' ),
$window = $( window );
- // This is used to avoid position: fixed weirdness in
mobile Safari when
- // the keyboard is visible
- if ( ( /ipad|iphone/i ).test( navigator.userAgent ) ) {
- this.$( el ).
- on( 'focus', function() {
- $header.removeClass(
'position-fixed' );
- // don't show fixed header on
iPhone, it causes bug 62120
- // (also, there is a Done
button on the keyboard anyway)
- if ( M.isWideScreen() ) {
- // wait for the
keyboard opening animation to finish
- setTimeout( function() {
- $header.css(
'top', $window.scrollTop() );
- }, IOS_KEYBOARD_DELAY );
- $window.on(
'scroll.fixIosHeader', function() {
- $header.css(
'top', $window.scrollTop() ).addClass( 'visible' );
- } );
- $window.on(
'touchmove.fixIosHeader', function() {
- // don't hide
header if we're at the top
- if (
$window.scrollTop() > 0 ) {
-
$header.removeClass( 'visible' );
- }
- } );
- }
- } ).
- on( 'blur', function() {
- // wait for the keyboard
closing animation to finish
+ var self = this;
+
+ if ( M.isIos ) {
+ this._resizeContent( $( window ).height() );
+ $( el )
+ .on( 'focus', function() {
setTimeout( function() {
- $header.css( 'top', 0
).addClass( 'position-fixed visible' );
- }, IOS_KEYBOARD_DELAY );
- $window.off( '.fixIosHeader' );
+ var keyboardHeight;
+
+ // detect virtual
keyboard height
+ $window.scrollTop( 999
);
+ keyboardHeight =
$window.scrollTop();
+ $window.scrollTop( 0 );
+
+ self._resizeContent(
$window.height() - keyboardHeight );
+ }, 0 );
+ } )
+ .on( 'blur', function() {
+ self._resizeContent(
$window.height() );
} );
}
},
diff --git a/javascripts/common/application.js
b/javascripts/common/application.js
index 353d260..6f023e2 100644
--- a/javascripts/common/application.js
+++ b/javascripts/common/application.js
@@ -16,10 +16,10 @@
currentPage,
inWideScreenMode = false,
ua = window.navigator.userAgent,
- isAppleDevice = /ipad|iphone/i.test( ua ),
- isIPhone4 = isAppleDevice && /OS 4_/.test( ua ),
- isOldIPhone = isAppleDevice && /OS [4]_[0-2]|OS [3]_/.test( ua
),
- isIPhone5 = isAppleDevice && /OS 5_/.test( ua ),
+ isIos = /ipad|iphone/i.test( ua ),
+ isIPhone4 = isIos && /OS 4_/.test( ua ),
+ isOldIPhone = isIos && /OS [4]_[0-2]|OS [3]_/.test( ua ),
+ isIPhone5 = isIos && /OS 5_/.test( ua ),
isAndroid2 = /Android 2/.test( ua );
// See if local storage is supported
@@ -140,6 +140,10 @@
$viewport = $( '#mw-mf-viewport' );
$( '<div id="notifications">' ).appendTo( $viewport );
+
+ if ( isIos ) {
+ $body.addClass( 'ios' );
+ }
if ( !supportsPositionFixed( navigator.userAgent ) ) {
$doc.addClass( 'no-position-fixed' );
@@ -429,6 +433,7 @@
reloadPage: reloadPage,
supportsGeoLocation: supportsGeoLocation,
supportsPositionFixed: supportsPositionFixed,
+ isIos: isIos,
prettyEncodeTitle: prettyEncodeTitle,
query: deParam( qs ),
unlockViewport: unlockViewport,
diff --git a/javascripts/modules/editor/VisualEditorOverlay.js
b/javascripts/modules/editor/VisualEditorOverlay.js
index cf1e83a..79196cf 100644
--- a/javascripts/modules/editor/VisualEditorOverlay.js
+++ b/javascripts/modules/editor/VisualEditorOverlay.js
@@ -149,17 +149,20 @@
this.destroyTarget();
},
onSurfaceReady: function () {
+ var self = this;
this.clearSpinner();
this.$( '.surface' ).show();
this.target.surface.getModel().getDocument().connect(
this, { 'transact': 'onTransact' } );
this.target.surface.$element.addClass( 'content' );
- // for some reason the first time contenteditables are
focused, focus
- // event doesn't fire if we don't blur them first
- this.$( '[contenteditable]' ).blur();
// we have to do it here because contenteditable
elements still do not
// exist when postRender is executed
this._fixIosHeader( '[contenteditable]' );
+ // for some reason the first time contenteditables are
focused, focus
+ // event doesn't fire if we don't do this (at least on
iOS Safari 7)
+ setTimeout( function() {
+ self.$( '[contenteditable]' ).focus();
+ }, 0 );
},
onTransact: function () {
this.hasChanged = true;
diff --git a/javascripts/modules/notifications/NotificationsOverlay.js
b/javascripts/modules/notifications/NotificationsOverlay.js
index 8d19a98..e643be4 100644
--- a/javascripts/modules/notifications/NotificationsOverlay.js
+++ b/javascripts/modules/notifications/NotificationsOverlay.js
@@ -11,7 +11,8 @@
active: false,
className: 'overlay notifications-overlay
navigation-drawer',
templatePartials: {
- content: M.template.get(
'modules/notifications/NotificationsOverlay' )
+ content: M.template.get(
'modules/notifications/NotificationsOverlayContent' ),
+ footer: M.template.get(
'modules/notifications/NotificationsOverlayFooter' )
},
defaults: {
heading: mw.msg( 'notifications' ),
diff --git a/javascripts/modules/search/SearchOverlay.js
b/javascripts/modules/search/SearchOverlay.js
index b843354..bda3129 100644
--- a/javascripts/modules/search/SearchOverlay.js
+++ b/javascripts/modules/search/SearchOverlay.js
@@ -78,12 +78,13 @@
} );
// tapping on background only should hide the overlay
- this.$el.on( M.tapEvent( 'click' ), function() {
- window.history.back();
- } );
- this.$( '> div' ).on( M.tapEvent( 'click' ), function(
ev ) {
- ev.stopPropagation();
- } );
+ this.$overlayContent
+ .on( M.tapEvent( 'click' ), function() {
+ window.history.back();
+ } )
+ .find( '> div' ).on( M.tapEvent( 'click' ),
function( ev ) {
+ ev.stopPropagation();
+ } );
// hide the keyboard when scrolling starts (avoid weird
situation when
// user taps on an item, the keyboard hides and wrong
item is clicked)
diff --git a/less/common/Overlay.less b/less/common/Overlay.less
index d911b4a..c907d84 100644
--- a/less/common/Overlay.less
+++ b/less/common/Overlay.less
@@ -242,24 +242,12 @@
}
}
-.overlay-header-container {
+.overlay-header-container,
+.overlay-footer-container {
width: 100%;
background: #fff;
- // needed for _fixIosHeader method to work properly
- position: absolute;
- top: 0;
- // make header reappearing less abrupt when scrolling on iOS with open
- // keyboard
- opacity: 0;
- // prevent .page-list overlaping the header when scrolling
- // needed for _fixIosHeader method to work properly
+ // prevent .page-list or VE surface overlaping the header when scrolling
z-index: 5;
-
- &.visible {
- opacity: 1;
- // transform for elements that slide down (VE second toolbar)
- .transition-transform( .3s, opacity .3s; );
- }
&.position-fixed {
// both top and left required for Android 2 for the element to
be visible
@@ -269,6 +257,14 @@
// element to be visible, right: 0 doesn't work)
right: 0;
}
+}
+
+.overlay-header-container {
+ top: 0;
+}
+
+.overlay-footer-container {
+ bottom: 0;
}
// Bottom Overlays
@@ -305,6 +301,21 @@
}
}
+.ios {
+ .overlay-header-container {
+ position: absolute !important;
+ }
+
+ .overlay-footer-container {
+ position: absolute !important;
+ }
+
+ .overlay-content {
+ overflow-y: scroll; // has to be scroll, not auto
+ -webkit-overflow-scrolling: touch;
+ }
+}
+
@media all and (min-width: @wgMFDeviceWidthTablet) {
.overlay {
diff --git a/less/modules/NotificationsOverlay.less
b/less/modules/NotificationsOverlay.less
index 8701e32..6ca5e98 100644
--- a/less/modules/NotificationsOverlay.less
+++ b/less/modules/NotificationsOverlay.less
@@ -11,11 +11,15 @@
position: static !important;
}
- .mw-mf-notifications {
- padding-bottom: 0;
- bottom: @headerHeight;
+ .overlay-content {
+ // this is needed not only on iOS, that's why we repeat it here
even though
+ // it's in Overlay.less too
+ overflow-y: scroll; // has to be scroll, not auto
+ -webkit-overflow-scrolling: touch;
+ position: absolute;
top: @headerHeight;
- overflow-y: auto;
+ bottom: @headerHeight;
+ width: 100%;
margin-top: 1px;
}
@@ -65,14 +69,6 @@
border-top: 1px solid @grayLight;
background-color: #FFF;
text-align: center;
- bottom: 0;
- }
-
- .mw-mf-notifications,
- .notifications-archive-link {
- position: absolute;
- right: 0;
- left: 0;
}
.mw-echo-title {
diff --git a/less/modules/search/SearchOverlay.less
b/less/modules/search/SearchOverlay.less
index 3b7ce19..6f4ab7c 100644
--- a/less/modules/search/SearchOverlay.less
+++ b/less/modules/search/SearchOverlay.less
@@ -29,6 +29,8 @@
}
.results {
+ box-shadow: 0 3px 3px 0 rgba(117, 117, 117, .3);
+
li {
padding-left: @headerHeight + @headerTitlePaddingH;
}
@@ -47,9 +49,5 @@
h2 {
font: inherit;
}
- }
-
- .search-overlay-contents {
- box-shadow: 0 1px 5px 0 rgba(117, 117, 117, .5);
}
}
diff --git a/templates/Overlay.html b/templates/Overlay.html
index 083f62c..9e9ff0e 100644
--- a/templates/Overlay.html
+++ b/templates/Overlay.html
@@ -1,4 +1,4 @@
-<div class="overlay-header-container visible {{#fixedHeader}}
position-fixed{{/fixedHeader}}">
+<div class="overlay-header-container {{#fixedHeader}}
position-fixed{{/fixedHeader}}">
<div class="overlay-header">
<ul>
<li><button class="cancel icon
icon-cancel">{{closeMsg}}</button></li>
@@ -15,4 +15,9 @@
{{/headerButtons}}
</div>
</div>
-{{>content}}
+<div class="overlay-content">
+ {{>content}}
+</div>
+<div class="overlay-footer-container position-fixed">
+ {{>footer}}
+</div>
diff --git a/templates/modules/editor/EditorOverlayBase.html
b/templates/modules/editor/EditorOverlayBase.html
index fa87312..23cb19f 100644
--- a/templates/modules/editor/EditorOverlayBase.html
+++ b/templates/modules/editor/EditorOverlayBase.html
@@ -1,4 +1,4 @@
-<div class="overlay-header-container visible position-fixed">
+<div class="overlay-header-container position-fixed">
{{>header}}
<div class="overlay-header save-header hideable hidden">
<ul>
@@ -22,20 +22,20 @@
</div>
</div>
-<div class="panels">
- <div class="save-panel panel hideable hidden">
- <p class="summary-request">{{{summaryRequestMsg}}}</p>
- <textarea rows="2" class="summary"
placeholder="{{summaryMsg}}"></textarea>
- <p class="license">{{{licenseMsg}}}</p>
- </div>
- <div class="captcha-panel panel hideable hidden">
- <div class="inputs-box">
- <img src="">
- <input class="captcha-word"
placeholder="{{captchaMsg}}" />
+<div class="overlay-content">
+ <div class="panels">
+ <div class="save-panel panel hideable hidden">
+ <p class="summary-request">{{{summaryRequestMsg}}}</p>
+ <textarea rows="2" class="summary"
placeholder="{{summaryMsg}}"></textarea>
+ <p class="license">{{{licenseMsg}}}</p>
+ </div>
+ <div class="captcha-panel panel hideable hidden">
+ <div class="inputs-box">
+ <img src="">
+ <input class="captcha-word"
placeholder="{{captchaMsg}}" />
+ </div>
</div>
</div>
+ <div class="spinner loading"></div>
+ {{>content}}
</div>
-
-<div class="spinner loading"></div>
-
-{{>content}}
diff --git a/templates/modules/notifications/NotificationsOverlay.html
b/templates/modules/notifications/NotificationsOverlayContent.html
similarity index 74%
rename from templates/modules/notifications/NotificationsOverlay.html
rename to templates/modules/notifications/NotificationsOverlayContent.html
index 6a5ae7f..6c2fd54 100644
--- a/templates/modules/notifications/NotificationsOverlay.html
+++ b/templates/modules/notifications/NotificationsOverlayContent.html
@@ -7,4 +7,3 @@
</li>
{{/notifications}}
</ul>
-<a href="{{archiveLink}}"
class="notifications-archive-link">{{archiveLinkMsg}}</a>
diff --git a/templates/modules/notifications/NotificationsOverlayFooter.html
b/templates/modules/notifications/NotificationsOverlayFooter.html
new file mode 100644
index 0000000..25c6d42
--- /dev/null
+++ b/templates/modules/notifications/NotificationsOverlayFooter.html
@@ -0,0 +1 @@
+<a href="{{archiveLink}}"
class="notifications-archive-link">{{archiveLinkMsg}}</a>
diff --git a/templates/modules/search/SearchOverlay.html
b/templates/modules/search/SearchOverlay.html
index 607feb6..cb3710b 100644
--- a/templates/modules/search/SearchOverlay.html
+++ b/templates/modules/search/SearchOverlay.html
@@ -1,19 +1,19 @@
-<div class="search-overlay-contents">
- <div class="overlay-header-container visible position-fixed">
- <div class="overlay-header">
- <ul>
- <li><button class="cancel icon
icon-cancel">{{closeMsg}}</button></li>
- </ul>
- <div class="overlay-search">
- <form method="get" action="{{action}}">
- <input class="search" type="search"
name="search" autocomplete="off" placeholder="{{placeholderMsg}}">
- </form>
- </div>
- <ul>
- <li><button class="clear icon
icon-clear">{{clearMsg}}</button></li>
- </ul>
+<div class="overlay-header-container position-fixed">
+ <div class="overlay-header">
+ <ul>
+ <li><button class="cancel icon
icon-cancel">{{closeMsg}}</button></li>
+ </ul>
+ <div class="overlay-search">
+ <form method="get" action="{{action}}">
+ <input class="search" type="search"
name="search" autocomplete="off" placeholder="{{placeholderMsg}}">
+ </form>
</div>
+ <ul>
+ <li><button class="clear icon
icon-clear">{{clearMsg}}</button></li>
+ </ul>
</div>
+</div>
+<div class="overlay-content">
<div class="search-content overlay-header">
<ul>
<li><button
class="icon">{{searchContentMsg}}</button></li>
--
To view, visit https://gerrit.wikimedia.org/r/145185
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I05f11246ca90a1ce3f741fd93194a827528cd597
Gerrit-PatchSet: 14
Gerrit-Project: mediawiki/extensions/MobileFrontend
Gerrit-Branch: master
Gerrit-Owner: JGonera <[email protected]>
Gerrit-Reviewer: Awjrichards <[email protected]>
Gerrit-Reviewer: JGonera <[email protected]>
Gerrit-Reviewer: Kaldari <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits