2015-12-23 23:09:12 +01:00

371 lines
10 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*global define*/
// most of this was shamelessly taken from @jakearchibald's svgomg ui:
// https://github.com/jakearchibald/svgomg/blob/master/src/js/page/ui/pan-zoom.js
define(
[ 'util/css', 'util/dom', 'util/addpublishers' ],
function ( cssHelper, domHelper, addPublishers ) {
function getXY ( obj ) {
return {
x: obj.pageX,
y: obj.pageY
};
}
function touchDistance ( touch1, touch2 ) {
var x = Math.abs( touch2.x - touch1.x );
var y = Math.abs( touch2.y - touch1.y );
return Math.sqrt( x * x + y * y );
}
function getMidpoint ( point1, point2 ) {
return {
x: ( point1.x + point2.x ) / 2,
y: ( point1.y + point2.y ) / 2
};
}
function getPoints ( event ) {
if ( event.touches ) {
return Array.prototype.map.call( event.touches, getXY );
} else {
return [ getXY( event ) ];
}
}
function PanZoom ( containerEl, targetEl, opts ) {
if ( ! ( this instanceof PanZoom ) ) {
return new PanZoom ( targetEl, containerEl, opts );
}
var self = this;
var canZoomWithPonter = true;
var publishers = addPublishers( self, 'scale' );
opts = opts || { }
var shouldCaptureFn = opts.shouldCaptureFn || function ( el ) { return true; };
var limitToContainer = typeof opts.limitToContainer === 'boolean' ? opts.limitToContainer : true;
var limitPadding = opts.limitPadding || 50;
var matrix = cssHelper.getCSSMatrix( targetEl );
var transform = cssHelper.cssMatrixToTransformObj( matrix );
var x = transform.translateX || 0;
var y = transform.translateY || 0;
var scale = 1;
var active = 0;
var lastPoints = [ ];
var containerBounds;
var updateAnimationFrameId = NaN;
var scaleAnimationFrameId = NaN;
var centerAnimationFrameId = NaN;
var resizeTimeoutId = NaN;
var targetElSize = { width: 100, height: 100 };
var containerElSize = { width: 100, height: 100 };
var centerScale = 1;
containerEl.addEventListener( 'mousedown', pointerPressed );
containerEl.addEventListener( 'touchstart', pointerPressed );
containerEl.addEventListener( 'wheel', wheelTurned );
window.addEventListener( 'resize', resized );
targetEl.style.WebkitTransformOrigin = targetEl.style.transformOrigin = '0 0';
updateContainerBounds();
function reset () {
// x = transform.translateX || 0;
// y = transform.translateY || 0;
updateValues ( transform.translateX || 0, transform.translateY || 0, 1 );
}
function updateValues ( newX, newY, newScale, targetBounds ) {
cancelAnimationFrame( updateAnimationFrameId );
if ( limitToContainer && targetEl !== containerEl && containerBounds ) {
targetBounds = targetBounds || targetEl.getBoundingClientRect();
if ( newX !== x || newY !== y || newScale !== scale ) {
var scaleDelta = 1 / ( scale / newScale );
if ( newX + targetBounds.width * scaleDelta < limitPadding ) {
newX = limitPadding - targetBounds.width * scaleDelta;
}
if ( newX > containerBounds.width - limitPadding ) {
newX = containerBounds.width - limitPadding;
}
if ( newY + targetBounds.height * scaleDelta < limitPadding ) {
newY = limitPadding - targetBounds.height * scaleDelta;
}
if ( newY > containerBounds.height - limitPadding ) {
newY = containerBounds.height - limitPadding;
}
}
}
newScale = Math.min( 5, Math.max( 0.01, newScale ) );
if ( scale === newScale && newScale === 5 ) {
return;
}
x = newX;
y = newY;
scale = newScale
publishers.scale.dispatch( scale );
updateAnimationFrameId = requestAnimationFrame( update );
}
function update () {
targetEl.style.WebkitTransform = targetEl.style.transform = 'translate3d(' + x + 'px, ' + y + 'px, 0) scale(' + scale + ')';
}
function wheelTurned ( event ) {
if ( ! shouldCaptureFn( event.target ) ) { return; }
if ( ! canZoomWithPonter ) { return; }
event.preventDefault();
var targetBounds = targetEl.getBoundingClientRect();
var delta = event.deltaY;
if ( event.deltaMode === 1 ) { // 1 is "lines", 0 is "pixels"
// Firefox uses "lines" when mouse is connected
delta *= 15;
}
// stop mouse wheel producing huge values
delta = Math.max( Math.min( delta, 60 ), -60 );
var scaleDiff = ( delta / 300 ) + 1;
// avoid to-small values
if ( scale * scaleDiff < 0.05 ) { return; }
updateValues(
x - ( event.pageX - targetBounds.left ) * ( scaleDiff - 1 ),
y - ( event.pageY - targetBounds.top ) * ( scaleDiff - 1 ),
scale * scaleDiff,
targetBounds
);
}
function firstPointerPressed ( event ) {
document.addEventListener( 'mousemove', pointerMoved );
document.addEventListener( 'mouseup', pointerReleased );
document.addEventListener( 'touchmove', pointerMoved );
document.addEventListener( 'touchend', pointerReleased );
}
function pointerPressed ( event ) {
if ( event.type == 'mousedown' && event.which != 1 ) {
return;
}
if ( ! shouldCaptureFn( event.target ) ) {
return;
}
if ( domHelper.isDescendant( containerEl, event.target ) ) {
event.preventDefault();
lastPoints = getPoints( event );
active++;
if ( active === 1 ) {
firstPointerPressed( event );
}
}
}
function pointerMoved ( event ) {
if ( event && event.target && domHelper.isDescendant( containerEl, event.target ) ) {
event.preventDefault();
var points = getPoints( event );
var averagePoint = points.reduce( getMidpoint );
var averageLastPoint = lastPoints.reduce( getMidpoint );
var targetBounds = targetEl.getBoundingClientRect();
var tmpX = x + averagePoint.x - averageLastPoint.x;
var tmpY = y + averagePoint.y - averageLastPoint.y;
if ( points[1] ) {
if ( ! canZoomWithPonter ) { return; }
var scaleDiff = touchDistance( points[0], points[1] ) / touchDistance( lastPoints[0], lastPoints[1] );
updateValues(
tmpX - ( averagePoint.x - targetBounds.left ) * ( scaleDiff - 1 ),
tmpY - ( averagePoint.y - targetBounds.top ) * ( scaleDiff - 1 ),
scale * scaleDiff,
targetBounds
);
} else {
updateValues( tmpX, tmpY, scale );
}
lastPoints = points;
}
}
function pointerReleased ( event ) {
event.preventDefault();
active--;
lastPoints.pop();
if ( active ) {
lastPoints = getPoints( event );
return;
}
document.removeEventListener( 'mousemove', pointerMoved );
document.removeEventListener( 'mouseup', pointerReleased );
document.removeEventListener( 'touchmove', pointerMoved );
document.removeEventListener( 'touchend', pointerReleased );
}
function updateContainerBounds () {
containerElSize = {
width: containerEl.clientWidth,
height: containerEl.clientHeight
}
targetElSize = {
width: targetEl.clientWidth,
height: targetEl.clientHeight
};
containerBounds = containerEl.getBoundingClientRect();
updateValues( x, y, scale );
}
function resized () {
clearTimeout( resizeTimeoutId );
resizeTimeoutId = setTimeout( function () {
updateContainerBounds();
if ( Math.abs( scale - centerScale ) <= 0.01 ) {
animateToCenter();
}
}, 100 );
}
function setToCenter () {
var centerValues = getCenterValues();
updateValues ( centerValues.x, centerValues.y, centerValues.scale );
return self;
}
function animateToCenter () {
var deltaX = 0;
var deltaY = 0;
var deltaScale = 0;
var easing = 0.1;
var centerValues = getCenterValues();
centerScale = centerValues.scale;
cancelAnimationFrame( scaleAnimationFrameId );
cancelAnimationFrame( centerAnimationFrameId );
function centerAnimationStep () {
deltaX = centerValues.x - x;
deltaY = centerValues.y - y;
deltaScale = centerValues.scale - scale;
if (
Math.abs( deltaX ) > 1 ||
Math.abs( deltaY ) > 1 ||
Math.abs( deltaScale ) > 0.01
) {
updateValues( x + deltaX * easing, y + deltaY * easing, scale + deltaScale * easing );
centerAnimationFrameId = requestAnimationFrame( centerAnimationStep );
} else {
updateValues( centerValues.x, centerValues.y, centerValues.scale );
}
}
centerAnimationFrameId = requestAnimationFrame( centerAnimationStep );
return self;
}
function settingUpdated ( key, value ) {
if ( key === 'canZoomWithPointer' && typeof value === 'boolean' ){
canZoomWithPonter = value;
}
}
function setScale ( newScale ) {
var targetX = ( containerElSize.width - targetElSize.width * newScale ) / 2;
var targetY = ( containerElSize.height - targetElSize.height * newScale ) / 2;
var deltaX = 0;
var deltaY = 0;
var deltaScale = 0;
var easing = 0.2;
cancelAnimationFrame( scaleAnimationFrameId );
cancelAnimationFrame( centerAnimationFrameId );
function scaleAnimationStep () {
deltaX = targetX - x;
deltaY = targetY - y;
deltaScale = newScale - scale;
if (
Math.abs( deltaX ) > 1 ||
Math.abs( deltaY ) > 1 ||
Math.abs( deltaScale ) > 0.01
) {
updateValues( x + deltaX * easing, y + deltaY * easing, scale + deltaScale * easing );
scaleAnimationFrameId = requestAnimationFrame( scaleAnimationStep );
} else {
updateValues( targetX, targetY, newScale );
}
}
scaleAnimationStep();
return self;
}
function getCenterValues () {
updateContainerBounds();
var targetScale = 1;
if ( targetElSize.width > containerElSize.width || targetElSize.height > containerElSize.height ) {
targetScale = Math.min(
containerElSize.width / targetElSize.width,
( containerElSize.height - 80 ) / targetElSize.height
);
}
return {
x: ( containerElSize.width - targetElSize.width * targetScale ) / 2,
y: ( containerElSize.height - targetElSize.height * targetScale ) / 2,
scale: targetScale
};
}
this.updateContainerBounds = updateContainerBounds;
this.reset = reset;
this.setToCenter = setToCenter;
this.animateToCenter = animateToCenter;
this.setScale = setScale;
this.settingUpdated = settingUpdated;
this.resized = resized;
}
return PanZoom;
}
);