@niuee/board
Version:
<h1 align="center"> board </h1> <p align="center"> board supercharges your html canvas element giving it the capabilities to pan, zoom, rotate, and much more. </p> <p align="center"> <a href="https://www.npmjs.com/package/@niuee/board">
3 lines (2 loc) • 7.04 kB
JavaScript
import{PointCal}from"point2point";class Observable{constructor(){this.observers=[]}subscribe(observer,options){if(this.observers.push(observer),null==options?void 0:options.signal){if(options.signal.aborted)return this.observers=this.observers.filter((o=>o!==observer)),()=>{};const abortHandler=()=>{var _a;this.observers=this.observers.filter((o=>o!==observer)),null===(_a=options.signal)||void 0===_a||_a.removeEventListener("abort",abortHandler)};options.signal.addEventListener("abort",abortHandler)}return()=>{this.observers=this.observers.filter((o=>o!==observer))}}notify(...data){this.observers.forEach((observer=>queueMicrotask((()=>observer(...data)))))}}class CameraUpdatePublisher{constructor(){this.pan=new Observable,this.zoom=new Observable,this.rotate=new Observable,this.all=new Observable}notifyPan(event,cameraState){this.pan.notify(event,cameraState),this.all.notify({type:"pan",diff:event.diff},cameraState)}notifyZoom(event,cameraState){this.zoom.notify(event,cameraState),this.all.notify({type:"zoom",deltaZoomAmount:event.deltaZoomAmount},cameraState)}notifyRotate(event,cameraState){this.rotate.notify(event,cameraState),this.all.notify({type:"rotate",deltaRotation:event.deltaRotation},cameraState)}on(eventName,callback,options){switch(eventName){case"pan":return this.pan.subscribe(callback,options);case"zoom":return this.zoom.subscribe(callback,options);case"rotate":return this.rotate.subscribe(callback,options);case"all":return this.all.subscribe(callback,options);default:throw new Error(`Invalid event name: ${eventName}`)}}}function normalizeAngleZero2TwoPI(angle){return angle=((angle%=2*Math.PI)+2*Math.PI)%(2*Math.PI)}function angleSpan(from,to){from=normalizeAngleZero2TwoPI(from);let angleDiff=(to=normalizeAngleZero2TwoPI(to))-from;return angleDiff>Math.PI&&(angleDiff=-(2*Math.PI-angleDiff)),angleDiff<-Math.PI&&(angleDiff+=2*Math.PI),angleDiff}class ContextCentricCamera{constructor(position={x:0,y:0},rotation=0,zoomLevel=1,viewPortWidth=1e3,viewPortHeight=1e3,observer=new CameraUpdatePublisher,boundaries={min:{x:-1e4,y:-1e4},max:{x:1e4,y:1e4}},zoomLevelBoundaries={min:.1,max:10},rotationBoundaries={start:0,end:2*Math.PI,ccw:!0,startAsTieBreaker:!1}){this._contextRotation=-rotation,this._zoomLevel=zoomLevel,this._contextPosition=PointCal.subVector({x:viewPortWidth/2,y:viewPortHeight/2},PointCal.multiplyVectorByScalar(PointCal.rotatePoint(position,-rotation),zoomLevel)),this._viewPortWidth=viewPortWidth,this._viewPortHeight=viewPortHeight,this._observer=observer,this._boundaries=boundaries,this._rotationBoundaries=rotationBoundaries,this._zoomBoundaries=zoomLevelBoundaries}get position(){const x=(this._viewPortWidth/2-this._contextPosition.x)/this._zoomLevel,y=(this._viewPortHeight/2-this._contextPosition.y)/this._zoomLevel;return PointCal.rotatePoint({x:x,y:y},-this._contextRotation)}get contextTransform(){return{position:this._contextPosition,rotation:this._contextRotation,zoomLevel:this._zoomLevel}}setPosition(destination){if(function(point,boundaries){if(null==boundaries)return!0;let leftSide=!1,rightSide=!1,topSide=!1,bottomSide=!1;return(null==boundaries.max||null==boundaries.max.x||point.x<=boundaries.max.x)&&(rightSide=!0),(null==boundaries.min||null==boundaries.min.x||point.x>=boundaries.min.x)&&(leftSide=!0),(null==boundaries.max||null==boundaries.max.y||point.y<=boundaries.max.y)&&(topSide=!0),(null==boundaries.min||null==boundaries.min.y||point.y>=boundaries.min.y)&&(bottomSide=!0),leftSide&&rightSide&&topSide&&bottomSide}(destination,this._boundaries)){const destinationInAbsoluteCoordinate=PointCal.subVector({x:this._viewPortWidth/2,y:this._viewPortHeight/2},PointCal.multiplyVectorByScalar(PointCal.rotatePoint(destination,-this.rotation),this._zoomLevel));this._contextPosition=destinationInAbsoluteCoordinate}}setPositionByDelta(delta){const destination=PointCal.addVector(this.position,delta);this.setPosition(destination)}setPositionWithUserInputDelta(delta){this._contextPosition=PointCal.addVector(this._contextPosition,delta)}getCameraOriginInWindow(centerInWindow){return{x:centerInWindow.x-this._viewPortWidth/2,y:centerInWindow.y-this._viewPortHeight/2}}convertFromViewPort2WorldSpace(point){const convertedPoint={x:point.x+this._viewPortWidth/2,y:point.y+this._viewPortHeight/2};return PointCal.multiplyVectorByScalar(PointCal.rotatePoint(PointCal.subVector(convertedPoint,this._contextPosition),-this._contextRotation),1/this._zoomLevel)}setZoomLevel(zoomLevel){(function(zoomLevel,zoomLevelLimits){return void 0===zoomLevelLimits||!(zoomLevel<=0||void 0!==zoomLevelLimits&&(void 0!==zoomLevelLimits.max&&zoomLevelLimits.max<zoomLevel||void 0!==zoomLevelLimits.min&&zoomLevelLimits.min>zoomLevel))})(zoomLevel,this._zoomBoundaries)&&(this._zoomLevel,this._zoomLevel=zoomLevel)}setRotation(rotation){const destination=-rotation;(function(rotation,rotationLimits){if(void 0===rotationLimits)return!0;if(normalizeAngleZero2TwoPI(rotationLimits.start)===normalizeAngleZero2TwoPI(rotationLimits.end))return!0;const normalizedRotation=normalizeAngleZero2TwoPI(rotation),angleSpanFromStart=angleSpan(rotationLimits.start,normalizedRotation),angleSpanFromEnd=angleSpan(rotationLimits.end,normalizedRotation);return!(rotationLimits.ccw&&(angleSpanFromStart<0||angleSpanFromEnd>0)||!rotationLimits.ccw&&(angleSpanFromStart>0||angleSpanFromEnd<0))})(destination,this._rotationBoundaries)&&(this._contextRotation=destination)}get zoomLevel(){return this._zoomLevel}get rotation(){return-this._contextRotation}getTransform(canvasWidth,canvasHeight,devicePixelRatio,alignCoorindate){const e=this._contextPosition.x*devicePixelRatio,f=this._contextPosition.y*devicePixelRatio,c=-Math.sin(this._contextRotation)*this._zoomLevel*devicePixelRatio;return{a:this._zoomLevel*Math.cos(this._contextRotation)*devicePixelRatio,b:Math.sin(this._contextRotation)*this._zoomLevel*devicePixelRatio,c:c,d:this._zoomLevel*Math.cos(this._contextRotation)*devicePixelRatio,e:e,f:f}}get viewPortWidth(){return this._viewPortWidth}get viewPortHeight(){return this._viewPortHeight}set viewportWidth(width){this._viewPortWidth=width}set viewportHeight(height){this._viewPortHeight=height}get zoomBoundaries(){return this._zoomBoundaries}get rotationBoundaries(){return this._rotationBoundaries}viewPortDelta2WorldDelta(delta){return delta}setMinZoomLevel(minZoomLevel){return null==this._zoomBoundaries&&(this._zoomBoundaries={min:void 0,max:void 0}),!(null!=this._zoomBoundaries.max&&this._zoomBoundaries.max<minZoomLevel)&&(this._zoomBoundaries.min=minZoomLevel,this._zoomLevel<minZoomLevel&&(this._zoomLevel=minZoomLevel),this._zoomBoundaries.min=minZoomLevel,!0)}setHorizontalBoundaries(min,max){this._boundaries.min.x=min,this._boundaries.max.x=max}setVerticalBoundaries(min,max){this._boundaries.min.y=min,this._boundaries.max.y=max}on(eventName,callback){return this._observer.on(eventName,callback)}set viewPortWidth(width){this._viewPortWidth=width}set viewPortHeight(height){this._viewPortHeight=height}}export{ContextCentricCamera};
//# sourceMappingURL=index.js.map