Old question, but recently posted a tutorial and source code for this after coming up short with any good answers. The tutorial covers zoom, pan and drag + drop. The code uses Angular (for useful data binding), but the principles are the same in vanilla JavaScript:
Link to source code: https://github.com/malcolmswaine/SVG-Zoom-Pan-Drag-Drop-Angular
Link to tutorial: https://youtu.be/lr-AeQa4FiY
Hope that helps (and it's okay to post these links here)
Zoom function excerpt:
mouseWheel(wheelEvent: WheelEvent) {
const zoomScale = 1.1;
wheelEvent.stopPropagation();
let zoomX = wheelEvent.offsetX;
let zoomY = wheelEvent.offsetY;
console.log("zoomX", zoomX, "zoomY", zoomY)
let zoomDirection = wheelEvent.deltaY;
let scaledViewboxWidth
let scaledViewboxHeight
let scaledViewboxX
let scaledViewboxY
let zoomLeftFraction = zoomX / this.svgGrid.nativeElement.clientWidth;
let zoomTopFraction = zoomY / this.svgGrid.nativeElement.clientHeight;
console.log("zoomLeftFraction", zoomLeftFraction, "zoomTopFraction", zoomTopFraction)
let [viewboxX, viewboxY, viewboxWidth, viewboxHeight] = this.svgGrid.nativeElement.getAttribute('viewBox')
.split(' ')
.map(s => parseFloat(s))
if(zoomDirection > 0) {
scaledViewboxWidth = viewboxWidth / zoomScale;
scaledViewboxHeight = viewboxHeight / zoomScale;
scaledViewboxX = viewboxX + ((viewboxWidth - scaledViewboxWidth) * zoomLeftFraction)
scaledViewboxY = viewboxY + ((viewboxHeight - scaledViewboxHeight) * zoomTopFraction)
}
else {
scaledViewboxWidth = viewboxWidth * zoomScale;
scaledViewboxHeight = viewboxHeight * zoomScale;
scaledViewboxX = viewboxX - ((scaledViewboxWidth - viewboxWidth) * zoomLeftFraction)
scaledViewboxY = viewboxY - ((scaledViewboxHeight - viewboxHeight) * zoomTopFraction)
}
const scaledViewbox = [scaledViewboxX, scaledViewboxY, scaledViewboxWidth, scaledViewboxHeight]
.map(s => s.toFixed(2))
.join(' ')
this.svgGrid.nativeElement.setAttribute('viewBox',scaledViewbox )
this.currentViewboxToSvgRatio = scaledViewboxWidth / this.svgGrid.nativeElement.clientWidth;
}
Template:
<h1>SVG drag drop</h1>
<div style="padding: 20px">
<svg style="border: 1px solid green"
#svgGrid
(pointerdown)="pointerDown($event, svgGrid, dragOperationTypes.Grid)"
(pointermove)="pointerMove($event)"
(pointerup)="pointerUp($event)"
(wheel)="mouseWheel($event)"
viewBox="0, 0, 500, 500"
width="500" height="500">
<g *ngFor="let index of [].constructor(39); let i = index">
<path stroke="green" stroke-width="0.2" [attr.d]="'M ' + ((i+1) * 50) + ' 0 V 2000'"></path>
<text font-size="10" [attr.x]="(i * 50) + 30" y="10">{{(i + 1) * 50}}</text>
</g>
<g *ngFor="let index of [].constructor(39); let i = index">
<path stroke="green" stroke-width="0.2" [attr.d]="'M 0 ' + ((i+1) * 50) + ' H 2000'"></path>
<text font-size="10" [attr.y]="(i * 50) + 45" x="10">{{(i + 1) * 50}}</text>
</g>
<rect x="200" y="200" width="100" height="100"
fill="blue" class="draggable" #rect1
(pointerdown)="pointerDown($event, rect1, dragOperationTypes.Shape)"></rect>
</svg>
</div>