Zoom in and Out on Mouse Click with CSS

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 new scaled 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/

Results

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 by amount.x, amount.y
  • Zooming the function view.scaleAt(at, amount) will scale (zoom in out) the view by amount (a number representing change in scale), at the position held by at.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



Leave a reply



Submit