Zoom in and out on mouse click with CSS
Let's use a trick here, an input checkbox:
input[type=checkbox] {
display: none;
}
.container img {
margin: 100px;
transition: transform 0.25s ease;
cursor: zoom-in;
}
input[type=checkbox]:checked ~ label > img {
transform: scale(2);
cursor: zoom-out;
}
<div class="container">
<input type="checkbox" id="zoomCheck">
<label for="zoomCheck">
<img src="https://via.placeholder.com/200">
</label>
</div>
Javascript zoom in/out to mouse x/y coordinates
You issue is mostly around the below lines
const x = Math.round(divMain.scrollLeft * scaleChange)
const y = Math.round(divMain.scrollTop * scaleChange)
The way scroll with scale works like below
- Calculate the unscaled
x, y
coordinate where zoom happens - Calculate the new scaled
x, y
coordinate my multiplying it with the new scale - Now you want this new coordinate remain at the same place where the existing coordinate was. So basically if you subtract the
offset x,y
from the newscaled x,y
, you get the scroll left and top.
The updated code is like below
for (const divMain of document.getElementsByClassName('main')) {
// drag the section
for (const divSection of divMain.getElementsByClassName('section')) {
// when mouse is pressed store the current mouse x,y
let previousX, previousY
divSection.addEventListener('mousedown', (event) => {
previousX = event.pageX
previousY = event.pageY
})
// when mouse is moved, scrollBy() the mouse movement x,y
divSection.addEventListener('mousemove', (event) => {
// only do this when the primary mouse button is pressed (event.buttons = 1)
if (event.buttons) {
let dragX = 0
let dragY = 0
// skip the drag when the x position was not changed
if (event.pageX - previousX !== 0) {
dragX = previousX - event.pageX
previousX = event.pageX
}
// skip the drag when the y position was not changed
if (event.pageY - previousY !== 0) {
dragY = previousY - event.pageY
previousY = event.pageY
}
// scrollBy x and y
if (dragX !== 0 || dragY !== 0) {
divMain.scrollBy(dragX, dragY)
}
}
})
}
// zoom in/out on the section
let scale = 1
const factor = 0.05
const max_scale =4
divMain.addEventListener('wheel', (e) => {
// preventDefault to stop the onselectionstart event logic
for (const divSection of divMain.getElementsByClassName('section')) {
e.preventDefault();
var delta = e.delta || e.wheelDelta;
if (delta === undefined) {
//we are on firefox
delta = e.originalEvent.detail;
}
delta = Math.max(-1,Math.min(1,delta)) // cap the delta to [-1,1] for cross browser consistency
offset = {x: divMain.scrollLeft, y: divMain.scrollTop};
image_loc = {
x: e.pageX + offset.x,
y: e.pageY + offset.y
}
zoom_point = {x:image_loc.x/scale, y: image_loc.y/scale}
// apply zoom
scale += delta*factor * scale
scale = Math.max(1,Math.min(max_scale,scale))
zoom_point_new = {x:zoom_point.x * scale, y: zoom_point.y * scale}
newScroll = {
x: zoom_point_new.x - e.pageX,
y: zoom_point_new.y - e.pageY
}
divSection.style.transform = `scale(${scale}, ${scale})`
divMain.scrollTop = newScroll.y
divMain.scrollLeft = newScroll.x
}
})
}
The updated fiddle is
https://jsfiddle.net/uy390v8t/1/
Zoom image in/out on mouse point using wheel with transform origin center. Need help in calculation
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>
Zoom/scale at mouse position
Use the canvas for zoomable content
Zooming and panning elements is very problematic. It can be done but the list of issues is very long. I would never implement such an interface.
Consider using the canvas, via 2D or WebGL to display such content to save your self many many problems.
The first part of the answer is implemented using the canvas. The same interface view
is used in the second example that pans and zooms an element.
A simple 2D view.
As you are only panning and zooming then a very simple method can be used.
The example below implements an object called view. This holds the current scale and position (pan)
It provides two function for user interaction.
- Panning the function
view.pan(amount)
will pan the view by distance in pixels held byamount.x
,amount.y
- Zooming the function
view.scaleAt(at, amount)
will scale (zoom in out) the view byamount
(a number representing change in scale), at the position held byat.x
,at.y
in pixels.
In the example the view is applied to the canvas rendering context using view.apply()
and a set of random boxes are rendered whenever the view changes.
The panning and zooming is via mouse events
Example using canvas 2D context
Use mouse button drag to pan, wheel to zoom
const ctx = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 500;
const rand = (m = 255, M = m + (m = 0)) => (Math.random() * (M - m) + m) | 0;
const objects = [];
for (let i = 0; i < 100; i++) {
objects.push({x: rand(canvas.width), y: rand(canvas.height),w: rand(40),h: rand(40), col: `rgb(${rand()},${rand()},${rand()})`});
}
requestAnimationFrame(drawCanvas);
const view = (() => {
const matrix = [1, 0, 0, 1, 0, 0]; // current view transform
var m = matrix; // alias
var scale = 1; // current scale
var ctx; // reference to the 2D context
const pos = { x: 0, y: 0 }; // current position of origin
var dirty = true;
const API = {
set context(_ctx) { ctx = _ctx; dirty = true },
apply() {
if (dirty) { this.update() }
ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5])
},
get scale() { return scale },
get position() { return pos },
isDirty() { return dirty },
update() {
dirty = false;
m[3] = m[0] = scale;
m[2] = m[1] = 0;
m[4] = pos.x;
m[5] = pos.y;
},
pan(amount) {
if (dirty) { this.update() }
pos.x += amount.x;
pos.y += amount.y;
dirty = true;
},
scaleAt(at, amount) { // at in screen coords
if (dirty) { this.update() }
scale *= amount;
pos.x = at.x - (at.x - pos.x) * amount;
pos.y = at.y - (at.y - pos.y) * amount;
dirty = true;
},
};
return API;
})();
view.context = ctx;
function drawCanvas() {
if (view.isDirty()) {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
view.apply(); // set the 2D context transform to the view
for (i = 0; i < objects.length; i++) {
var obj = objects[i];
ctx.fillStyle = obj.col;
ctx.fillRect(obj.x, obj.y, obj.h, obj.h);
}
}
requestAnimationFrame(drawCanvas);
}
canvas.addEventListener("mousemove", mouseEvent, {passive: true});
canvas.addEventListener("mousedown", mouseEvent, {passive: true});
canvas.addEventListener("mouseup", mouseEvent, {passive: true});
canvas.addEventListener("mouseout", mouseEvent, {passive: true});
canvas.addEventListener("wheel", mouseWheelEvent, {passive: false});
const mouse = {x: 0, y: 0, oldX: 0, oldY: 0, button: false};
function mouseEvent(event) {
if (event.type === "mousedown") { mouse.button = true }
if (event.type === "mouseup" || event.type === "mouseout") { mouse.button = false }
mouse.oldX = mouse.x;
mouse.oldY = mouse.y;
mouse.x = event.offsetX;
mouse.y = event.offsetY
if(mouse.button) { // pan
view.pan({x: mouse.x - mouse.oldX, y: mouse.y - mouse.oldY});
}
}
function mouseWheelEvent(event) {
var x = event.offsetX;
var y = event.offsetY;
if (event.deltaY < 0) { view.scaleAt({x, y}, 1.1) }
else { view.scaleAt({x, y}, 1 / 1.1) }
event.preventDefault();
}
body {
background: gainsboro;
margin: 0;
}
canvas {
background: white;
box-shadow: 1px 1px 1px rgba(0, 0, 0, .2);
}
<canvas id="canvas"></canvas>
CSS cursor zoom-in/out
For Chrome you have to use the prefixed-property-value:
.button.zoom-in {
...
cursor: -webkit-zoom-in;
cursor: zoom-in;
}
Here is a jsfiddle.
Related Topics
Problem Applying CSS in Django with Static-Files App
CSS Style Not Recognizing Numbers
Different Behaviours for Col-Lg
My Dropkick Plugin's Dropdown Looking Strange
Skew The Shadow, Not The Content
How to Show a <Div> by Clicking an Image in CSS
List-Style-Type:None Not Working! Get Rid of The Bullets
Extension Content Script (Js/Or CSS) Is Not Applying
How to Change CSS in Rmarkdown Cell & Shiny
What Is The 'Best Practice' Way of Creating an Icon Button with an Svg
Primefaces Elements Can't Load a Url of My CSS File
Displaying Third Tier Submenus Properly with CSS Only Menu
Auto Grid Columns, Without Wrapping to Next Row
Absolutely True Centred Background Image
Browser Developer Tools: What Is The Position of The HTML Element