Here is a slightly different approach that, for each zoom-out operation, calculates how many further operations would be required to restore the default scale (1x) and, according to this, shifts the canvas view proportionally towards its initial position
Credit to Christian C. Salvadó for the augmented Math.Log function to allow the specification of a base!
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/[email protected]/konva.min.js"></script>
<meta charset="utf-8" />
<title>Konva Zoom Relative to Stage Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
var layer = new Konva.Layer();
stage.add(layer);
var circle = new Konva.Rect({
x: stage.width() / 2,
y: stage.height() / 2,
width: 50,
height: 50,
fill: 'green',
});
layer.add(circle);
layer.draw();
const scaleFactor = 1.03;
stage.addEventListener("wheel", (e) => zoomStage(e));
function zoomStage(event) {
event.preventDefault();
var oldScale = stage.scaleX();
var oldPos = stage.getPointerPosition();
var zoomIn = event.deltaY < 0;
var scaleMult = zoomIn ? scaleFactor : 1 / scaleFactor;
var newScale = Math.max(oldScale * scaleMult, 1);
var scaleDelta = newScale / oldScale;
stage.scale({ x: newScale, y: newScale });
if (zoomIn) {
stage.position({
x: oldPos.x * (1 - scaleDelta) + stage.x() * scaleDelta,
y: oldPos.y * (1 - scaleDelta) + stage.y() * scaleDelta,
});
} else {
var timesScaled = Math.round(Math.log(newScale, scaleFactor));
var positionScaleFactor = timesScaled / (timesScaled + 1);
stage.position({
x: stage.x() * positionScaleFactor,
y: stage.y() * positionScaleFactor,
});
}
stage.batchDraw();
}
// https://stackoverflow.com/a/3019319
Math.log = (function() {
var log = Math.log;
return function(n, base) {
return log(n) / (base ? log(base) : 1);
};
})();
</script>
</body>
</html>