UNPKG

trackball

Version:

The RAW library to add a virtual Trackball to your DOM

20 lines (18 loc) 6.03 kB
/* Trackball.js v1.3.1 8/16/2025 https://raw.org/article/trackball-rotation-using-quaternions/ Copyright (c) 2025, Robert Eisele (https://raw.org/) Licensed under the MIT license. */ 'use strict';(function(v){function g(a){a||={};a.nodeType&&(a={scene:a});"string"===typeof a.scene&&(a.scene=document.querySelector(a.scene));const c=a.scene;if(!c)throw Error("[Trackball] opts.scene is required");this.opts={onDraw:"function"===typeof a.onDraw?a.onDraw:function(){},onStart:"function"===typeof a.onStart?a.onStart:function(){},onEnd:"function"===typeof a.onEnd?a.onEnd:function(){},enabled:!1!==a.enabled,limitAxis:a.limitAxis??null,invertX:!!a.invertX,invertY:!!a.invertY,speed:a.speed?? 1,ballsize:a.ballsize??.75,border:a.border??.5,inertia:"object"===typeof a.inertia||!0===a.inertia?a.inertia||{}:!1,usePointerCapture:!1!==a.usePointerCapture,passiveMove:!1!==a.passiveMove,activeArea:a.activeArea??null,areaPolicy:a.areaPolicy||"inside-only"};this.scene=c;this.q=this.p=a.q||Quaternion.ONE;this._onPointerDown=w.bind(this);this._onPointerMove=x.bind(this);this._onPointerUp=y.bind(this);this.scene.addEventListener("pointerdown",this._onPointerDown,{passive:!0});document.addEventListener("pointermove", this._onPointerMove,{passive:!!this.opts.passiveMove});document.addEventListener("pointerup",this._onPointerUp,{passive:!0});document.addEventListener("pointercancel",this._onPointerUp,{passive:!0});h.call(this)}function w(a){if(this.opts.enabled){var c=q.call(this);if(n(a.clientX,a.clientY,c)||"always"===this.opts.areaPolicy){l.call(this);this.velocity=0;this.velocityAxis=null;this.t0=this.t1=performance.now();this.lastDQ=null;if(this.opts.usePointerCapture&&a.target&&"setPointerCapture"in a.target)try{a.target.setPointerCapture(a.pointerId)}catch(e){}var b= r.call(this,a.clientX,a.clientY,c);this.drag={startVec:b,prevVec:b,startPos:[a.clientX,a.clientY],rect:c};this.p=this.q;this.lastPointer={x:a.clientX,y:a.clientY};this.opts.onStart&&this.opts.onStart(this.q);h.call(this)}}}function x(a){this.drag&&(this.lastPointer={x:a.clientX,y:a.clientY},this.rafPending||(this.rafPending=!0,requestAnimationFrame(z.bind(this))))}function y(a){if(this.drag){this.t1=performance.now();var c=n(a.clientX,a.clientY,this.drag.rect);("inside-only"!==this.opts.areaPolicy|| c)&&t.call(this,a.clientX,a.clientY);this.p=this.q;this.drag=null;this.opts.inertia?(a=this.opts.inertia||{},A.call(this,a.damping??.93,a.minVelocity??.001,a.maxStep??.2,a.timeDiv??50)):(this.opts.onEnd&&this.opts.onEnd(this.q),h.call(this))}}function z(){this.rafPending=!1;if(this.drag&&this.lastPointer){var a=this.lastPointer.x,c=this.lastPointer.y,b=n(a,c,this.drag.rect);if("inside-only"!==this.opts.areaPolicy||b)b=this.opts.activeArea,(b="function"===typeof b||b instanceof HTMLElement||"string"=== typeof b?q.call(this):null)&&(this.drag.rect=b),t.call(this,a,c),h.call(this)}}function t(a,c){a=r.call(this,this.opts.invertX?2*this.drag.startPos[0]-a:a,this.opts.invertY?2*this.drag.startPos[1]-c:c,this.drag.rect);c=Quaternion.fromVectors(this.drag.startVec,a);this.q=c.mul(this.p);this.lastDQ=c;this.drag.prevVec=a}function A(a,c,b,e){function k(){d.angle+=d.angleChange;d.angleChange*=a||.93;d.q=Quaternion.fromAxisAngle(d.spinAxis,d.angle).mul(d.spinBase);h.call(d);d.angleChange<c?(l.call(d),d.p= d.q,d.opts.onEnd&&d.opts.onEnd(d.q)):d.inertiaID=requestAnimationFrame(k)}this.inertiaID&&cancelAnimationFrame(this.inertiaID);var f=this.lastDQ;if(f){var p=Math.max(-1,Math.min(1,f.w)),m=Math.sqrt(Math.max(0,1-p*p));f=1E-12<m?[f.x/m,f.y/m,f.z/m]:[0,0,1];this.angleChange=Math.min(b||.2,Math.abs(2*Math.acos(p)/Math.max((this.t1-this.t0)/(e||50),1)));if(0<this.angleChange){this.angle=0;this.spinAxis=f;this.spinBase=this.p;var d=this;this.inertiaID=requestAnimationFrame(k)}}}function l(){this.inertiaID&& cancelAnimationFrame(this.inertiaID);this.velocity=this.inertiaID=0;this.velocityAxis=null}function h(){this.opts.onDraw&&this.opts.onDraw(this.q)}function q(){var a=this.opts.activeArea;return a?"function"===typeof a?u(a()):"string"===typeof a?(a=document.querySelector(a))&&a instanceof HTMLElement?a.getBoundingClientRect():this.scene.getBoundingClientRect():a instanceof HTMLElement?a.getBoundingClientRect():u(a):this.scene.getBoundingClientRect()}function u(a){if(!a)return document.body.getBoundingClientRect(); if("undefined"!==typeof DOMRect&&a instanceof DOMRect)return a;const c=a.left??a.x??0,b=a.top??a.y??0,e=a.width??0;a=a.height??0;return{left:c,top:b,right:c+e,bottom:b+a,width:e,height:a,x:c,y:b,toJSON:function(){return this}}}function n(a,c,b){return a>=b.left&&a<=b.right&&c>=b.top&&c<=b.bottom}function r(a,c,b){var e=Math.max(b.width,b.height)-1;a=(2*(a-b.left)-b.width-1)/e;c=(2*(c-b.top)-b.height-1)/e;a="x"===this.opts.limitAxis?0:a/this.opts.ballsize;c="y"===this.opts.limitAxis?0:c/this.opts.ballsize; e=this.opts.border;b=1+e;e*=1+e/2;const k=(a*a+c*c)*b*b,f=Math.sqrt(k);return f<2/(b+1/b)?[a,c,Math.sqrt(1-k)]:f<b?(b-=f,[a,c,e-Math.sqrt((e+b)*(e-b))]):[a,c,0]}g.prototype={scene:null,opts:null,drag:null,rafPending:!1,lastPointer:null,inertiaID:0,velocityAxis:null,velocity:0,q:null,p:null,angleChange:0,angle:0,spinAxis:null,spinBase:null,t0:0,t1:0,lastDQ:null,_onPointerDown:null,_onPointerMove:null,_onPointerUp:null,rotate:function(a){this.drag||this.inertiaID||(this.q=this.p=this.p.mul(a),h.call(this))}, setQuaternion:function(a){l.call(this);this.drag=null;this.q=this.p=a;h.call(this)},setActiveArea:function(a){this.opts.activeArea=a},setEnabled:function(a){this.opts.enabled=!!a},dispose:function(){l.call(this);this.scene.removeEventListener("pointerdown",this._onPointerDown);document.removeEventListener("pointermove",this._onPointerMove);document.removeEventListener("pointerup",this._onPointerUp);document.removeEventListener("pointercancel",this._onPointerUp)}};"function"===typeof define&&define.amd? define([],function(){return g}):"object"===typeof exports?(Object.defineProperty(g,"__esModule",{value:!0}),g["default"]=g,g.Trackball=g,module.exports=g):v.Trackball=g})(this);