fabric-guideline-plugin
Version:
🤩 Help you easily append guidelines and auto-snap to your fabric.js canvas.
2 lines (1 loc) • 5.77 kB
JavaScript
(function(u,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("fabric")):typeof define=="function"&&define.amd?define(["exports","fabric"],c):(u=typeof globalThis<"u"?globalThis:u||self,c(u["fabric-guideline"]={},u.fabric))})(this,function(u,c){"use strict";const f=C=>Object.keys(C);class T{aligningLineMargin=4;aligningLineWidth=.75;aligningLineColor="#F68066";ignoreObjTypes=[];pickObjTypes=[];canvas;ctx;viewportTransform;verticalLines=[];horizontalLines=[];activeObj=new c.fabric.Object;constructor({canvas:t,aligningOptions:i,ignoreObjTypes:e,pickObjTypes:s}){this.canvas=t,this.ctx=t.getSelectionContext(),this.ignoreObjTypes=e||[],this.pickObjTypes=s||[],i&&(this.aligningLineMargin=i.lineMargin||this.aligningLineMargin,this.aligningLineWidth=i.lineWidth||this.aligningLineWidth,this.aligningLineColor=i.lineColor||this.aligningLineColor)}drawSign(t,i){const e=this.ctx;e.lineWidth=.5,e.strokeStyle=this.aligningLineColor,e.beginPath();const s=2;e.moveTo(t-s,i-s),e.lineTo(t+s,i+s),e.moveTo(t+s,i-s),e.lineTo(t-s,i+s),e.stroke()}drawLine(t,i,e,s){const a=this.ctx,n=c.fabric.util.transformPoint(new c.fabric.Point(t,i),this.canvas.viewportTransform),r=c.fabric.util.transformPoint(new c.fabric.Point(e,s),this.canvas.viewportTransform);a.save(),a.lineWidth=this.aligningLineWidth,a.strokeStyle=this.aligningLineColor,a.beginPath(),a.moveTo(n.x,n.y),a.lineTo(r.x,r.y),a.stroke(),this.drawSign(n.x,n.y),this.drawSign(r.x,r.y),a.restore()}drawVerticalLine(t){const i=this.getObjDraggingObjCoords(this.activeObj);!f(i).some(e=>Math.abs(i[e].x-t.x)<1e-4)||this.drawLine(t.x,Math.min(t.y1,t.y2),t.x,Math.max(t.y1,t.y2))}drawHorizontalLine(t){const i=this.getObjDraggingObjCoords(this.activeObj);!f(i).some(e=>Math.abs(i[e].y-t.y)<1e-4)||this.drawLine(Math.min(t.x1,t.x2),t.y,Math.max(t.x1,t.x2),t.y)}isInRange(t,i){return Math.abs(Math.round(t)-Math.round(i))<=this.aligningLineMargin/this.canvas.getZoom()}watchMouseDown(){this.canvas.on("mouse:down",()=>{this.clearLinesMeta(),this.viewportTransform=this.canvas.viewportTransform})}watchMouseUp(){this.canvas.on("mouse:up",()=>{this.clearLinesMeta(),this.canvas.renderAll()})}watchMouseWheel(){this.canvas.on("mouse:wheel",()=>{this.clearLinesMeta()})}clearLinesMeta(){this.verticalLines.length=this.horizontalLines.length=0}watchObjectMoving(){this.canvas.on("object:moving",t=>{this.clearLinesMeta();const i=t.target;this.activeObj=i;const e=this.canvas.getObjects().filter(a=>this.ignoreObjTypes.length?!this.ignoreObjTypes.some(n=>a[n.key]===n.value):this.pickObjTypes.length?this.pickObjTypes.some(n=>a[n.key]===n.value):!0);!this.canvas._currentTransform||this.traversAllObjects(i,e)})}getObjDraggingObjCoords(t){const i=t.aCoords,e=new c.fabric.Point((i.tl.x+i.br.x)/2,(i.tl.y+i.br.y)/2),s=e.x-t.getCenterPoint().x,a=e.y-t.getCenterPoint().y;return f(i).reduce((n,r)=>({...n,[r]:{x:i[r].x-s,y:i[r].y-a}}),{c:t.getCenterPoint()})}omitCoords(t,i){let e;if(i==="vertical"){let s=["tl",t.tl],a=["tl",t.tl];f(t).forEach(n=>{t[n].x<s[1].x&&(s=[n,t[n]]),t[n].x>a[1].x&&(a=[n,t[n]])}),e={[s[0]]:s[1],[a[0]]:a[1],c:t.c}}else{let s=["tl",t.tl],a=["tl",t.tl];f(t).forEach(n=>{t[n].y<s[1].y&&(s=[n,t[n]]),t[n].y>a[1].y&&(a=[n,t[n]])}),e={[s[0]]:s[1],[a[0]]:a[1],c:t.c}}return e}getObjMaxWidthHeightByCoords(t){const i=Math.max(Math.abs(t.c.y-t.tl.y),Math.abs(t.c.y-t.tr.y))*2,e=Math.max(Math.abs(t.c.x-t.tl.x),Math.abs(t.c.x-t.tr.x))*2;return{objHeight:i,objWidth:e}}calcCenterPointByACoords(t){return new c.fabric.Point((t.tl.x+t.br.x)/2,(t.tl.y+t.br.y)/2)}traversAllObjects(t,i){const e=this.getObjDraggingObjCoords(t),s=[],a=[];for(let n=i.length;n--;){if(i[n]===t)continue;const r={...i[n].aCoords,c:i[n].getCenterPoint()},{objHeight:y,objWidth:L}=this.getObjMaxWidthHeightByCoords(r);f(e).forEach(o=>{const M=i[n].angle!==0?this.omitCoords(r,"horizontal"):r;function m(h,l){let g,x;return h==="c"?(g=Math.min(r.c.x-L/2,l[o].x),x=Math.max(r.c.x+L/2,l[o].x)):(g=Math.min(r[h].x,l[o].x),x=Math.max(r[h].x,l[o].x)),{x1:g,x2:x}}f(M).forEach(h=>{if(this.isInRange(e[o].y,r[h].y)){const l=r[h].y;let{x1:g,x2:x}=m(h,e);const w=e[o].y-l;if(a.push(e.c.y-w),t.aCoords){let{x1:p,x2:d}=m(h,{...t.aCoords,c:this.calcCenterPointByACoords(t.aCoords)});this.horizontalLines.push({y:l,x1:p,x2:d})}else this.horizontalLines.push({y:l,x1:g,x2:x})}})}),f(e).forEach(o=>{const M=i[n].angle!==0?this.omitCoords(r,"vertical"):r;function m(h,l){let g,x;return h==="c"?(g=Math.min(M.c.y-y/2,l[o].y),x=Math.max(M.c.y+y/2,l[o].y)):(g=Math.min(r[h].y,l[o].y),x=Math.max(r[h].y,l[o].y)),{y1:g,y2:x}}f(M).forEach(h=>{if(this.isInRange(e[o].x,r[h].x)){const l=r[h].x;let{y1:g,y2:x}=m(h,e);const w=e[o].x-l;if(s.push(e.c.x-w),t.aCoords){let{y1:p,y2:d}=m(h,{...t.aCoords,c:this.calcCenterPointByACoords(t.aCoords)});this.verticalLines.push({x:l,y1:p,y2:d})}else this.verticalLines.push({x:l,y1:g,y2:x})}})}),this.snap({activeObject:t,draggingObjCoords:e,snapXPoints:s,snapYPoints:a})}}snap({activeObject:t,snapXPoints:i,draggingObjCoords:e,snapYPoints:s}){const a=(n,r)=>n.length?n.map(y=>({abs:Math.abs(r-y),val:y})).sort((y,L)=>y.abs-L.abs)[0].val:r;t.setPositionByOrigin(new c.fabric.Point(a(i,e.c.x),a(s,e.c.y)),"center","center")}clearGuideline(){this.canvas.clearContext(this.ctx)}watchRender(){this.canvas.on("before:render",()=>{this.clearGuideline()}),this.canvas.on("after:render",()=>{for(let t=this.verticalLines.length;t--;)this.drawVerticalLine(this.verticalLines[t]);for(let t=this.horizontalLines.length;t--;)this.drawHorizontalLine(this.horizontalLines[t]);this.canvas.calcOffset()})}init(){this.watchObjectMoving(),this.watchRender(),this.watchMouseDown(),this.watchMouseUp(),this.watchMouseWheel()}}u.AlignGuidelines=T,Object.defineProperties(u,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});