CONTEXT :
PROBLEM :
Can someone assist in optimizing this calculation further so that it works with default transform origin ?
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.container {
background-color: lightgrey;
}
.stage {
height: 100%;
width: 100%;
overflow: hidden;
}
#image {
transform-origin: 0 0;
height: auto;
width: 80%;
cursor: grab;
}
.actions {
display: flex;
position: absolute;
bottom: 0;
left: 0;
height: 1.5rem;
width: 100%;
background-color: lightgrey;
}
.action {
margin-right: 1rem;
}
</style>
</head>
<body>
<div class="container">
<div class="stage">
<img id="image" src="https://cdn.pixabay.com/photo/2018/01/14/23/12/nature-3082832__480.jpg" />
</div>
<div class="actions">
<div class="action">
<label for="rotate">Rotate </label>
<input type="range" id="rotate" name="rotate" min="0" max="360">
</div>
</div>
</div>
<script>
const img = document.getElementById('image');
const rotate = document.getElementById('rotate');
let mouseX;
let mouseY;
let mouseTX;
let mouseTY;
let startXOffset = 222.6665;
let startYOffset = 224.713;
let startX = 0;
let startY = 0;
let panning = false;
const ts = {
scale: 1,
rotate: 0,
translate: {
x: 0,
y: 0
}
};
rotate.oninput = function(event) {
event.preventDefault();
ts.rotate = event.target.value;
setTransform();
};
img.onwheel = function(event) {
event.preventDefault();
let xs = (event.clientX - ts.translate.x) / ts.scale;
let ys = (event.clientY - ts.translate.y) / ts.scale;
let delta = (event.wheelDelta ? event.wheelDelta : -event.deltaY);
ts.scale = (delta > 0) ? (ts.scale * 1.2) : (ts.scale / 1.2);
ts.translate.x = event.clientX - xs * ts.scale;
ts.translate.y = event.clientY - ys * ts.scale;
setTransform();
};
img.onmousedown = function(event) {
event.preventDefault();
panning = true;
img.style.cursor = 'grabbing';
mouseX = event.clientX;
mouseY = event.clientY;
mouseTX = ts.translate.x;
mouseTY = ts.translate.y;
};
img.onmouseup = function(event) {
panning = false;
img.style.cursor = 'grab';
};
img.onmousemove = function(event) {
event.preventDefault();
const x = event.clientX;
const y = event.clientY;
pointX = (x - startX);
pointY = (y - startY);
if (!panning) {
return;
}
ts.translate.x =
mouseTX + (x - mouseX);
ts.translate.y =
mouseTY + (y - mouseY);
setTransform();
};
function setTransform() {
const steps = `translate(${ts.translate.x}px,${ts.translate.y}px) scale(${ts.scale}) rotate(${ts.rotate}deg)`;
img.style.transform = steps;
}
</script>
</body>
</html>
How to scale the image on mouse point if the transform origin is defaulted to 50% 50% by default ?
To shift origin to 50% 50% we need x,y position of mouse, relative to the image i.e. image top left as origin till image bottom right. Then we compensate image position by using the relative coordinates. We need to consider image dimensions as well.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<style>
.container {
background-color: lightgrey;
}
.stage {
height: 90vh;
width: 100%;
overflow: hidden;
}
#image {
transform-origin: 50% 50%;
height: auto;
width: 40%;
cursor: grab;
}
.actions {
display: flex;
position: absolute;
bottom: 0;
left: 0;
height: 1.5rem;
width: 100%;
background-color: lightgrey;
}
.action {
margin-right: 1rem;
}
</style>
</head>
<body>
<div class="container">
<div class="stage">
<img id="image" src="https://cdn.pixabay.com/photo/2018/01/14/23/12/nature-3082832__480.jpg" />
</div>
<div class="actions">
<div class="action">
<label for="rotate">Rotate </label>
<input type="range" id="rotate" name="rotate" min="0" max="360">
<button onclick="reset()">Reset All</button>
</div>
</div>
</div>
<script>
const img = document.getElementById('image');
const rotate = document.getElementById('rotate');
let mouseX;
let mouseY;
let mouseTX;
let mouseTY;
let startXOffset = 222.6665;
let startYOffset = 224.713;
let startX = 0;
let startY = 0;
let panning = false;
const ts = {
scale: 1,
rotate: 0,
translate: {
x: 0,
y: 0
}
};
rotate.oninput = function(event) {
event.preventDefault();
ts.rotate = event.target.value;
setTransform();
};
img.onwheel = function(event) {
event.preventDefault();
//need more handling to avoid fast scrolls
var func = img.onwheel;
img.onwheel = null;
let rec = img.getBoundingClientRect();
let x = (event.clientX - rec.x) / ts.scale;
let y = (event.clientY - rec.y) / ts.scale;
let delta = (event.wheelDelta ? event.wheelDelta : -event.deltaY);
ts.scale = (delta > 0) ? (ts.scale + 0.2) : (ts.scale - 0.2);
//let m = (ts.scale - 1) / 2;
let m = (delta > 0) ? 0.1 : -0.1;
ts.translate.x += (-x * m * 2) + (img.offsetWidth * m);
ts.translate.y += (-y * m * 2) + (img.offsetHeight * m);
setTransform();
img.onwheel = func;
};
img.onmousedown = function(event) {
event.preventDefault();
panning = true;
img.style.cursor = 'grabbing';
mouseX = event.clientX;
mouseY = event.clientY;
mouseTX = ts.translate.x;
mouseTY = ts.translate.y;
};
img.onmouseup = function(event) {
panning = false;
img.style.cursor = 'grab';
};
img.onmousemove = function(event) {
event.preventDefault();
let rec = img.getBoundingClientRect();
let xx = event.clientX - rec.x;
let xy = event.clientY - rec.y;
const x = event.clientX;
const y = event.clientY;
pointX = (x - startX);
pointY = (y - startY);
if (!panning) {
return;
}
ts.translate.x =
mouseTX + (x - mouseX);
ts.translate.y =
mouseTY + (y - mouseY);
setTransform();
};
function setTransform() {
const steps = `translate(${ts.translate.x}px,${ts.translate.y}px) scale(${ts.scale}) rotate(${ts.rotate}deg) translate3d(0,0,0)`;
//console.log(steps);
img.style.transform = steps;
}
function reset() {
ts.scale = 1;
ts.rotate = 0;
ts.translate = {
x: 0,
y: 0
};
rotate.value = 180;
img.style.transform = 'none';
}
setTransform();
</script>
</body>
</html>
To make things simple I am adding 0.2 to the scale instead of multiplying with 1.2. You can change it back with right proportions. Also added a reset button.
Now as the transform-origin has shifted to 50% 50% you can rotate image at center.