infinetely moveable and scalable elements on interactive webpage

57 Views Asked by At

I am creating a webpage that will display objects (images, rectangles, circles) at specified coordinates on screen. Objects and their coordinates are stored in database. Coordinates x and y are in pixel units and can be any 64-bit signed number meaning that coordinates can be outside of initial screen. That is why I want to make it possible for user to scale the whole workspace (that which contains objects) and move around the workspace.

What I am basically trying to do is to crate the ability like in google maps where you can scale and move around places.

What I have so far:

const workspace = document.getElementById("div1");
let zoom = 1;
const ZOOM_SPEED = 0.1;

document.onwheel = function (e) {
    e = e || window.event;
    if (e.deltaY > 0) {
        workspace.style.transform = `scale(${(zoom += ZOOM_SPEED)})`;
    } else if (e.deltaY < 0) {
        workspace.style.transform = `scale(${(zoom -= ZOOM_SPEED)})`;
    }
};

document.onmousedown = function (e){
    e = e || window.event;
    e.preventDefault();

    let _startX = e.clientX;
    let _startY = e.clientY;
    let _offsetX = workspace.offsetLeft;
    let _offsetY = workspace.offsetTop;
    
    document.onmousemove = function (e) {
        e = e || window.event;
        workspace.style.left = (_offsetX + e.clientX - _startX) + 'px';
        workspace.style.top = (_offsetY + e.clientY - _startY) + 'px';
    };
};

document.onmouseup = function (e) {
    document.onmousemove = null;
};
html {
    background-color:green;
    overflow: hidden;
}

#div1 {
    position: fixed;
    height: 100%;
    width: 100%;
    top: 0;
    left: 0;
    display: grid;
    overflow: hidden;
    background: yellow;
}

.frame {
    position: absolute;
    width: 100px;
    height: 50px;
    border: 3px solid #ccc;
    background: red;
}

#f1 {
  left: 20px;
  top: 80px;
}

#f2 {
  left: 300px;
  top: 200px;
}
<!DOCTYPE html>
<html lang="en">
    <head>
    </head>
    
    <body>
      <div id="div1">
        <h1>Scroll with wheel, move with click and drag</h1>

        <div class="frame" id="f1">
          <p>f1</p>
        </div>

        <div class="frame" id="f2">
          <p>f2</p>
        </div>
      </div>
    </body>
</html>

Please run this code snippet in "Full page" mode or visit: https://jsfiddle.net/02t5dLm4/1/

I am able to scale div1 (this is the workspace) and move it around and it works as expected meaning all its childs are also scaled and moved. However as you can see from jsfiddle scaling and moving the workspace introduces these green areas that is where workspace does not cover the screen. This is what I do not want to happen because when the workspace will be scaled it will be possible for new objects to have to be drawn on it and if the workspace doesn't exist where new objects should be drawn (objects are not drawn in the workspace) then I will not be able to scale and move the newly drawn objects along with the workspace.

I've tried to manually change workspace size after scaling and moving but could not get it to work to grow size correctly in desired directions. If I scaled the workspace and tried to change its width/height and the whole workspace moved with it. Then I tried to set transform-origin: left top for workspace and resizing after scaling worked nicely however I was scaling into top left corner which is not what I desired since I want to scale in center.

Any help is greately appreciated.

1

There are 1 best solutions below

6
Wongjn On

You could consider having an inner element of the #div1 element that you move or resize, instead of the #div1 element itself. This means the #div1 element would retain its initial size/position and you won't see any green:

const workspace = document.getElementById("div1");
const inner = document.getElementById("foo");
let zoom = 1;
const ZOOM_SPEED = 0.1;

document.onwheel = function (e) {
    e = e || window.event;
    if (e.deltaY > 0) {
        inner.style.transform = `scale(${(zoom += ZOOM_SPEED)})`;
    } else if (e.deltaY < 0) {
        inner.style.transform = `scale(${(zoom -= ZOOM_SPEED)})`;
    }
};

document.onmousedown = function (e){
    e = e || window.event;
    e.preventDefault();

    let _startX = e.clientX;
    let _startY = e.clientY;
    let _offsetX = inner.offsetLeft;
    let _offsetY = inner.offsetTop;
    
    document.onmousemove = function (e) {
        e = e || window.event;
        inner.style.left = (_offsetX + e.clientX - _startX) + 'px';
        inner.style.top = (_offsetY + e.clientY - _startY) + 'px';
    };
};

document.onmouseup = function (e) {
    document.onmousemove = null;
};
html {
    background-color:green;
    overflow: hidden;
}

#div1 {
    position: fixed;
    height: 100%;
    width: 100%;
    top: 0;
    left: 0;
    display: grid;
    overflow: hidden;
    background: yellow;
}

#foo {
  position: relative;
  height: 100%;
  width: 100%;
}

.frame {
    position: absolute;
    width: 100px;
    height: 50px;
    border: 3px solid #ccc;
    background: red;
}

#f1 {
  left: 20px;
  top: 80px;
}

#f2 {
  left: 300px;
  top: 200px;
}
<!DOCTYPE html>
<html lang="en">
    <head>
    </head>
    
    <body>
      <div id="div1">
        <div id="foo">
          <h1>Scroll with wheel, move with click and drag</h1>

          <div class="frame" id="f1">
            <p>f1</p>
          </div>

          <div class="frame" id="f2">
            <p>f2</p>
          </div>
        </div>
      </div>
    </body>
</html>