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 };
};
Related
I'm currently doing a project regarding p5.js and I need to do the following:
Input: given image and rectangle;
Algorithm: move the image inside the rectangle. By saying moving, I mean when the mouse is clicked, move the whole image to match the size of the rectangle by showing only portion of the image (size of the rectangle).
Output: dragging image inside the rectangle.
Update: When I import an image with React input form, it does not change the current image in P5 even I call the p5.redraw() function.
My current code is moving the image, but I need an image to be inside the rectangle and show only part of the image that is this size of the rectangle. The code is written in React:
const [selectedFile, setSelectedFile] = useState();
let backgroundImage;
let dragging = false; // Is the object being dragged?
let rollover = false; // Is the mouse over the ellipse?
let x, y, w, h; // Location and size
let offsetX, offsetY; // Mouseclick offset
const setup = (p5, parentRef) => {
p5.createCanvas(1000, 500).parent(parentRef);
// Starting location
x = 350;
y = 50;
const url =
"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/640px-Image_created_with_a_mobile_phone.png";
backgroundImage = p5.loadImage(url);
// Dimensions
w = 700;
h = 700;
};
const draw = (p5) => {
p5.background(233);
if (
p5.mouseX > x &&
p5.mouseX < x + w &&
p5.mouseY > y &&
p5.mouseY < y + h
) {
rollover = true;
} else {
rollover = false;
}
// Adjust location if being dragged
if (dragging) {
x = p5.mouseX + offsetX;
y = p5.mouseY + offsetY;
}
if (selectedFile != null) {
const url = URL.createObjectURL(selectedFile);
backgroundImage = p5.loadImage(url);
// p5.image(backgroundImage, x, y, w, h);
// p5.redraw();
// backgroundImage = p5.loadImage(url, () => {
// p5.image(backgroundImage, x, y, w, h);
// p5.redraw();
// });
} else {
}
p5.image(backgroundImage, x, y);
drawMaskOverlay(p5);
};
const drawMaskOverlay = (p5) => {
p5.fill(255);
p5.noStroke();
p5.beginShape();
// CW
p5.vertex(0, 0);
p5.vertex(p5.width, 0);
p5.vertex(p5.width, p5.height);
p5.vertex(0, p5.height);
// cutout contour CCW
p5.beginContour();
p5.vertex(400, 100);
p5.vertex(400, 400);
p5.vertex(600, 400);
p5.vertex(600, 100);
p5.endContour();
p5.endShape();
};
const mousePressed = (p5) => {
if (
p5.mouseX > x &&
p5.mouseX < x + w &&
p5.mouseY > y &&
p5.mouseY < y + h
) {
dragging = true;
offsetX = x - p5.mouseX;
offsetY = y - p5.mouseY;
}
};
const mouseReleased = (p5) => {
// Quit dragging
dragging = false;
};
to display it, I use the following code:
return (
<div className="App">
<div>
<h1>Select an image</h1>
<input
type="file"
name="file"
id="file"
// onChange={(e) => setSelectedFile(e.target.files[0])}
onChange={(e) => setSelectedFile(e.target.files[0])}
/>
<Sketch
// preload={preload}
setup={setup}
draw={draw}
mouseReleased={mouseReleased}
mousePressed={mousePressed}
/>
</div>
</div>
);
I use react-p5 library.
You can see and test the code in StackBlitz.
One quick workaround is to simply overlay a cutout shape using beginContour() / endContour(), thus only showing a portion of the image:
let backgroundImage;
let dragging = false;
let rollover = false;
let x, y, w, h; // Location and size
let offsetX, offsetY;
preload = () => {
const url = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/640px-Image_created_with_a_mobile_phone.png";
backgroundImage = loadImage(url);
};
setup = () => {
createCanvas(1000, 500);
// Starting location
x = 100;
y = 100;
// Dimensions
w = 300;
h = 400;
}
draw = () => {
background(233);
if (
mouseX > x &&
mouseX < x + w &&
mouseY > y &&
mouseY < y + h
) {
rollover = true;
} else {
rollover = false;
}
if (dragging) {
x = mouseX + offsetX;
y = mouseY + offsetY;
}
image(backgroundImage, x, y, w, h);
// noFill();
// stroke(0);
// rect(100, 100, 200, 300);
drawMaskOverlay();
};
function drawMaskOverlay(){
fill(0);
noStroke();
beginShape();
// CW
vertex(0, 0);
vertex(width, 0);
vertex(width, height);
vertex(0, height);
// cutout contour CCW
beginContour();
vertex(100, 100);
vertex(100, 400);
vertex(300, 400);
vertex(300, 100);
endContour();
endShape();
}
mousePressed = () => {
if (
mouseX > x &&
mouseX < x + w &&
mouseY > y &&
mouseY < y + h
) {
dragging = true;
offsetX = x - mouseX;
offsetY = y - mouseY;
}
};
mouseReleased = () => {
dragging = false;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
If the project is a school project it's unclear whether the above will pass if it doesn't cover the materials taught. If it's a personal project, the above should be a simple enough workaround.
Other alternatives ways of showing parts of the image could be:
working with p5.Image which has a mask() method (potentially in conjuction with p5.Graphics which would make it easy draw into and covert to a p5.Image via get())
with with p5.Image and instead of masking, clearing a blank (buffer) image and only copying a portion of backgroundImage (depending on it's dragged location) via copy() which would make it appear like backgroundImage is masked.
Update based on your file select comment here's a tweaked version of the above snippet:
let backgroundImage;
let dragging = false;
let rollover = false;
let x, y, w, h; // Location and size
let offsetX, offsetY;
preload = () => {
const url = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/640px-Image_created_with_a_mobile_phone.png";
backgroundImage = loadImage(url);
};
setup = () => {
createCanvas(1000, 500);
// Starting location
x = 100;
y = 100;
// Dimensions
w = 300;
h = 400;
createFileInput(handleFile);
}
handleFile = (file) => {
if (file.type === 'image') {
backgroundImage = createImg(file.data, '');
backgroundImage.hide();
} else {
backgroundImage = null;
}
}
draw = () => {
background(233);
if (
mouseX > x &&
mouseX < x + w &&
mouseY > y &&
mouseY < y + h
) {
rollover = true;
} else {
rollover = false;
}
if (dragging) {
x = mouseX + offsetX;
y = mouseY + offsetY;
}
// only render the image if it exists
if(backgroundImage)
image(backgroundImage, x, y, w, h);
// noFill();
// stroke(0);
// rect(100, 100, 200, 300);
drawMaskOverlay();
};
function drawMaskOverlay(){
fill(0);
noStroke();
beginShape();
// CW
vertex(0, 0);
vertex(width, 0);
vertex(width, height);
vertex(0, height);
// cutout contour CCW
beginContour();
vertex(100, 100);
vertex(100, 400);
vertex(300, 400);
vertex(300, 100);
endContour();
endShape();
}
mousePressed = () => {
if (
mouseX > x &&
mouseX < x + w &&
mouseY > y &&
mouseY < y + h
) {
dragging = true;
offsetX = x - mouseX;
offsetY = y - mouseY;
}
};
mouseReleased = () => {
dragging = false;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
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 have a canvas, inside of which i have a board/grid. When a user highlights their mouse over an intersection of the grid, i want it to show where their game peice will go. This worked perfectly fine when the board was the exact size of the canvas. I made it abit smaller by x all the way round.
So as you can see in the picture below, the green shows the canvas and the grid is the board. I put my cursor at the very bottom right corner of the green to show when it triggers. The only one that works fine is the middle one because regardless how big i make the board, the middle will always be the middle.
Any easy fix would just be to make the area with the mouseover event, the dimensions of the board instead of the canvas but the event listener is on the canvas. My code is below the image
Variables:
var canvas = document.getElementById("game-canvas");
var context = canvas.getContext("2d");
var boardSize = 13;
var border = canvas.width / 20;
var boardWidth = canvas.width - (border * 2);
var boardHeight = canvas.height - (border * 2);
var cellWidth = boardWidth / (boardSize - 1);
var cellHeight = boardHeight / (boardSize - 1);
var lastX;
var lastY;
Mouse over event:
canvas.addEventListener('mousemove', function(evt)
{
var position = getGridPoint(evt);
if ((position.x != lastX) || (position.y != lastY))
{
placeStone((position.x * cellWidth) + border, (position.y * cellWidth) + border, 'rgba(0, 0, 0, 0.2)');
}
lastX = position.x;
lastY = position.y;
});
Gets the point on the grid and converts that into a number 0 - 13 (in this case)
function getGridPoint(evt)
{
var rect = canvas.getBoundingClientRect();
var x = Math.round((evt.clientX-rect.left)/(rect.right-rect.left)*boardWidth);
var y = Math.round((evt.clientY-rect.top)/(rect.bottom-rect.top)*boardHeight);
var roundX = Math.round(x / cellWidth);
var roundY = Math.round(y / cellHeight);
return {
x: roundX,
y: roundY
};
}
And finally draws the piece on the board:
function placeStone(x, y, color)
{
var radius = cellWidth / 2;
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fillStyle = color;
context.fill();
context.lineWidth = 5;
}
I left a couple bits out like how the grid refreshs so its not a string of circles following your mouse and stuff, to keep it as short as i can, im hoping its just a simple asnwer and nobody needs to recreate it but if you do i can include the function that refreshes the grid and draws everything. Thankyou for any advice
To get the position relative to a box
// just as an example w,h are width and height
const box = { x : 10, y : 10, w : 100, h : 100 };
// mouse is the mouse coords and relative to the topleft of canvas (0,0);
var mouse.box = {}
mouse.box.x = mouse.x - box.x;
mouse.box.y = mouse.y - box.y;
Negative values for mouse.box x,y and values greater than box width and height have mouse outside.
For more convenience you can get the mouse normalize pos in the box
mouse.box.nx = mouse.box.x / box.w;
mouse.box.ny = mouse.box.y / box.h;
The coords for nx,ny are in the range 0-1 when inside or on the edge of the box;
If you want to have grid positions then define the grid
box.gridW = 10; // grid divisions width
box.gridH = 10; // grid divisions height
Then getting the grid pos of mouse
mouse.box.gx = Math.floor(mouse.box.nx * box.gridW);
mouse.box.gy = Math.floor(mouse.box.ny * box.gridH);
const ctx = canvas.getContext("2d");
const box = { x : 50,y : 10, w : 200, h : 200, gridW : 10, gridH : 10}
function drawGrid(){
var sx = box.w / box.gridW;
var sy = box.h / box.gridH;
var bx = box.x;
var by = box.y;
for(var y = 0; y < box.gridH; y ++){
for(var x = 0; x < box.gridW; x ++){
ctx.strokeRect(x * sx + bx, y * sx + by,sx,sy);
}
}
if(mouse.box){
if(mouse.box.nx >= 0 && mouse.box.nx <= 1 &&
mouse.box.ny >= 0 && mouse.box.ny <= 1){
ctx.fillRect(mouse.box.gx * sx + bx, mouse.box.gy * sx + by,sx,sy);
}
}
}
const mouse = {};
canvas.addEventListener("mousemove",(e)=>{
mouse.x = e.pageX;
mouse.y = e.pageY;
});
function updateMouse(){
if(!mouse.box){
mouse.box = {};
}
mouse.box.x = mouse.x - box.x;
mouse.box.y = mouse.y - box.y;
mouse.box.nx = mouse.box.x / box.w;
mouse.box.ny = mouse.box.y / box.h;
mouse.box.gx = Math.floor(mouse.box.nx * box.gridW);
mouse.box.gy = Math.floor(mouse.box.ny * box.gridH);
var p = 20;
ctx.fillText("x : " + mouse.x,box.x+box.w+10,p); p+= 14;
ctx.fillText("y : " + mouse.y,box.x+box.w+10,p); p+= 20;
ctx.fillText("Box relative",box.x+box.w+10,p); p+= 14;
ctx.fillText("x : " + mouse.box.x,box.x+box.w+10,p); p+= 14;
ctx.fillText("y : " + mouse.box.y,box.x+box.w+10,p); p+= 14;
ctx.fillText("nx : " + mouse.box.nx,box.x+box.w+10,p); p+= 14;
ctx.fillText("ny : " + mouse.box.ny,box.x+box.w+10,p); p+= 14;
ctx.fillText("gx : " + mouse.box.gx,box.x+box.w+10,p); p+= 14;
ctx.fillText("gy : " + mouse.box.gy,box.x+box.w+10,p); p+= 14;
}
function mainLoop(time){
if(canvas.width !== innerWidth || canvas.height !== innerHeight){ // resize canvas if window size has changed
canvas.width = innerWidth;
canvas.height = innerHeight;
}
ctx.setTransform(1,0,0,1,0,0); // set default transform
ctx.clearRect(0,0,canvas.width,canvas.height); // clear the canvas
updateMouse();
drawGrid();
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
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/
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'));