Hello I want to crop an image using canvas, but when the image is not on the top of the page, but has a margin or other elements before on the page, the result will get incorrect and has a wrong offset of the margin. I think it is a bad practice to substract the offset. Do I have to wrap the content in a special way or are my position attributes wrong?
the relevant function is cropMedia.
function cropMedia(media, {
stretch = 1,
left = 0,
top = 0,
width,
height
} = {}) {
const croppedCanvas = document.createElement('canvas');
croppedCanvas.width = width;
croppedCanvas.height = height;
const ctx = croppedCanvas.getContext('2d');
ctx.drawImage(media, left, top, width, height, 0, 0, width * stretch, height * stretch);
return croppedCanvas;
}
Here is my codepen for more relevant code: http://codepen.io/anon/pen/YqNaow
thank you ver much
Pay attention to mouseup. You try to get correct top and left values for fixed element, but its top and left properties of style calculated by the viewport of browser (not by the top and left of document). This is correct code.
class CanvasCrop {
constructor(media) {
let x1 = 0;
let y1 = 0;
let x2 = 0;
let y2 = 0;
let dragThreshold = 50;
let mousedown = false;
let dragging = false;
const selectionRect = document.getElementById('selectionRect');
function reCalc() {
var x3 = Math.min(x1, x2);
var x4 = Math.max(x1, x2);
var y3 = Math.min(y1, y2);
var y4 = Math.max(y1, y2);
selectionRect.style.left = x3 + 'px';
selectionRect.style.top = y3 + 'px';
selectionRect.style.width = x4 - x3 + 'px';
selectionRect.style.height = y4 - y3 + 'px';
}
function cropMedia(media, {
stretch = 1,
left = 0,
top = 0,
width,
height
} = {}) {
const croppedCanvas = document.createElement('canvas');
croppedCanvas.width = width;
croppedCanvas.height = height;
const ctx = croppedCanvas.getContext('2d');
ctx.drawImage(media, left, top, width, height, 0, 0, width * stretch, height * stretch);
return croppedCanvas;
}
media.onmousedown = function(e) {
mousedown = true;
selectionRect.hidden = 0;
x1 = e.clientX;
y1 = e.clientY;
};
onmousemove = function(e) {
//todo implement isDragging
if (mousedown) {
x2 = e.clientX;
y2 = e.clientY;
var deltaX = Math.abs(x2 - x1);
var deltaY = Math.abs(x2 - x1);
reCalc();
if (deltaX > dragThreshold || deltaY > dragThreshold) dragging = true;
}
};
onmouseup = (e) => {
var pic = document.getElementById('pic');
var offsetTop = pic.offsetTop;
var offsetLeft = pic.offsetLeft;
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
var scrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft;
scrollTop -= offsetTop;
scrollLeft -= offsetLeft;
selectionRect.hidden = 1;
mousedown = false;
if (dragging) {
dragging = false;
let croppedCanvas = cropMedia(media, {
left: parseInt(selectionRect.style.left, 10) + scrollLeft,
top: parseInt(selectionRect.style.top, 10) + scrollTop,
width: parseInt(selectionRect.style.width, 10),
height: parseInt(selectionRect.style.height, 10)
});
const preview = document.getElementById('preview');
preview.innerHTML = '';
preview.appendChild(croppedCanvas);
}
};
}
}
const cc = new CanvasCrop(document.getElementById('pic'));
Related
I want know how to figure out if the puzzle is solved.
I am creating an app for creating custom 6-piece puzzle.
Here's the code:
function approved(){
// Get the canvas and context
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// Load the image
var img = new Image();
img.src = document.getElementById('puzzleprieview').src;
img.onload = function() {
// Set the canvas size
canvas.width = img.width;
canvas.height = img.height;
// Draw the image on the canvas
ctx.drawImage(img, 0, 0);
// Split the image into parts
var w = img.width / 3;
var h = img.height / 2;
for (var i = 0; i < 7; i++) {
var x = (i % 3) * w;
var y = Math.floor(i / 3) * h;
// Create a new canvas for each part
var partCanvas = document.createElement("canvas");
partCanvas.draggable="true";
partCanvas.className="sampcanvas"
$(".sampcanvas").draggable({snap: true});
partCanvas.width = w;
partCanvas.height = h;
var partCtx = partCanvas.getContext("2d");
// Draw the part of the image on the new canvas
partCtx.drawImage(canvas, x, y, w, h, 0, 0, w, h);
var number=Math.floor(Math.random() * 100);
// Do something with each part, such as append it to the document
const getRandom = (min, max) => Math.floor(Math.random()*(max-min+1)+min);
partCanvas.style.left= getRandom(0, 300 - 200)+'px'; // ๐๐ผ Horizontally
partCanvas.style.top = getRandom(0, 300 - 200)+'px'; // ๐๐ผ Vertically
document.getElementById('pieces').appendChild(partCanvas);
}
}
}
That code is used to create puzzle pieces and append them.
What I need to do is somehow compare the puzzle piece solving to the canvas that is created
Well, great task in general and question in particular !
To achieve this purpose you could, f.e. put into each puzzlePart block it's initial position using "data-" attributes:
var x = (i % 3) * w;
var y = Math.floor(i / 3) * h;
// Create a new canvas for each part
let partCanvas = document.createElement('canvas');
partCanvas.dataset.xx = x;
partCanvas.dataset.yy = y;
Next, whenever needed, you are able to compare current position of puzzle parts with their initial ones. I took initial position from .dataset and current using .getBoundingClientRect():
var canvasOffsets = canvas.getBoundingClientRect();
var elementScreenOffsets = partCanvas.getBoundingClientRect();
var elementOffsets = {
x: elementScreenOffsets.left - canvasOffsets.left,
y: elementScreenOffsets.top - canvasOffsets.top,
};
var initialOffsets = {
x: partCanvas.dataset.xx,
y: partCanvas.dataset.yy,
};
As an option, you could for example "freeze" puzzle parts on the screen when they're moreless in their initial position:
var FREEZE_DISTASNCE = 30;
if (
Math.abs(elementOffsets.x - initialOffsets.x) <
FREEZE_DISTASNCE &&
Math.abs(elementOffsets.y - initialOffsets.y) <
FREEZE_DISTASNCE
) {
makeNotDraggable(partCanvas);
partCanvas.style.left = partCanvas.dataset.xx;
partCanvas.style.top = partCanvas.dataset.yy;
partCanvas.style.zIndex = -1;
}
So eventually I decided to modify initial question's code to make it fully working using my ideas above. Plus I decided to implement this POC with "generic" image which you could pick
For drag-n-drop implementation I referenced this article: https://www.w3schools.com/howto/howto_js_draggable.asp
File inputs Api: https://developer.mozilla.org/ru/docs/Web/HTML/Element/Input/file
document
.querySelector('.js-puzzle-image')
.addEventListener('change', function () {
document.querySelector('.js-puzzle-image').style.display = 'none';
document.querySelector('#pieces').style.display = 'inline-block';
var imageSrc = window.URL.createObjectURL(this.files[0]);
approved(imageSrc);
});
function approved(imageSrc) {
const el_wrap = document.querySelector('#pieces');
// Get the canvas and context
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Load the image
var img = new Image();
img.src = imageSrc;
img.onload = function () {
// Set the canvas size
canvas.width = img.width;
canvas.height = img.height;
el_wrap.style.width = `${img.width}px`;
el_wrap.style.height = `${img.height}px`;
// Draw the image on the canvas
ctx.drawImage(img, 0, 0);
// Split the image into parts
var w = img.width / 3;
var h = img.height / 2;
for (var i = 0; i < 6; i++) {
var x = (i % 3) * w;
var y = Math.floor(i / 3) * h;
// Create a new canvas for each part
let partCanvas = document.createElement('canvas');
partCanvas.dataset.xx = x;
partCanvas.dataset.yy = y;
partCanvas.draggable = 'true';
partCanvas.className = 'sampcanvas';
//$('.sampcanvas').draggable({ snap: true });
partCanvas.width = w;
partCanvas.height = h;
var partCtx = partCanvas.getContext('2d');
// Draw the part of the image on the new canvas
partCtx.drawImage(canvas, x, y, w, h, 0, 0, w, h);
// Do something with each part, such as append it to the document
const getRandom = (min, max) =>
Math.floor(Math.random() * (max - min + 1) + min);
partCanvas.style.left = getRandom(0, img.width - w) + 'px'; // ๐๐ผ Horizontally
partCanvas.style.top = getRandom(0, img.height - h) + 'px'; // ๐๐ผ Vertically
document.getElementById('pieces').appendChild(partCanvas);
// making elements "draggable", but "freeze" them when they're on the correct position
makeDraggable(partCanvas, () => {
var canvasOffsets = canvas.getBoundingClientRect();
var elementScreenOffsets = partCanvas.getBoundingClientRect();
var elementOffsets = {
x: elementScreenOffsets.left - canvasOffsets.left,
y: elementScreenOffsets.top - canvasOffsets.top,
};
var initialOffsets = {
x: partCanvas.dataset.xx,
y: partCanvas.dataset.yy,
};
var FREEZE_DISTASNCE = 30;
if (
Math.abs(elementOffsets.x - initialOffsets.x) <
FREEZE_DISTASNCE &&
Math.abs(elementOffsets.y - initialOffsets.y) <
FREEZE_DISTASNCE
) {
makeNotDraggable(partCanvas);
partCanvas.style.left = partCanvas.dataset.xx + 'px';
partCanvas.style.top = partCanvas.dataset.yy + 'px';
partCanvas.style.zIndex = -1;
}
});
}
canvas.style.display = 'none';
};
}
// https://www.w3schools.com/howto/howto_js_draggable.asp
var zIndexTracker = 0;
function makeNotDraggable(elmnt) {
elmnt.onmousedown = undefined;
}
function makeDraggable(elmnt, onDrop) {
var pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
elmnt.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
elmnt.style.zIndex = ++zIndexTracker;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = elmnt.offsetTop - pos2 + 'px';
elmnt.style.left = elmnt.offsetLeft - pos1 + 'px';
}
function closeDragElement() {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
onDrop();
}
}
#pieces {
position: relative;
border: 1px dashed red;
display: inline-block;
}
.sampcanvas {
position: absolute;
}
<input type="file" accept=".png, .jpg, .jpeg" class="js-puzzle-image" />
<div id="pieces" style="display: none">
<canvas id="canvas"></canvas>
</div>
Of course, this code snippet is "not ideal" from code quality perspective and should be re-worked to be more object-oriented, to have better code responsibility splitting, etc... But for demostration purposes, I believe, that's a good start
Try adding this
partCanvas.dataset.index = i;
and then you can in your drop function do something like this
const finished = () => {
const indexes = document.querySelectorAll('.sampcanvas')
.map(imgPart => imgPart.dataset.index)
.join('');
console.log(indexes);
return indexes === '0123456';
}
Here is the link to the codepen: https://codepen.io/Jsbbvk/pen/RwGBwOO
const edgePadding = 80;
const panSpeed = 5;
const expandCanvasEdge = (x, y) => {
let pan = {
x: 0,
y: 0,
};
const width = canvas.getWidth(),
height = canvas.getHeight();
if (x <= edgePadding) {
//left
const speedRatio = 1 - Math.max(0, x) / edgePadding;
pan.x = panSpeed * speedRatio;
} else if (x >= width - edgePadding) {
//right
const speedRatio =
1 - (width - Math.min(width, x)) / edgePadding;
pan.x = -panSpeed * speedRatio;
}
if (y <= edgePadding) {
//top
const speedRatio = 1 - Math.max(0, y) / edgePadding;
pan.y = panSpeed * speedRatio;
} else if (y >= height - edgePadding) {
//bottom
const speedRatio =
1 - (height - Math.min(height, y)) / edgePadding;
pan.y = -panSpeed * speedRatio;
}
if (pan.x || pan.y) {
canvas.relativePan(new fabric.Point(pan.x, pan.y));
}
}
canvas.on('mouse:move', function(opt) {
if (this.isMouseDown && this.isDrawingMode) {
let {x, y} = canvas.getPointer(opt.e, true);
expandCanvasEdge(x, y);
}
if (!this.isDrawingMode && this.isDragging) {
//panning
var e = opt.e;
var vpt = this.viewportTransform;
vpt[4] += e.clientX - this.lastPosX;
vpt[5] += e.clientY - this.lastPosY;
this.requestRenderAll();
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
}
});
In the demo, when you draw close to the edge of the canvas, the canvas should pan to allow more drawing space.
However, while the panning is happening, the drawing (path) is static on the canvas; it doesn't stretch as the canvas pans.
Is there a way to fix this issue?
I did some deep research for you and found a few examples.
You can overcome this situation by using the relativePan function.
One of the examples I have found:
function startPan(event) {
if (event.button != 2) {
return;
}
var x0 = event.screenX,
y0 = event.screenY;
function continuePan(event) {
var x = event.screenX,
y = event.screenY;
fc.relativePan({ x: x - x0, y: y - y0 });
x0 = x;
y0 = y;
}
function stopPan(event) {
$(window).off('mousemove', continuePan);
$(window).off('mouseup', stopPan);
};
$(window).mousemove(continuePan);
$(window).mouseup(stopPan);
$(window).contextmenu(cancelMenu);
};
function cancelMenu() {
$(window).off('contextmenu', cancelMenu);
return false;
}
$(canvasWrapper).mousedown(startPan);
You can determine a roadmap by examining the resources and demos here.
JSFiddle demo https://jsfiddle.net/tornado1979/up48rxLs/
I am implementing Canvas Drag and Drop Functionality in ReactJs. Problem is that the Object is always centered under the pointer. To initiate the dragโnโdrop can we mousedown anywhere on the Object. If do it at the edge, then the Object suddenly โjumpsโ to become centered. Here is my Code...
var rect = this.props.refs.svg.getBoundingClientRect();
const { xUnit, yUnit } = CanvasDimensions(
rect,
this.props.canvasWidth,
this.props.canvasHeight,
e.clientX,
e.clientY
);
if (this.state.isDragging) {
var tempELement = this.props.conceptItemsList.findIndex(
item => item.sys.id === this.state.DraggingId
);
var ElementWidth = this.props.conceptItemsList[tempELement].width;
var ElementHeight = this.props.conceptItemsList[tempELement].height;
if (this.props.conceptItemsList[tempELement].__typename === "Text") {
ElementWidth =
this.props.refs[this.state.DraggingId].getBoundingClientRect().width /
xUnit;
}
const { X, Y } = CanvasDimensions(
rect,
this.props.canvasWidth,
this.props.canvasHeight,
e.clientX,
e.clientY,
ElementWidth,
ElementHeight
);
if (
Y + this.props.conceptItemsList[tempELement].height >
this.props.CanvasHeight
) {
this.props.setCanvasHeight(
Y + this.props.conceptItemsList[tempELement].height
);
}
this.props.conceptItemsList[tempELement].x = X;
this.props.conceptItemsList[tempELement].y = Y;
this.props.SetDragging(this.props.conceptItemsList[tempELement]);
}
export const CanvasDimensions = (
rect,
canvasWidth,
canvasHeight,
clientX,
clientY,
width = ImageWidth,
height = ImageHeight
) => {
const xUnit = rect.width / canvasWidth;
const yUnit = rect.height / canvasHeight;
const canvasX = clientX - rect.left;
const canvasY = clientY - rect.top;
var X = canvasX / xUnit;
var Y = canvasY / yUnit;
X = X < width / 2 ? X - X : X - width / 2;
Y = Y < height / 2 ? Y - Y : Y - height / 2;
if (X + width / 1.7 > canvasWidth) {
X = X - width / 2;
}
return { X, Y, xUnit, yUnit };
};
I am creating a smudging tool with HTML5 canvas. Now I have to shift the pixel color at the point of mouse pointer to the next position where mouse pointer moves. Is it possible to do with javascript?
<canvas id="canvas"><canvas>
var canvas = document.getElementById("canvas");
var context = canvas.getContext('2d');
var url = 'download.jpg';
var imgObj = new Image();
imgObj.src = url;
imgObj.onload = function(e) {
context.drawImage(imgObj, 0, 0);
}
function findPos(obj) {
var curleft = 0,
curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return {
x: curleft,
y: curtop
};
}
return undefined;
}
function rgbToHex(r, g, b) {
if (r > 255 || g > 255 || b > 255)
throw "Invalid color component";
return ((r << 16) | (g << 8) | b).toString(16);
}
$('#canvas').mousemove(function(e) {
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
console.log(x, y);
var c = this.getContext('2d');
var p = c.getImageData(x, y, 1, 1).data;
var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
console.log(hex)
});
I am very short on time ATM so code only.
Uses an offscreen canvas brush to get a copy of the background canvas background where the mouse was last frame. Then use a radial gradient to feather the brush using ctx.globalCompositeOperation = "destination-in". Then draw the updated brush at the next mouse position.
The main canvas is use just to display, the canvas being smeared is called background You can put whatever content you want on that canvas (eg image) and it can be any size, and you can zoom, pan, rotate the background though you will have to convert the mouse coordinates to match the background coordinates
Click drag mouse to smear colours.
const ctx = canvas.getContext("2d");
const background = createCanvas(canvas.width,canvas.height);
const brushSize = 64;
const bs = brushSize;
const bsh = bs / 2;
const smudgeAmount = 0.25; // values from 0 none to 1 full
// helpers
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); }; // the ; after while loop is important don't remove
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
// simple mouse
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
// brush gradient for feather
const grad = ctx.createRadialGradient(bsh,bsh,0,bsh,bsh,bsh);
grad.addColorStop(0,"black");
grad.addColorStop(1,"rgba(0,0,0,0)");
const brush = createCanvas(brushSize)
// creates an offscreen canvas
function createCanvas(w,h = w){
var c = document.createElement("canvas");
c.width = w;
c.height = h;
c.ctx = c.getContext("2d");
return c;
}
// get the brush from source ctx at x,y
function brushFrom(ctx,x,y){
brush.ctx.globalCompositeOperation = "source-over";
brush.ctx.globalAlpha = 1;
brush.ctx.drawImage(ctx.canvas,-(x - bsh),-(y - bsh));
brush.ctx.globalCompositeOperation = "destination-in";
brush.ctx.globalAlpha = 1;
brush.ctx.fillStyle = grad;
brush.ctx.fillRect(0,0,bs,bs);
}
// short cut vars
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
var lastX;
var lastY;
// update background is size changed
function createBackground(){
background.width = w;
background.height = h;
background.ctx.fillStyle = "white";
background.ctx.fillRect(0,0,w,h);
doFor(64,()=>{
background.ctx.fillStyle = `rgb(${randI(255)},${randI(255)},${randI(255)}`;
background.ctx.fillRect(randI(w),randI(h),randI(10,100),randI(10,100));
});
}
// main update function
function update(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if(w !== innerWidth || h !== innerHeight){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
createBackground();
}else{
ctx.clearRect(0,0,w,h);
}
ctx.drawImage(background,0,0);
// if mouse down then do the smudge for all pixels between last mouse and mouse now
if(mouse.button){
brush.ctx.globalAlpha = smudgeAmount;
var dx = mouse.x - lastX;
var dy = mouse.y - lastY;
var dist = Math.sqrt(dx*dx+dy*dy);
for(var i = 0;i < dist; i += 1){
var ni = i / dist;
brushFrom(background.ctx,lastX + dx * ni,lastY + dy * ni);
ni = (i+1) / dist;
background.ctx.drawImage(brush,lastX + dx * ni - bsh,lastY + dy * ni - bsh);
}
}else{
brush.ctx.clearRect(0,0,bs,bs); /// clear brush if not used
}
lastX = mouse.x;
lastY = mouse.y;
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>
The goal is simple, using a mousewheel, zoom into a specific point (where the mouse is). This means after zooming the mouse will be in the same roughly the same spot of the picture.
(Purely illustrative, I don't care if you use dolphins, ducks or madonna for the image)
I do not wish to use canvas, and so far I've tried something like this:
HTML
<img src="whatever">
JS
function zoom(e){
var deltaScale = deltaScale || -e.deltaY / 1000;
var newScale = scale + deltaScale;
var newWidth = img.naturalWidth * newScale;
var newHeight = img.naturalHeight * newScale;
var x = e.pageX;
var y = e.pageY;
var newX = x * newWidth / img.width;
var newY = y * newHeight / img.height;
var deltaX = newX - x;
var deltaY = newY - y;
setScale(newScale);
setPosDelta(-deltaX,-deltaY);
}
function setPosDelta(dX, dY) {
var imgPos = getPosition();
setPosition(imgPos.x + dX, imgPos.y + dY);
}
function getPosition() {
var x = parseFloat(img.style.left);
var y = parseFloat(img.style.top);
return {
x: x,
y: y
}
}
function setScale(n) {
scale = n;
img.width = img.naturalWidth * n;
img.height = img.naturalHeight * n;
}
What this attempts to do is calculate the x,y coordinates of the dolphin's eye before and after the zoom, and after calculating the distance between those two points, substracts it from the left,top position in order to correct the zoom displacement, with no particular success.
The zoom occurs naturally extending the image to the right and to the bottom, so the correction tries to pull back to the left and to the top in order to keep the mouse on that damn dolphin eye! But it definitely doesn't.
Tell me, what's wrong with the code/math? I feel this question is not too broad, considering I couldn't find any solutions besides the canvas one.
Thanks!
[EDIT] IMPORTANT
CSS transform order matters, if you follow the selected answer, make sure you order the transition first, and then the scale. CSS transforms are executed backwards (right to left) so the scaling would be processed first, and then the translation.
Here is an implementation of zooming to a point. The code uses the CSS 2D transform and includes panning the image on a click and drag. This is easy because of no change in scale.
The trick when zooming is to normalize the offset amount using the current scale (in other words: divide it by the current scale) first, then apply the new scale to that normalized offset. This keeps the cursor exactly where it is independent of scale.
var scale = 1,
panning = false,
xoff = 0,
yoff = 0,
start = {x: 0, y: 0},
doc = document.getElementById("document");
function setTransform() {
doc.style.transform = "translate(" + xoff + "px, " + yoff + "px) scale(" + scale + ")";
}
doc.onmousedown = function(e) {
e.preventDefault();
start = {x: e.clientX - xoff, y: e.clientY - yoff};
panning = true;
}
doc.onmouseup = function(e) {
panning = false;
}
doc.onmousemove = function(e) {
e.preventDefault();
if (!panning) {
return;
}
xoff = (e.clientX - start.x);
yoff = (e.clientY - start.y);
setTransform();
}
doc.onwheel = function(e) {
e.preventDefault();
// take the scale into account with the offset
var xs = (e.clientX - xoff) / scale,
ys = (e.clientY - yoff) / scale,
delta = (e.wheelDelta ? e.wheelDelta : -e.deltaY);
// get scroll direction & set zoom level
(delta > 0) ? (scale *= 1.2) : (scale /= 1.2);
// reverse the offset amount with the new scale
xoff = e.clientX - xs * scale;
yoff = e.clientY - ys * scale;
setTransform();
}
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
#document {
width: 100%;
height: 100%;
transform-origin: 0px 0px;
transform: scale(1) translate(0px, 0px);
}
<div id="document">
<img style="width: 100%"
src="https://i.imgur.com/fHyEMsl.jpg"
crossOrigin="" />
</div>
This is an implementation that is closer to your original idea using top and left offsets and modifying the width attribute of the image instead of using the css transform in my other answer.
var scale = 1.0,
img = document.getElementById("image"),
deltaX = 0,
deltaY = 0;
// set the initial scale once the image is loaded
img.onload = function() {
scale = image.offsetWidth / image.naturalWidth;
}
img.onwheel = function(e) {
e.preventDefault();
// first, remove the scale so we have the native offset
var xoff = (e.clientX - deltaX) / scale,
yoff = (e.clientY - deltaY) / scale,
delta = (e.wheelDelta ? e.wheelDelta : -e.deltaY);
// get scroll direction & set zoom level
(delta > 0) ? (scale *= 1.05) : (scale /= 1.05);
// limit the smallest size so the image does not disappear
if (img.naturalWidth * scale < 16) {
scale = 16 / img.naturalWidth;
}
// apply the new scale to the native offset
deltaX = e.clientX - xoff * scale;
deltaY = e.clientY - yoff * scale;
// now modify the attributes of the image to reflect the changes
img.style.top = deltaY + "px";
img.style.left = deltaX + "px";
img.style.width = (img.naturalWidth * scale) + "px";
}
window.onresize = function(e) {
document.getElementById("wrapper").style.width = window.innerWidth + "px";
document.getElementById("wrapper").style.height = window.innerHeight + "px";
}
window.onload = function(e) {
document.getElementById("wrapper").style.width = window.innerWidth + "px";
document.getElementById("wrapper").style.height = window.innerHeight + "px";
}
html,
body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
div {
overflow: hidden;
}
<div id="wrapper" style="position:relative;">
<img id="image" style="width:100%;position:absolute;top:0px;left:0px;"
src="https://i.imgur.com/fHyEMsl.jpg"
crossOrigin="" />
</div>
I liked the both posts from fmacdee. I factored the code he created out to be a reusable version that can be called on any image.
just call:
var imageScaler = new ImageScaler(document.getElementById("image"));
imageScaler.setup();
and include this code somewhere in your project:
var ImageScaler = function(img)
{
this.img = img;
this.scale = this.getImageScale();
this.panning = false;
this.start = {x: 0, y: 0};
this.delta = {x: 0, y: 0};
};
ImageScaler.prototype =
{
constructor: ImageScaler,
setup: function()
{
this.setupEvents();
},
setupEvents: function()
{
var img = this.img;
var callBack = this.onScale.bind(this);
var touchDown = this.touchDown.bind(this),
touhcMove = this.touchMove.bind(this),
touchUp = this.touchUp.bind(this);
img.onwheel = callBack;
img.onmousedown = touchDown;
img.onmousemove = touhcMove;
img.onmouseup = touchUp;
},
getImageScale: function()
{
var img = this.img;
return img.offsetWidth / img.naturalWidth;
},
getMouseDirection: function(e)
{
return (e.wheelDelta ? e.wheelDelta : -e.deltaY);
},
getOffset: function(e)
{
var scale = this.scale,
delta = this.delta;
// first, remove the scale so we have the native offset
return {
x: (e.clientX - delta.x) / scale,
y: (e.clientY - delta.y) / scale
};
},
scaleElement: function(x, y, scale)
{
var img = this.img;
img.style.top = y + "px";
img.style.left = x + "px";
img.style.width = (img.naturalWidth * scale) + "px";
},
minScale: 0.2,
updateScale: function(delta)
{
// get scroll direction & set zoom level
var scale = (delta > 0) ? (this.scale *= 1.05) : (this.scale /= 1.05);
// limit the smallest size so the image does not disappear
if (scale <= this.minScale)
{
this.scale = this.minScale;
}
return this.scale;
},
touchDown: function(e)
{
var delta = this.delta;
this.start = {x: e.clientX - delta.x, y: e.clientY - delta.y};
this.panning = true;
},
touchMove: function(e)
{
e.preventDefault();
if (this.panning === false)
{
return;
}
var delta = this.delta,
start = this.start;
delta.x = (e.clientX - start.x);
delta.y = (e.clientY - start.y);
console.log(delta, start)
this.scaleElement(delta.x, delta.y, this.scale);
},
touchUp: function(e)
{
this.panning = false;
},
onScale: function(e)
{
var offset = this.getOffset(e);
e.preventDefault();
// get scroll direction & set zoom level
var delta = this.getMouseDirection(e);
var scale = this.updateScale(delta);
// apply the new scale to the native offset
delta = this.delta;
delta.x = e.clientX - offset.x * scale;
delta.y = e.clientY - offset.y * scale;
this.scaleElement(delta.x, delta.y, scale);
}
};
I made a fiddle to view the results: http://jsfiddle.net/acqo5n8s/12/