Peter Hollingsworth
Peter Hollingsworth

Reputation: 2063

How to position a fixed-location element on IOS browser when zoomed?

I have a nice little React drag-drop library that works for mouse- and touch systems. For touch, it grabs the touch location via clientX and clientY (e.targetTouches[0].clientX, e.targetTouches[0].clientY). It uses those coordinates to place the dragged element, which has position: fixed.

HOWEVER it turns out that at least on IOS Safari (v.11.x), when you zoom the display, the coordinate system for position:fixed no longer matches the window coordinate system. So the dragged element displays in the wrong place on the page.

Picture the zoomed-in browser window as a small rectangular view onto a larger rectangle containing the un-zoomed content. The location:fixed coordinate system uses the larger rectangle. The window coordinate system uses the small one.

As you scroll, the window pans around the larger rectangle in a way that's difficult to describe, with the result that the offset between 0,0 in position-fixed-land and 0,0 in the browser window is always changing.

How can I get the offset between the browser window and the "position:fixed" coordinate systems? (I can then add that offset to the position of the dragged element to position it correctly.)

Upvotes: 4

Views: 1802

Answers (2)

Chris Barr
Chris Barr

Reputation: 34032

I came across this in my search for a solution. I have a position:absolute element that I update the position on when the window is resized, and iOS triggers this on zooming in/out.

I discovered window.visualViewport which has the key information needed to offset this!

My solution for setting the position/size of an element with the zoom offset:

positionNavHighlight(link: HTMLElement) {
  const rect = link.getBoundingClientRect();

  const offsetTop = window.visualViewport.offsetTop;
  const offsetLeft = window.visualViewport.offsetLeft;

  navActiveBg.style.top = `${rect.top + offsetTop}px`;
  navActiveBg.style.left = `${rect.left + offsetLeft}px`;
  navActiveBg.style.height = `${rect.height}px`;
  navActiveBg.style.width = `${rect.width}px`;
}

Upvotes: 0

Peter Hollingsworth
Peter Hollingsworth

Reputation: 2063

Stick an element at 0,0, position:fixed.

Get its x/y offset from the browser window using getBoundingClientRect().

Then delete the element.

function getFixedOffset() {
    let fixedElem = document.createElement('div');
    fixedElem.style.cssText = 'position:fixed; top: 0; left: 0';
    document.body.appendChild(fixedElem);
    const rect = fixedElem.getBoundingClientRect();
    document.body.removeChild(fixedElem);
    return [rect.left, rect.top]
}

This works (yay!) but feels pretty kludgey, creating and then destroying a DOM element every time the user drags-and-drops. Other suggestions are welcome.

Upvotes: 1

Related Questions