Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fix elements (esp top right) to the visual viewport in chrome mobile, with very wide content?

Is there a way to fix elements (esp top right) to the visual viewport in chrome mobile?

We want to show very large images that the website visitor will zoom in and out of. The viewer needs to be able to see the whole picture and then target small areas to inspect. We will load images much wider than the screen available. Say from 1500 to 6000px wide.

As well as the above we want fixed elements for navigation in the top left and top right.

On Webkit/Safari we get the behaviour we want.

enter image description here

Chrome/Android mobile (initial load):

enter image description here

Chrome/Android mobile (zooming all the way out):

enter image description here

Chrome on mobile seems to want to fix the top right div off screen to the layout viewport. Ie the green fixed top right div is off the screen till you zoom all the way out to the whole pic.

These specs seem to suggest this is by design in Chrome.

Setting minimum-scale=1 would superficially seem to resolve this, but it disables zooming in/out which makes this NOT the solution.

Is there a solution to get Chrome to behave the same as Webkit/Safari? Solutions involving Javascript probably acceptable.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Testing fixed width headers on mobile</title>
</head>

<style>
    .tl {
        position:fixed;
        top:0;
        left:0;
        background-color:red;
    }
    .tr {
        position:fixed;
        top:0;
        right:0;
        background-color:green;
    }
    body img {
        margin-top:110px;
    }
</style>

<body>
    <div class="tl">Fixed top left</div>
    <div class="tr">Fixed top right</div>

    <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/7e/In_the_Conservatory.jpg/1280px-In_the_Conservatory.jpg" />
</body>
</html>
like image 811
hawbsl Avatar asked Jul 24 '19 16:07

hawbsl


1 Answers

The cleanest solution would be to place the image inside an element with width: 100%; overflow: auto;

For height of the parent element you could use calc(100% - X) where X is the height of the navbar (this is assuming you want the image to take up the entire screen unless zoomed out).

See below for full example.

EDIT: I have updated the answer below with help from this codepen [https://codepen.io/techslides/pen/zowLd] for the zooming and panning inside a canvas element. You can also add an event listener to check for window resize and redraw the canvas as needed.

var canvas = document.getElementsByTagName('canvas')[0];
canvas.width = window.innerWidth;
canvas.height = window.innerHeight - 20;

var canvasimage = new Image;

window.onload = function() {

  var ctx = canvas.getContext('2d');
  trackTransforms(ctx);

  function redraw() {

    // Clear the entire canvas
    var p1 = ctx.transformedPoint(0, 0);
    var p2 = ctx.transformedPoint(canvas.width, canvas.height);
    ctx.clearRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);

    ctx.save();
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.restore();

    ctx.drawImage(canvasimage, 0, 0);

  }
  redraw();

  var lastX = canvas.width / 2,
    lastY = canvas.height / 2;

  var dragStart, dragged;

  canvas.addEventListener('mousedown', function(evt) {
    document.body.style.mozUserSelect = document.body.style.webkitUserSelect = document.body.style.userSelect = 'none';
    lastX = evt.offsetX || (evt.pageX - canvas.offsetLeft);
    lastY = evt.offsetY || (evt.pageY - canvas.offsetTop);
    dragStart = ctx.transformedPoint(lastX, lastY);
    dragged = false;
  }, false);

  canvas.addEventListener('mousemove', function(evt) {
    lastX = evt.offsetX || (evt.pageX - canvas.offsetLeft);
    lastY = evt.offsetY || (evt.pageY - canvas.offsetTop);
    dragged = true;
    if (dragStart) {
      var pt = ctx.transformedPoint(lastX, lastY);
      ctx.translate(pt.x - dragStart.x, pt.y - dragStart.y);
      redraw();
    }
  }, false);

  canvas.addEventListener('mouseup', function(evt) {
    dragStart = null;
    if (!dragged) zoom(evt.shiftKey ? -1 : 1);
  }, false);

  var scaleFactor = 1.1;

  var zoom = function(clicks) {
    var pt = ctx.transformedPoint(lastX, lastY);
    ctx.translate(pt.x, pt.y);
    var factor = Math.pow(scaleFactor, clicks);
    ctx.scale(factor, factor);
    ctx.translate(-pt.x, -pt.y);
    redraw();
  }

  var handleScroll = function(evt) {
    var delta = evt.wheelDelta ? evt.wheelDelta / 40 : evt.detail ? -evt.detail : 0;
    if (delta) zoom(delta);
    return evt.preventDefault() && false;
  };

  canvas.addEventListener('DOMMouseScroll', handleScroll, false);
  canvas.addEventListener('mousewheel', handleScroll, false);
};

canvasimage.src = 'https://i.stack.imgur.com/ITUHK.png';

// Adds ctx.getTransform() - returns an SVGMatrix
// Adds ctx.transformedPoint(x,y) - returns an SVGPoint
function trackTransforms(ctx) {
  var svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
  var xform = svg.createSVGMatrix();
  ctx.getTransform = function() {
    return xform;
  };

  var savedTransforms = [];
  var save = ctx.save;
  ctx.save = function() {
    savedTransforms.push(xform.translate(0, 0));
    return save.call(ctx);
  };

  var restore = ctx.restore;
  ctx.restore = function() {
    xform = savedTransforms.pop();
    return restore.call(ctx);
  };

  var scale = ctx.scale;
  ctx.scale = function(sx, sy) {
    xform = xform.scaleNonUniform(sx, sy);
    return scale.call(ctx, sx, sy);
  };

  var rotate = ctx.rotate;
  ctx.rotate = function(radians) {
    xform = xform.rotate(radians * 180 / Math.PI);
    return rotate.call(ctx, radians);
  };

  var translate = ctx.translate;
  ctx.translate = function(dx, dy) {
    xform = xform.translate(dx, dy);
    return translate.call(ctx, dx, dy);
  };

  var transform = ctx.transform;
  ctx.transform = function(a, b, c, d, e, f) {
    var m2 = svg.createSVGMatrix();
    m2.a = a;
    m2.b = b;
    m2.c = c;
    m2.d = d;
    m2.e = e;
    m2.f = f;
    xform = xform.multiply(m2);
    return transform.call(ctx, a, b, c, d, e, f);
  };

  var setTransform = ctx.setTransform;
  ctx.setTransform = function(a, b, c, d, e, f) {
    xform.a = a;
    xform.b = b;
    xform.c = c;
    xform.d = d;
    xform.e = e;
    xform.f = f;
    return setTransform.call(ctx, a, b, c, d, e, f);
  };

  var pt = svg.createSVGPoint();
  ctx.transformedPoint = function(x, y) {
    pt.x = x;
    pt.y = y;
    return pt.matrixTransform(xform.inverse());
  }
}
body {
  margin: 0;
  padding: 0;
  overflow: hidden;
}

.wide {
  width: 200vw;
}

.nav {
  width: 100%;
  height: 20px;
}

.clearfix::after {
  content: "";
  clear: both;
  display: table;
}

.left-nav {
  background-color: red;
}

.right-nav {
  float: right;
  background-color: green;
}
<div class="nav clearfix">
  <span class="left-nav">Top Left</span>
  <span class="right-nav">Top Right</span>
</div>
<canvas>
        </canvas>
like image 197
Nathan Fries Avatar answered Nov 06 '22 23:11

Nathan Fries