MediaWiki:Gadget-ZoomableMap.js
From Halopedia, the Halo wiki
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/* <pre>
* ZoomableMap v1.6
* Copyright (c) 2022 Jesús Martínez (User:Ciencia_Al_Poder)
*
* Permite hacer scroll y zoom en una imagen grande dentro de una página
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version
*/
(function( $ ) {
'use strict';
var _sensibility = 2,
_init = function() {
$( '.zoomablemap' ).each( function() {
$( this ).data( 'zoomablemap', new ZoomableMap( this ) );
} );
},
_initZoomableMap = function() {
var $controls, $zoomin, $zoomout, $contents, $toRemove = null;
$contents = this.$el.children();
// imagemap wraps everything in a div.noresize, which causes problems when dragging. Remove the extra element
if ( $contents.length === 1 && $contents.is( '.noresize' ) ) {
$toRemove = $contents;
$contents = $contents.children();
}
this.$mapCont = $( '<div class="mapcontent"></div>' ).append( $('<div class="mapinner"></div>').append( $contents ) );
if ( $toRemove ) {
$toRemove.remove();
}
$controls = $( '<div class="mapcontrols"></div>' );
$zoomin = $( '<div class="control zoomin" tabindex="0" role="button"></div>' ).text( '+' ).on( 'click', _eventDelegate( this, _zoomIn ) );
$zoomout = $( '<div class="control zoomout" tabindex="0" role="button"></div>' ).text( '-' ).on( 'click', _eventDelegate( this, _zoomOut ) );
$controls.append( $zoomin, $zoomout );
this.$el.append( this.$mapCont, $controls );
this.$mapCont.on( {
pointerdown: _eventDelegate( this, _pointerDown ),
wheel: _eventDelegate( this, _wheel )
} );
_updateScale.call( this, -1 );
this.$el.addClass( 'loadcomplete' );
},
_eventDelegate = function( thisArg, fn ) {
return function( e ) {
return fn.call( thisArg, e );
};
},
_zoomIn = function() {
_updateScale.call( this, 0.1 );
return false;
},
_zoomOut = function() {
_updateScale.call( this, -0.1 );
return false;
},
_wheel = function( e ) {
_updateScale.call( this, e.originalEvent.deltaY > 0 ? -0.1 : 0.1 );
return false;
},
_updateScale = function( delta ) {
var rect, newScale, minScale, innerDimensions;
newScale = this.scale + delta;
if ( newScale < 0.1 ) {
newScale = 0.1;
}
innerDimensions = _getInnerDimensions.call( this );
// If the element is hidden (collapsible sections) this will be an empty array
rect = this.$mapCont[0].getClientRects();
if ( !rect || rect.length === 0 ) {
return;
}
// Lower limit is to fit the contents on the current width
minScale = Math.min( rect[0].width / innerDimensions.width, rect[0].height / innerDimensions.height );
if ( newScale < minScale ) {
newScale = minScale;
}
if ( newScale > 1 ) {
newScale = 1;
}
this.scale = newScale;
this.$mapCont.find( '> .mapinner' ).eq( 0 ).css( {
transform: 'scale(' + String( newScale ) + ')',
height: String( innerDimensions.height * newScale ) + 'px'
} );
},
_pointerDown = function( e ) {
var pos = _getEventCoordinates( e, this.$mapCont[0] );
e = e.originalEvent;
// Retarget all pointer events (until pointerup)
this.$mapCont[0].setPointerCapture( e.pointerId );
if ( e.isPrimary ) {
this.dragStartPoint = pos;
this.pointerCache = [];
this.initialPointerDistance = 0;
this.$mapCont.on( {
'pointermove': _eventDelegate( this, _dragMove ),
'pointerup': _eventDelegate( this, _dragEnd )
} );
this.$mapCont.addClass( 'pointerdown' );
}
// Allow max 2 pointers
if ( this.pointerCache !== null && this.pointerCache.length < 2 ) {
this.pointerCache.push( { id: e.pointerId, x: pos.x, y: pos.y } );
// On 2nd pointer, set initial zoom position
if ( !e.isPrimary ) {
this.initialPointerDistance = _getPointDistance.call( this );
this.initialPinchZoomScale = this.scale;
}
}
return false;
},
_dragMove = function( e ) {
var newPos, newProps, newDistance;
newPos = _getEventCoordinates( e, this.$mapCont[0] );
e = e.originalEvent;
// This should never happen
if ( this.pointerCache === null ) {
return;
}
// Update point cache
for ( var i = 0; i < this.pointerCache.length; i++ ) {
if ( this.pointerCache[ i ].id == e.pointerId ) {
this.pointerCache[ i ].x = newPos.x;
this.pointerCache[ i ].y = newPos.y;
break;
}
}
if ( e.isPrimary ) {
newProps = {
scrollLeft: this.dragStartPoint.scrollLeft - newPos.x + this.dragStartPoint.x,
scrollTop: this.dragStartPoint.scrollTop - newPos.y + this.dragStartPoint.y
};
this.$mapCont.prop( newProps );
}
if ( this.pointerCache.length == 2 && this.initialPointerDistance > 0 ) {
newDistance = _getPointDistance.call( this );
_updateScale.call( this, this.initialPinchZoomScale * newDistance / this.initialPointerDistance - this.scale );
}
if ( !this.dragging ) {
this.$mapCont.addClass( 'dragging' );
this.dragging = true;
}
},
_dragEnd = function( e ) {
var newPos, propagate = false;
e = e.originalEvent;
if ( !this.pointerCache ) {
return;
}
// Remove pointer from cache
for ( var i = 0; i < this.pointerCache.length; i++ ) {
if ( this.pointerCache[ i ].id == e.pointerId ) {
this.pointerCache.splice( i, 1 );
break;
}
}
if ( !e.isPrimary ) {
return;
}
newPos = _getEventCoordinates( e, this.$mapCont[0] );
// If it hasn't scrolled, propagate the event
if (
Math.abs( newPos.x - this.dragStartPoint.x ) <= _sensibility &&
Math.abs( newPos.y - this.dragStartPoint.y ) <= _sensibility
) {
propagate = true;
}
this.$mapCont.off( 'pointermove pointerup' ).removeClass( 'pointerdown' );
this.dragStartPoint = null;
this.dragging = false;
this.$mapCont.removeClass( 'dragging' );
this.pointerCache = null;
if ( propagate ) {
this.$el.trigger( $.Event( 'mapclick', { clientX: e.clientX, clientY: e.clientY } ) );
}
},
_getPointDistance = function() {
var xx, yy;
xx = this.pointerCache[ 0 ].x - this.pointerCache[ 1 ].x;
yy = this.pointerCache[ 0 ].y - this.pointerCache[ 1 ].y;
return Math.sqrt( xx * xx + yy * yy );
},
/* Given an event and element, returns pointer coordinates and scroll position of element */
_getEventCoordinates = function( event, el ) {
return {
x: event.clientX,
y: event.clientY,
scrollLeft: el.scrollLeft,
scrollTop: el.scrollTop
};
},
_getInnerDimensions = function() {
var innerEl;
if ( !this.innerDimensions ) {
innerEl = this.$mapCont.find( '> .mapinner' )[0];
if ( innerEl.scrollWidth === 0 || innerEl.scrollHeight === 0 ) {
return null;
}
this.innerDimensions = { width: innerEl.scrollWidth, height: innerEl.scrollHeight };
}
return this.innerDimensions;
};
// Class ZoomableMap
function ZoomableMap( el ) {
this.$el = $( el );
this.$mapCont = null;
this.dragStartPoint = null;
this.dragging = false;
this.scale = 1;
this.pointerCache = null;
this.innerDimensions = null;
this.initialPointerDistance = 0;
this.initialPinchZoomScale = 0;
_initZoomableMap.call( this );
}
$(_init);
})( jQuery );