admin管理员组

文章数量:1315782

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?

Share Improve this question asked Jan 30 at 0:27 Joseph MassaroJoseph Massaro 1434 bronze badges 1
  • 1 el.style.rotate may work but el.style.transform with rotate(xdeg) is probably what you want. The default value for transform-origin is 50% 50% I believe so it shouldn't be rotating about the top left. – NickSlash Commented Jan 30 at 1:33
Add a comment  | 

2 Answers 2

Reset to default 1

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.

  1. Transformations in CSS do not affect the size or origin of the element, nor their parent.
  2. Despite this, getBoundingclientRect() always has the actual .left and .top of a rotated element.
  3. Background images set via CSS cannot be manipulated.
  4. There is no way to reposition the origin point of an element, and the element remembers its original position at all times.
  5. 1 and 3 may end up as CSS functions in the future.

本文标签: htmlHow should you snap an image to a grid via Javascript after it has been rotated with CSSStack Overflow