I have a 9x6 grid with 80 px squares.
<div class = "grid" style="width: 720px; height:480px; margin: auto; background-color: white;">
<style>
.grid {
background-image:
repeating-linear-gradient(#ccc 0 1px, transparent 1px 100%),
repeating-linear-gradient(90deg, #ccc 0 1px, transparent 1px 100%);
background-size: 80px 80px;
outline: 10px solid black;
}
</style>
I have an image that is 160px wide and 80px tall.
<img class="movable" src="stick.png" id="Stick" style="left: 256.5px; top: 168px;">
The user can drag the image and drop it onto the grid. It snaps into place perfectly, accounting for the position of the grid.
If the image is rotated using CSS, the snap is 40px off for both dimensions. I tried adding a parent div as a container and noticed that the origin of the image remains in the same place, no matter how it is rotated. Because of this, targ.style.left and targ.getBoundingClientRect().left are totally different values. I also tried rotating the image but moving the parent div - this results in the same behavior. Furthermore, the snap is off even more for larger images.
160px x 80px image is off by (40, -40)
80px x 240px image is off by (-80, 40)
80px x 400px image is off by (-160, 160)
function startDrag(e) {
// determine event object
if (!e) {
var e = window.event;
}
if(e.preventDefault) e.preventDefault();
// IE uses srcElement, others use target
targ = e.target ? e.target : e.srcElement;
if (targ.className != 'movable') {return};
// calculate event X, Y coordinates
offsetX = e.clientX;
offsetY = e.clientY;
// assign default values for top and left properties
if(!targ.style.left) { targ.style.left=offsetX-(e.target.getBoundingClientRect().width/2)};
if (!targ.style.top) { targ.style.top=offsetY-(e.target.getBoundingClientRect().height/2)};
// calculate integer values for top and left
// properties
coordX = parseInt(targ.style.left);
coordY = parseInt(targ.style.top);
drag = true;
// move div element
document.onmousemove=dragDiv;
return false;
}
function dragDiv(e) {
if (!drag) {return};
if (!e) { var e= window.event};
// var targ=e.target?e.target:e.srcElement;
// move div element
targ.style.left=coordX+e.clientX-offsetX+'px';
targ.style.top=coordY+e.clientY-offsetY+'px';
return false;
}
function stopDrag() {
drag=false;
let grid_offset = grid.getBoundingClientRect();
let item_offset = targ.getBoundingClientRect();
let xa = Math.abs(grid_offset.left - (80*Math.floor(parseInt(grid_offset.left)/80)));
let ya = Math.abs(grid_offset.top - (80*Math.floor(parseInt(grid_offset.top)/80)));
let snap = {
x: 80*Math.round((item_offset.left - xa)/80),
y: 80*Math.round((item_offset.top - ya)/80)
}
targ.style.left = snap.x + xa;
targ.style.top = snap.y + ya;
}
function rotate_item (e) {
e.preventDefault();
if (!e.target.classList.contains("movable")) return;
if (!e.target.style.rotate) e.target.style.rotate = "0deg";
let deg = e.target.style.rotate.slice(0,-3);
if (e.deltaY < 0) {
deg = parseInt(deg) + 90;
e.target.style.rotate = deg + "deg";
} else {
deg = parseInt(deg) - 90;
e.target.style.rotate = deg + "deg";
}
}
I suppose I could write a lengthy switch to account for all possible image sizes and orientations, or maybe add the difference between targ.style.left and targ.getBoundingClientRect().left, but I'm looking for a more elegant method and want to understand what's going on. What am I doing wrong here? What is the "proper" way to achieve this?
I have a 9x6 grid with 80 px squares.
<div class = "grid" style="width: 720px; height:480px; margin: auto; background-color: white;">
<style>
.grid {
background-image:
repeating-linear-gradient(#ccc 0 1px, transparent 1px 100%),
repeating-linear-gradient(90deg, #ccc 0 1px, transparent 1px 100%);
background-size: 80px 80px;
outline: 10px solid black;
}
</style>
I have an image that is 160px wide and 80px tall.
<img class="movable" src="stick.png" id="Stick" style="left: 256.5px; top: 168px;">
The user can drag the image and drop it onto the grid. It snaps into place perfectly, accounting for the position of the grid.
If the image is rotated using CSS, the snap is 40px off for both dimensions. I tried adding a parent div as a container and noticed that the origin of the image remains in the same place, no matter how it is rotated. Because of this, targ.style.left and targ.getBoundingClientRect().left are totally different values. I also tried rotating the image but moving the parent div - this results in the same behavior. Furthermore, the snap is off even more for larger images.
160px x 80px image is off by (40, -40)
80px x 240px image is off by (-80, 40)
80px x 400px image is off by (-160, 160)
function startDrag(e) {
// determine event object
if (!e) {
var e = window.event;
}
if(e.preventDefault) e.preventDefault();
// IE uses srcElement, others use target
targ = e.target ? e.target : e.srcElement;
if (targ.className != 'movable') {return};
// calculate event X, Y coordinates
offsetX = e.clientX;
offsetY = e.clientY;
// assign default values for top and left properties
if(!targ.style.left) { targ.style.left=offsetX-(e.target.getBoundingClientRect().width/2)};
if (!targ.style.top) { targ.style.top=offsetY-(e.target.getBoundingClientRect().height/2)};
// calculate integer values for top and left
// properties
coordX = parseInt(targ.style.left);
coordY = parseInt(targ.style.top);
drag = true;
// move div element
document.onmousemove=dragDiv;
return false;
}
function dragDiv(e) {
if (!drag) {return};
if (!e) { var e= window.event};
// var targ=e.target?e.target:e.srcElement;
// move div element
targ.style.left=coordX+e.clientX-offsetX+'px';
targ.style.top=coordY+e.clientY-offsetY+'px';
return false;
}
function stopDrag() {
drag=false;
let grid_offset = grid.getBoundingClientRect();
let item_offset = targ.getBoundingClientRect();
let xa = Math.abs(grid_offset.left - (80*Math.floor(parseInt(grid_offset.left)/80)));
let ya = Math.abs(grid_offset.top - (80*Math.floor(parseInt(grid_offset.top)/80)));
let snap = {
x: 80*Math.round((item_offset.left - xa)/80),
y: 80*Math.round((item_offset.top - ya)/80)
}
targ.style.left = snap.x + xa;
targ.style.top = snap.y + ya;
}
function rotate_item (e) {
e.preventDefault();
if (!e.target.classList.contains("movable")) return;
if (!e.target.style.rotate) e.target.style.rotate = "0deg";
let deg = e.target.style.rotate.slice(0,-3);
if (e.deltaY < 0) {
deg = parseInt(deg) + 90;
e.target.style.rotate = deg + "deg";
} else {
deg = parseInt(deg) - 90;
e.target.style.rotate = deg + "deg";
}
}
I suppose I could write a lengthy switch to account for all possible image sizes and orientations, or maybe add the difference between targ.style.left and targ.getBoundingClientRect().left, but I'm looking for a more elegant method and want to understand what's going on. What am I doing wrong here? What is the "proper" way to achieve this?
If you want it to rotate around the top left corner you should set the transform-origin: 0 0
. This way it will keep being snapped to the grid if it was snapped before the rotation.
Basically just:
.movable {
transform-origin: 0 0;
}
I decided to just compute the difference of the position like so:
let difference = {
x: parseInt(targ.style.left) - targ.getBoundingClientRect().left,
y: parseInt(targ.style.top) - targ.getBoundingClientRect().top
}
targ.style.left = snap.x + xa + difference.x;
targ.style.top = snap.y + ya + difference.y
I learned a few interesting things along the way.
el.style.rotate
may work butel.style.transform
withrotate(xdeg)
is probably what you want. The default value fortransform-origin
is 50% 50% I believe so it shouldn't be rotating about the top left. – NickSlash Commented Jan 30 at 1:33