Three.js OrbitControls Orthographic Panning - javascript
In my program, the further out you zoom, the slower you can pan around. So, I want to increase the panning speed based on the zoom level. I've tried .panSpeed, but that doesn't work, and .panSpeed doesn't seem to exist anymore in OrbitControls.js. Is there another function that I can use?
Here is the documentation I looked at for Three.js OrbitControls:
https://github.com/mrdoob/three.js/blob/master/examples/js/controls/OrbitControls.js
If you look in the documentation you just linked youll'find
// Set to false to disable panning
this.enablePan = true;
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
Maybe .keyPanSpeed is what you're looking for.
UPDATE 1:
In the OrbitControls.js you have the following function to handle pan:
function pan( deltaX, deltaY ) {
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
constraint.pan( deltaX, deltaY, element.clientWidth, element.clientHeight );
}
So if for example you multiply deltaX and deltaY by 10 you'll see the pan going 10 times faster:
constraint.pan( deltaX * 10, deltaY * 10, element.clientWidth, element.clientHeight );
Try to insert your calculations based on zoom right there, it should work.
UPDATE 2:
Actually I'm using an older version of OrbitControls, which is slightly different from the current one. Here it is:
/**
* #author qiao / https://github.com/qiao
* #author mrdoob / http://mrdoob.com
* #author alteredq / http://alteredqualia.com/
* #author WestLangley / http://github.com/WestLangley
* #author erich666 / http://erichaines.com
*/
/*global THREE, console */
( function () {
function OrbitConstraint ( object ) {
this.object = object;
// "target" sets the location of focus, where the object orbits around
// and where it pans with respect to.
this.target = new THREE.Vector3();
// Limits to how far you can dolly in and out ( PerspectiveCamera only )
this.minDistance = 0;
this.maxDistance = Infinity;
// Limits to how far you can zoom in and out ( OrthographicCamera only )
this.minZoom = 0;
this.maxZoom = Infinity;
// How far you can orbit vertically, upper and lower limits.
// Range is 0 to Math.PI radians.
this.minPolarAngle = 0; // radians
this.maxPolarAngle = Math.PI; // radians
// How far you can orbit horizontally, upper and lower limits.
// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
this.minAzimuthAngle = - Infinity; // radians
this.maxAzimuthAngle = Infinity; // radians
// Set to true to enable damping (inertia)
// If damping is enabled, you must call controls.update() in your animation loop
this.enableDamping = false;
this.dampingFactor = 0.25;
////////////
// internals
var scope = this;
var EPS = 0.000001;
// Current position in spherical coordinate system.
var theta;
var phi;
// Pending changes
var phiDelta = 0;
var thetaDelta = 0;
var scale = 1;
var panOffset = new THREE.Vector3();
var zoomChanged = false;
// API
this.getPolarAngle = function () {
return phi;
};
this.getAzimuthalAngle = function () {
return theta;
};
this.rotateLeft = function ( angle ) {
thetaDelta -= angle;
};
this.rotateUp = function ( angle ) {
phiDelta -= angle;
};
// pass in distance in world space to move left
this.panLeft = function() {
var v = new THREE.Vector3();
return function panLeft ( distance ) {
var te = this.object.matrix.elements;
// get X column of matrix
v.set( te[ 0 ], te[ 1 ], te[ 2 ] );
v.multiplyScalar( - distance );
panOffset.add( v );
};
}();
// pass in distance in world space to move up
this.panUp = function() {
var v = new THREE.Vector3();
return function panUp ( distance ) {
var te = this.object.matrix.elements;
// get Y column of matrix
v.set( te[ 4 ], te[ 5 ], te[ 6 ] );
v.multiplyScalar( distance );
panOffset.add( v );
};
}();
// pass in x,y of change desired in pixel space,
// right and down are positive
this.pan = function ( deltaX, deltaY, screenWidth, screenHeight ) {
if ( scope.object instanceof THREE.PerspectiveCamera ) {
// perspective
var position = scope.object.position;
var offset = position.clone().sub( scope.target );
var targetDistance = offset.length();
// half of the fov is center to top of screen
targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
// we actually don't use screenWidth, since perspective camera is fixed to screen height
scope.panLeft( 2 * deltaX * targetDistance / screenHeight );
scope.panUp( 2 * deltaY * targetDistance / screenHeight );
} else if ( scope.object instanceof THREE.OrthographicCamera ) {
// orthographic
scope.panLeft( deltaX * ( scope.object.right - scope.object.left ) / screenWidth );
scope.panUp( deltaY * ( scope.object.top - scope.object.bottom ) / screenHeight );
} else {
// camera neither orthographic or perspective
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
}
};
this.dollyIn = function ( dollyScale ) {
if ( scope.object instanceof THREE.PerspectiveCamera ) {
scale /= dollyScale;
} else if ( scope.object instanceof THREE.OrthographicCamera ) {
scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) );
scope.object.updateProjectionMatrix();
zoomChanged = true;
} else {
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
}
};
this.dollyOut = function ( dollyScale ) {
if ( scope.object instanceof THREE.PerspectiveCamera ) {
scale *= dollyScale;
} else if ( scope.object instanceof THREE.OrthographicCamera ) {
scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) );
scope.object.updateProjectionMatrix();
zoomChanged = true;
} else {
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
}
};
this.update = function() {
var offset = new THREE.Vector3();
// so camera.up is the orbit axis
var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
var quatInverse = quat.clone().inverse();
var lastPosition = new THREE.Vector3();
var lastQuaternion = new THREE.Quaternion();
return function () {
var position = this.object.position;
offset.copy( position ).sub( this.target );
// rotate offset to "y-axis-is-up" space
offset.applyQuaternion( quat );
// angle from z-axis around y-axis
theta = Math.atan2( offset.x, offset.z );
// angle from y-axis
phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
theta += thetaDelta;
phi += phiDelta;
// restrict theta to be between desired limits
theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) );
// restrict phi to be between desired limits
phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
// restrict phi to be betwee EPS and PI-EPS
phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
var radius = offset.length() * scale;
// restrict radius to be between desired limits
radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
// move target to panned location
this.target.add( panOffset );
offset.x = radius * Math.sin( phi ) * Math.sin( theta );
offset.y = radius * Math.cos( phi );
offset.z = radius * Math.sin( phi ) * Math.cos( theta );
// rotate offset back to "camera-up-vector-is-up" space
offset.applyQuaternion( quatInverse );
position.copy( this.target ).add( offset );
this.object.lookAt( this.target );
if ( this.enableDamping === true ) {
thetaDelta *= ( 1 - this.dampingFactor );
phiDelta *= ( 1 - this.dampingFactor );
} else {
thetaDelta = 0;
phiDelta = 0;
}
scale = 1;
panOffset.set( 0, 0, 0 );
// update condition is:
// min(camera displacement, camera rotation in radians)^2 > EPS
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
if ( zoomChanged ||
lastPosition.distanceToSquared( this.object.position ) > EPS ||
8 * ( 1 - lastQuaternion.dot( this.object.quaternion ) ) > EPS ) {
lastPosition.copy( this.object.position );
lastQuaternion.copy( this.object.quaternion );
zoomChanged = false;
return true;
}
return false;
};
}();
};
// This set of controls performs orbiting, dollying (zooming), and panning. It maintains
// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is
// supported.
//
// Orbit - left mouse / touch: one finger move
// Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
// Pan - right mouse, or arrow keys / touch: three finter swipe
THREE.OrbitControls = function ( object, domElement ) {
var constraint = new OrbitConstraint( object );
this.domElement = ( domElement !== undefined ) ? domElement : document;
// API
Object.defineProperty( this, 'constraint', {
get: function() {
return constraint;
}
} );
this.getPolarAngle = function () {
return constraint.getPolarAngle();
};
this.getAzimuthalAngle = function () {
return constraint.getAzimuthalAngle();
};
// Set to false to disable this control
this.enabled = true;
// center is old, deprecated; use "target" instead
this.center = this.target;
// This option actually enables dollying in and out; left as "zoom" for
// backwards compatibility.
// Set to false to disable zooming
this.enableZoom = true;
this.zoomSpeed = 1.0;
// Set to false to disable rotating
this.enableRotate = true;
this.rotateSpeed = 1.0;
// Set to false to disable panning
this.enablePan = true;
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
// Set to true to automatically rotate around the target
// If auto-rotate is enabled, you must call controls.update() in your animation loop
this.autoRotate = false;
this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
// Set to false to disable use of the keys
this.enableKeys = true;
// The four arrow keys
this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
// Mouse buttons
this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
////////////
// internals
var scope = this;
var rotateStart = new THREE.Vector2();
var rotateEnd = new THREE.Vector2();
var rotateDelta = new THREE.Vector2();
var panStart = new THREE.Vector2();
var panEnd = new THREE.Vector2();
var panDelta = new THREE.Vector2();
var dollyStart = new THREE.Vector2();
var dollyEnd = new THREE.Vector2();
var dollyDelta = new THREE.Vector2();
var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
var state = STATE.NONE;
// for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.zoom0 = this.object.zoom;
// events
var changeEvent = { type: 'change' };
var startEvent = { type: 'start' };
var endEvent = { type: 'end' };
// pass in x,y of change desired in pixel space,
// right and down are positive
function pan( deltaX, deltaY ) {
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
constraint.pan( deltaX, deltaY, element.clientWidth, element.clientHeight );
}
this.update = function () {
if ( this.autoRotate && state === STATE.NONE ) {
constraint.rotateLeft( getAutoRotationAngle() );
}
if ( constraint.update() === true ) {
this.dispatchEvent( changeEvent );
}
};
this.reset = function () {
state = STATE.NONE;
this.target.copy( this.target0 );
this.object.position.copy( this.position0 );
this.object.zoom = this.zoom0;
this.object.updateProjectionMatrix();
this.dispatchEvent( changeEvent );
this.update();
};
function getAutoRotationAngle() {
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
}
function getZoomScale() {
return Math.pow( 0.95, scope.zoomSpeed );
}
function onMouseDown( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
if ( event.button === scope.mouseButtons.ORBIT ) {
if ( scope.enableRotate === false ) return;
state = STATE.ROTATE;
rotateStart.set( event.clientX, event.clientY );
} else if ( event.button === scope.mouseButtons.ZOOM ) {
if ( scope.enableZoom === false ) return;
state = STATE.DOLLY;
dollyStart.set( event.clientX, event.clientY );
} else if ( event.button === scope.mouseButtons.PAN ) {
if ( scope.enablePan === false ) return;
state = STATE.PAN;
panStart.set( event.clientX, event.clientY );
}
if ( state !== STATE.NONE ) {
document.addEventListener( 'mousemove', onMouseMove, false );
document.addEventListener( 'mouseup', onMouseUp, false );
scope.dispatchEvent( startEvent );
}
}
function onMouseMove( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
if ( state === STATE.ROTATE ) {
if ( scope.enableRotate === false ) return;
rotateEnd.set( event.clientX, event.clientY );
rotateDelta.subVectors( rotateEnd, rotateStart );
// rotating across whole screen goes 360 degrees around
constraint.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
// rotating up and down along whole screen attempts to go 360, but limited to 180
constraint.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
rotateStart.copy( rotateEnd );
} else if ( state === STATE.DOLLY ) {
if ( scope.enableZoom === false ) return;
dollyEnd.set( event.clientX, event.clientY );
dollyDelta.subVectors( dollyEnd, dollyStart );
if ( dollyDelta.y > 0 ) {
constraint.dollyIn( getZoomScale() );
} else if ( dollyDelta.y < 0 ) {
constraint.dollyOut( getZoomScale() );
}
dollyStart.copy( dollyEnd );
} else if ( state === STATE.PAN ) {
if ( scope.enablePan === false ) return;
panEnd.set( event.clientX, event.clientY );
panDelta.subVectors( panEnd, panStart );
pan( panDelta.x, panDelta.y );
panStart.copy( panEnd );
}
if ( state !== STATE.NONE ) scope.update();
}
function onMouseUp( /* event */ ) {
if ( scope.enabled === false ) return;
document.removeEventListener( 'mousemove', onMouseMove, false );
document.removeEventListener( 'mouseup', onMouseUp, false );
scope.dispatchEvent( endEvent );
state = STATE.NONE;
}
function onMouseWheel( event ) {
if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return;
event.preventDefault();
event.stopPropagation();
var delta = 0;
if ( event.wheelDelta !== undefined ) {
// WebKit / Opera / Explorer 9
delta = event.wheelDelta;
} else if ( event.detail !== undefined ) {
// Firefox
delta = - event.detail;
}
if ( delta > 0 ) {
constraint.dollyOut( getZoomScale() );
} else if ( delta < 0 ) {
constraint.dollyIn( getZoomScale() );
}
scope.update();
scope.dispatchEvent( startEvent );
scope.dispatchEvent( endEvent );
}
function onKeyDown( event ) {
if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
switch ( event.keyCode ) {
case scope.keys.UP:
pan( 0, scope.keyPanSpeed );
scope.update();
break;
case scope.keys.BOTTOM:
pan( 0, - scope.keyPanSpeed );
scope.update();
break;
case scope.keys.LEFT:
pan( scope.keyPanSpeed, 0 );
scope.update();
break;
case scope.keys.RIGHT:
pan( - scope.keyPanSpeed, 0 );
scope.update();
break;
}
}
function touchstart( event ) {
if ( scope.enabled === false ) return;
switch ( event.touches.length ) {
case 1: // one-fingered touch: rotate
if ( scope.enableRotate === false ) return;
state = STATE.TOUCH_ROTATE;
rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;
case 2: // two-fingered touch: dolly
if ( scope.enableZoom === false ) return;
state = STATE.TOUCH_DOLLY;
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
var distance = Math.sqrt( dx * dx + dy * dy );
dollyStart.set( 0, distance );
break;
case 3: // three-fingered touch: pan
if ( scope.enablePan === false ) return;
state = STATE.TOUCH_PAN;
panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
break;
default:
state = STATE.NONE;
}
if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent );
}
function touchmove( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
event.stopPropagation();
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
switch ( event.touches.length ) {
case 1: // one-fingered touch: rotate
if ( scope.enableRotate === false ) return;
if ( state !== STATE.TOUCH_ROTATE ) return;
rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
rotateDelta.subVectors( rotateEnd, rotateStart );
// rotating across whole screen goes 360 degrees around
constraint.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
// rotating up and down along whole screen attempts to go 360, but limited to 180
constraint.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
rotateStart.copy( rotateEnd );
scope.update();
break;
case 2: // two-fingered touch: dolly
if ( scope.enableZoom === false ) return;
if ( state !== STATE.TOUCH_DOLLY ) return;
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
var distance = Math.sqrt( dx * dx + dy * dy );
dollyEnd.set( 0, distance );
dollyDelta.subVectors( dollyEnd, dollyStart );
if ( dollyDelta.y > 0 ) {
constraint.dollyOut( getZoomScale() );
} else if ( dollyDelta.y < 0 ) {
constraint.dollyIn( getZoomScale() );
}
dollyStart.copy( dollyEnd );
scope.update();
break;
case 3: // three-fingered touch: pan
if ( scope.enablePan === false ) return;
if ( state !== STATE.TOUCH_PAN ) return;
panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
panDelta.subVectors( panEnd, panStart );
pan( panDelta.x, panDelta.y );
panStart.copy( panEnd );
scope.update();
break;
default:
state = STATE.NONE;
}
}
function touchend( /* event */ ) {
if ( scope.enabled === false ) return;
scope.dispatchEvent( endEvent );
state = STATE.NONE;
}
function contextmenu( event ) {
event.preventDefault();
}
this.dispose = function() {
this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
this.domElement.removeEventListener( 'mousedown', onMouseDown, false );
this.domElement.removeEventListener( 'mousewheel', onMouseWheel, false );
this.domElement.removeEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox
this.domElement.removeEventListener( 'touchstart', touchstart, false );
this.domElement.removeEventListener( 'touchend', touchend, false );
this.domElement.removeEventListener( 'touchmove', touchmove, false );
document.removeEventListener( 'mousemove', onMouseMove, false );
document.removeEventListener( 'mouseup', onMouseUp, false );
window.removeEventListener( 'keydown', onKeyDown, false );
}
this.domElement.addEventListener( 'contextmenu', contextmenu, false );
this.domElement.addEventListener( 'mousedown', onMouseDown, false );
this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
this.domElement.addEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox
this.domElement.addEventListener( 'touchstart', touchstart, false );
this.domElement.addEventListener( 'touchend', touchend, false );
this.domElement.addEventListener( 'touchmove', touchmove, false );
window.addEventListener( 'keydown', onKeyDown, false );
// force an update at start
this.update();
};
THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
Object.defineProperties( THREE.OrbitControls.prototype, {
object: {
get: function () {
return this.constraint.object;
}
},
target: {
get: function () {
return this.constraint.target;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: target is now immutable. Use target.set() instead.' );
this.constraint.target.copy( value );
}
},
minDistance : {
get: function () {
return this.constraint.minDistance;
},
set: function ( value ) {
this.constraint.minDistance = value;
}
},
maxDistance : {
get: function () {
return this.constraint.maxDistance;
},
set: function ( value ) {
this.constraint.maxDistance = value;
}
},
minZoom : {
get: function () {
return this.constraint.minZoom;
},
set: function ( value ) {
this.constraint.minZoom = value;
}
},
maxZoom : {
get: function () {
return this.constraint.maxZoom;
},
set: function ( value ) {
this.constraint.maxZoom = value;
}
},
minPolarAngle : {
get: function () {
return this.constraint.minPolarAngle;
},
set: function ( value ) {
this.constraint.minPolarAngle = value;
}
},
maxPolarAngle : {
get: function () {
return this.constraint.maxPolarAngle;
},
set: function ( value ) {
this.constraint.maxPolarAngle = value;
}
},
minAzimuthAngle : {
get: function () {
return this.constraint.minAzimuthAngle;
},
set: function ( value ) {
this.constraint.minAzimuthAngle = value;
}
},
maxAzimuthAngle : {
get: function () {
return this.constraint.maxAzimuthAngle;
},
set: function ( value ) {
this.constraint.maxAzimuthAngle = value;
}
},
enableDamping : {
get: function () {
return this.constraint.enableDamping;
},
set: function ( value ) {
this.constraint.enableDamping = value;
}
},
dampingFactor : {
get: function () {
return this.constraint.dampingFactor;
},
set: function ( value ) {
this.constraint.dampingFactor = value;
}
},
// backward compatibility
noZoom: {
get: function () {
console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
return ! this.enableZoom;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
this.enableZoom = ! value;
}
},
noRotate: {
get: function () {
console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
return ! this.enableRotate;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
this.enableRotate = ! value;
}
},
noPan: {
get: function () {
console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
return ! this.enablePan;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
this.enablePan = ! value;
}
},
noKeys: {
get: function () {
console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
return ! this.enableKeys;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
this.enableKeys = ! value;
}
},
staticMoving : {
get: function () {
console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
return ! this.constraint.enableDamping;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
this.constraint.enableDamping = ! value;
}
},
dynamicDampingFactor : {
get: function () {
console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
return this.constraint.dampingFactor;
},
set: function ( value ) {
console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
this.constraint.dampingFactor = value;
}
}
} );
}() );
Related
Horizontal scrolling with mouse wheel in vanilla JS
I'm trying to migrate some jQuery code to vanilla JS that modifies the mouse wheel behavior to scroll a site horizontally instead of vertically: jQuery: var wheel = function() { var width = $( window ).width(); if ( width > 954 ) { $( 'html' ).on( 'wheel', function( e ) { e.preventDefault(); if ( Math.abs( e.originalEvent.deltaY ) >= Math.abs( e.originalEvent.deltaX ) ) { this.scrollLeft += ( e.originalEvent.deltaY * 10 ); } else { this.scrollLeft -= ( e.originalEvent.deltaX * 10 ); } } ); } else { $( 'html' ).off( 'wheel' ); } } wheel(); $( window ).on( 'resize', wheel ); As you can see ( if (width > 954) ), the new behavior is just set on desktops, not mobile nor tablet devices. This is the vanilla JS code I came up with: Vanilla JS: var wheel = function() { var width = window.innerWidth; var scroll = function( e ) { e.preventDefault(); if ( Math.abs( e.deltaY ) >= Math.abs( e.deltaX ) ) { this.scrollLeft += ( e.deltaY * 10 ); } else { this.scrollLeft -= ( e.deltaX * 10 ); } } if ( width > 954 ) { document.documentElement.addEventListener( 'wheel', scroll ); } else { document.documentElement.removeEventListener( 'wheel', scroll ); } } wheel(); window.addEventListener( 'resize', wheel ); But, when I resize the window to the tablet/mobile width, the horizontal scrolling is not disabled and I cannot scroll the site vertically. It seems as if the removeEventListener() function is not really removing my listener function. Any ideas about what's going on here?
You are binding wheel handler many times as you resize the window. I would suggest to bind it once and then check the window width in it. Maybe something like this: var scroll = function( e ) { var width = window.innerWidth; if(width <= 954) return; e.preventDefault(); if ( Math.abs( e.deltaY ) >= Math.abs( e.deltaX ) ) { this.scrollLeft += ( e.deltaY * 10 ); } else { this.scrollLeft -= ( e.deltaX * 10 ); } } document.documentElement.addEventListener( 'wheel', scroll);
Prevent click when dragging
Im using Three.js to display a 3d model which users can drag the camera around and click objects to zoom in on them. The issue I'm having is that when you click and drag it reads it as a click and triggers the animation, I need to prevent clicking when dragging, so clicks are only registered when it is just a click and no mouse movement. function onClick(event) { event.preventDefault(); mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1; mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1; raycaster.setFromCamera( mouse, camera ); var intersects = raycaster.intersectObjects( scene.children, true ); if ( intersects.length > 0 && intersects[0].object.name==="Tree006") { var object = intersects[0].object; gsap.to( camera.position, { duration: 1, x: mesh["Tree006"].position.x, y: mesh["Tree006"].position.y, z: mesh["Tree006"].position.z, onUpdate: function() { controls.enabled = false; camera.lookAt(0,0,0); } } ); console.log( 'Intersection:', intersects[ 0 ] ); } if ( intersects.length > 0 && intersects[0].object.name!=="Tree006") { var object = intersects[0].object; gsap.to( camera.position, { duration: 1, // seconds x: 6, y: 4, z: 6, onUpdate: function() { controls.enabled = true; camera.lookAt( 0,0,0 ); } } ); } }
Event click is only fired when you release the mouse button over the element. So you can set some flag on mousedown and reset that flag on mouseup. This flag would tell the click handler not to do anything. var btn1 = document.querySelector("#btn1"); var flag = false; btn1.addEventListener('click', function(ev) { if (!flag) { alert(1); } ev.preventDefault(); }) btn1.addEventListener('mousedown', function(ev) { flag = true; console.log("flag to disable click is true"); }) btn1.addEventListener('mouseup', function(ev) { // this is dirty hack, until i find a better solution: setTimeout(function() { flag = false console.log("flag to disable click is false"); }); }) <button id="btn1">no click</button> <button id="btn2" onclick="alert(2)">click</button>
THREE.js function - convert to accept a different initial rotation
I'm trying to convert the code found here http://benchung.com/smooth-mouse-rotation-three-js/ to and AFRAME component. This is all good if the initial rotation is '0 0 0', but now I'm trying to set a different initial rotation. #Piotr kindly put a fiddle together for me But basically I want to be able to set that initial rotation and then the object rotate on click and drag using the rest of the function. AFRAME.registerComponent('drag-rotate',{ schema : { mouseSpeed : {default:1}, touchSpeed : {default:2}, rotation : {type: 'vec3'}, disabled: {default: false} }, windowHalfX: window.innerWidth / 2, windowHalfY: window.innerHeight / 2, targetRotationX:0, targetRotationOnMouseDownX:0, targetRotationY:0, targetRotationOnMouseDownY: 0, mouseX:0, mouseXOnMouseDown:0, mouseY: 0, mouseYOnMouseDown: 0, init : function(){ this.ifMouseDown = false document.addEventListener('touchstart',this.onTouchStart.bind(this)) document.addEventListener('touchend',this.onTouchEnd.bind(this)) document.addEventListener('touchmove',this.onTouchMove.bind(this)) document.addEventListener('mousedown',this.OnDocumentMouseDown.bind(this)) window.addEventListener( 'resize', this.onWindowResize.bind(this) ) }, update: function (oldData) { if(!AFRAME.utils.deepEqual(oldData.rotation, this.data.rotation)){ this.el.setAttribute('rotation', this.data.rotation) this._targetRotation = this.el.object3D.rotation.clone() this.targetRotationX = this._targetRotation.x this.targetRotationY = this._targetRotation.y } }, remove: function() { document.removeEventListener('touchstart',this.onTouchStart.bind(this)) document.removeEventListener('mousedown',this.OnDocumentMouseDown.bind(this)) window.removeEventListener( 'resize', this.onWindowResize.bind(this)) }, onWindowResize: function () { this.windowHalfX = window.innerWidth / 2 this.windowHalfY = window.innerHeight / 2 }, OnDocumentMouseDown : function(event){ this.ifMouseDown = ['A-SCENE', 'CANVAS'].includes(event.target?.tagName) if(this.ifMouseDown){ document.addEventListener('mouseup',this.OnDocumentMouseUp.bind(this)) document.addEventListener('mousemove',this.OnDocumentMouseMove.bind(this)) this.mouseXOnMouseDown = event.clientX - this.windowHalfX this.targetRotationOnMouseDownX = this.targetRotationX this.mouseYOnMouseDown = event.clientY - this.windowHalfY this.targetRotationOnMouseDownY = this.targetRotationY } }, OnDocumentMouseUp : function(){ this.ifMouseDown = false document.removeEventListener('mouseup',this.OnDocumentMouseUp.bind(this)) document.removeEventListener('mousemove',this.OnDocumentMouseMove.bind(this)) }, OnDocumentMouseMove : function(event) { if(this.ifMouseDown){ this.mouseX = event.clientX - this.windowHalfX; this.mouseY = event.clientY - this.windowHalfY; this.targetRotationY = this.targetRotationOnMouseDownY + (this.mouseY - this.mouseYOnMouseDown) * this.data.mouseSpeed/1000 this.targetRotationX = this.targetRotationOnMouseDownX + (this.mouseX - this.mouseXOnMouseDown) * this.data.mouseSpeed/1000 } }, onTouchStart: function(event){ if (event.touches.length == 1) { this.ifMouseDown = ['A-SCENE', 'CANVAS'].includes(event.target?.tagName) this.x_cord = event.touches[ 0 ].pageX this.y_cord = event.touches[ 0 ].pageY document.addEventListener('touchend',this.onTouchEnd.bind(this)) document.addEventListener('touchmove',this.onTouchMove.bind(this)) this.mouseXOnMouseDown = event.touches[ 0 ].pageX - this.windowHalfX this.targetRotationOnMouseDownX = this.targetRotationX this.mouseYOnMouseDown = event.touches[ 0 ].pageX - this.windowHalfY this.targetRotationOnMouseDownY = this.targetRotationY } }, onTouchMove: function(event){ if(this.ifMouseDown){ this.mouseX = event.touches[ 0 ].pageX - this.windowHalfX; this.mouseY = event.touches[ 0 ].pageY - this.windowHalfY; this.targetRotationY = this.targetRotationOnMouseDownY + (this.mouseY - this.mouseYOnMouseDown) * this.data.touchSpeed/1000 this.targetRotationX = this.targetRotationOnMouseDownX + (this.mouseX - this.mouseXOnMouseDown) * this.data.touchSpeed/1000 } }, onTouchEnd: function(event){ document.removeEventListener('touchend',this.onTouchEnd.bind(this)) document.removeEventListener('touchmove',this.onTouchMove.bind(this)) this.ifMouseDown = false }, tick: function(){ if(this.data.disabled) return this.el.object3D.rotation.y += ( this.targetRotationX - this.el.object3D.rotation.y ) * 0.1 this.finalRotationY = (this.targetRotationY - this.el.object3D.rotation.x) if (this.el.object3D.rotation.x <= 1 && this.el.object3D.rotation.x >= -1 ) this.el.object3D.rotation.x += this.finalRotationY * 0.1 if (this.el.object3D.rotation.x > 1 ) this.el.object3D.rotation.x = 1 if (this.el.object3D.rotation.x < -1 ) this.el.object3D.rotation.x = -1 }, }); The initial angle I set with AFRAME in the update function isn't the same as set here. i.e. with this component disabled With it enabled If I zero these values as in the example code then the rotation is '0 0 0' and it works as normal. this.el.setAttribute('rotation', this.data.rotation) this._targetRotation = this.el.object3D.rotation.clone() this.targetRotationX = 0 this.targetRotationY = 0
This magic code worked this.el.object3D.rotation.y += ( (this._targetRotation.y + this.targetRotationX) - this.el.object3D.rotation.y ) * 0.1 this.finalRotationY = ((this._targetRotation.x + this.targetRotationY) - this.el.object3D.rotation.x)
JQuery trigger function if element is in viewport
I'm trying to make a function openAnimation() working, when the element is "in viewport"! Now, this particular function is GSAP. Every time I run openAnimation() doesn't work as aspected. Runs twice and repeats each time the in-view class is been added. -So how can I run my GSAP function with this plug in? https://codepen.io/davide77/pen/qPLoKP function inView( opt ) { if( opt.selector === undefined ) { console.log( 'Valid selector required for inView' ); return false; } var elems = [].slice.call( document.querySelectorAll( opt.selector ) ), once = opt.once === undefined ? true : opt.once, offsetTop = opt.offsetTop === undefined ? 0 : opt.offsetTop, offsetBot = opt.offsetBot === undefined ? 0 : opt.offsetBot, count = elems.length, winHeight = 0, ticking = false; function update() { var i = count; while( i-- ) { var elem = elems[ i ], rect = elem.getBoundingClientRect(); if( rect.bottom >= offsetTop && rect.top <= winHeight - offsetBot ) { elem.classList.add( 'in-view' ); if( once ) { count--; elems.splice( i, 1 ); } } else { elem.classList.remove( 'in-view' ); } } ticking = false; } function onResize() { winHeight = window.innerHeight; requestTick(); } function onScroll() { requestTick(); } function requestTick() { if( !ticking ) { requestAnimationFrame( update ); ticking = true; } } window.addEventListener( 'resize', onResize, false ); document.addEventListener( 'scroll', onScroll, false ); document.addEventListener( 'touchmove', onScroll, false ); onResize(); } inView({ selector: '.view-poll', // an .in-view class will get toggled on these elements once: true, // set this to false to have the .in-view class be toggled on AND off offsetTop: 0, // top threshold to be considered "in view" offsetBot: 0 // bottom threshold to be considered "in view" }); // HOW CAN I RUN THIS FUNCTION NOW? function openAnimation() { var rotate = $('.rotate.in-view'); var scale = $('.scale.in-view'); var translate = $('.translate.in-view'); //feature Left TweenLite.from(rotate, 1.2, {y:-400, opacity: 0.0, delay:0.0, }, 0.05); TweenLite.from(scale, 1.2, {y:-400, opacity: 0.0, delay:0.0, }, 0.05); TweenLite.from(translate, 1.2, {y:-400, opacity: 0.0, delay:0.0, }, 0.05); }
You can use the library jQuery-visible. With its help, you can create a window event onScroll and check if your element is "visible", then call your function.
You can use http://scrollmagic.io/. With this you can initiate any function on scroll or when its in view.
jquery animate if element is in viewport
What I need here is to trigger the animation when the element is visible (in viewport). I have a function called openAnimation() that runs a GSPA TweenMax. that should animate nicely my elements, but it does't work. Basically I need to trigger the openAnimation() when my element in inview. Demo-codepen function inView( opt ) { if( opt.selector === undefined ) { console.log( 'Valid selector required for inView' ); return false; } var elems = [].slice.call( document.querySelectorAll( opt.selector ) ), once = opt.once === undefined ? true : opt.once, offsetTop = opt.offsetTop === undefined ? 0 : opt.offsetTop, offsetBot = opt.offsetBot === undefined ? 0 : opt.offsetBot, count = elems.length, winHeight = 0, ticking = false; function update() { var i = count; while( i-- ) { var elem = elems[ i ], rect = elem.getBoundingClientRect(); if( rect.bottom >= offsetTop && rect.top <= winHeight - offsetBot ) { elem.classList.add( 'in-view' ); if( once ) { count--; elems.splice( i, 1 ); } } else { elem.classList.remove( 'in-view' ); } } ticking = false; } function onResize() { winHeight = window.innerHeight; requestTick(); } function onScroll() { requestTick(); } function requestTick() { if( !ticking ) { requestAnimationFrame( update ); ticking = true; } } window.addEventListener( 'resize', onResize, false ); document.addEventListener( 'scroll', onScroll, false ); document.addEventListener( 'touchmove', onScroll, false ); onResize(); } inView({ selector: '.view-poll', // an .in-view class will get toggled on these elements once: true, // set this to false to have the .in-view class be toggled on AND off offsetTop: 0, // top threshold to be considered "in view" offsetBot: 0 // bottom threshold to be considered "in view" }); function openAnimation() { var rotate = $('.rotate.in-view'); var scale = $('.scale.in-view'); var translate = $('.translate.in-view'); //feature Left TweenLite.from(rotate, 1.2, {y:-400, opacity: 0.0, delay:0.0, }, 0.05); TweenLite.from(scale, 1.2, {y:-400, opacity: 0.0, delay:0.0, }, 0.05); TweenLite.from(translate, 1.2, {y:-400, opacity: 0.0, delay:0.0, }, 0.05); }