1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
/*!
* VisualEditor MediaWiki Initialization ViewportZoomHandler class.
*
* @copyright See AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Prevent iOS browsers from wrongly zooming in the page when the surface is focussed. (T216446)
*
* When the user places a cursor for text input anywhere on the page, iOS browsers zoom in the page
* to ensure the text size is legible and the cursor can be comfortably placed in the right place
* with a finger.
*
* There's a browser bug that, on some devices (e.g. iPhone XS, but not iPhone SE), causes this
* zoom to occur even though our text is already using the required minimum font size (16px).
* Additionally, the zoom occurs when placing the cursor in image captions, which intentionally
* use a smaller font size.
*
* In both cases the zoom is more problematic than helpful, because it causes parts of the toolbar
* to disappear outside the viewport.
*
* To prevent it, temporarily add a tag like `<meta name="viewport" content="maximum-scale=1.0">`
* to the page when the user is about to focus the editing surface. However, on iOS Chrome, this
* also prevents intentional pinch-zoom. To avoid this, immediately remove the tag again after
* focussing, or if it looks like the user is trying to zoom (used multi-touch or caused a scroll).
*
* @class
* @constructor
*/
ve.init.mw.ViewportZoomHandler = function VeInitMwViewportZoomHandler() {
// eslint-disable-next-line no-jquery/no-global-selector
this.$viewportMeta = $( 'meta[name="viewport"]' );
if ( !this.$viewportMeta.length ) {
this.$viewportMeta = $( '<meta>' ).attr( 'name', 'viewport' ).appendTo( document.head );
}
this.origViewportMetaContent = this.$viewportMeta.attr( 'content' );
this.onTouchStartHandler = this.onTouchStart.bind( this );
this.onTouchMoveHandler = this.onTouchMove.bind( this );
this.onTouchEndHandler = this.onTouchEnd.bind( this );
};
/* Methods */
/**
* Change the `<meta name="viewport">` tag to prevent automatic zooming.
*/
ve.init.mw.ViewportZoomHandler.prototype.preventZoom = function () {
this.$viewportMeta.attr( 'content', ( i, val ) => {
// Remove existing maximum-scale, if any, and add 'maximum-scale=1.0'. Don't change other values.
if ( val ) {
val = val.replace( /maximum-scale=[\d.]+(,\s*|$)/, '' );
val += ', ';
} else {
val = '';
}
return val + 'maximum-scale=1.0';
} );
};
/**
* Change the `<meta name="viewport">` tag to allow automatic zooming once again.
*/
ve.init.mw.ViewportZoomHandler.prototype.allowZoom = function () {
this.$viewportMeta.attr( 'content', this.origViewportMetaContent );
};
/**
* Start listening to events and preventing zooming.
*
* @param {ve.ui.Surface} surface
*/
ve.init.mw.ViewportZoomHandler.prototype.attach = function ( surface ) {
this.surface = surface;
this.surface.getView().$element.on( {
touchstart: this.onTouchStartHandler,
touchmove: this.onTouchMoveHandler,
touchend: this.onTouchEndHandler
} );
this.surface.getModel().connect( this, {
focus: 'onFocus'
} );
};
/**
* Stop listening to events.
*/
ve.init.mw.ViewportZoomHandler.prototype.detach = function () {
this.surface.getView().$element.off( {
touchstart: this.onTouchStartHandler,
touchmove: this.onTouchMoveHandler,
touchend: this.onTouchEndHandler
} );
this.surface.getModel().disconnect( this, {
focus: 'onFocus'
} );
this.surface = null;
};
/**
* Handle touch start events.
*
* @param {jQuery.Event} e Touch start event
*/
ve.init.mw.ViewportZoomHandler.prototype.onTouchStart = function ( e ) {
if ( e.touches.length === 1 ) {
this.wasMoved = false;
}
this.allowZoom();
};
/**
* Handle touch move events.
*
* @param {jQuery.Event} e Touch move event
*/
ve.init.mw.ViewportZoomHandler.prototype.onTouchMove = function () {
this.wasMoved = true;
};
/**
* Handle touch end events.
*
* @param {jQuery.Event} e Touch end event
*/
ve.init.mw.ViewportZoomHandler.prototype.onTouchEnd = function ( e ) {
if ( e.touches.length === 0 && !this.wasMoved ) {
// There was a single touch point, that hasn't moved, and now it's gone.
// Looks like we're going to focus the surface, so prevent automatic zoom.
this.preventZoom();
} else {
// Otherwise, allow zoom, so that the user can pinch-zoom
this.allowZoom();
}
};
/**
* Handle surface model focus events.
*/
ve.init.mw.ViewportZoomHandler.prototype.onFocus = function () {
this.allowZoom();
};
|