Подтвердить что ты не робот

Использование масштабирования CSS() для масштабирования элемента без обрезки, поддерживая прокрутку

Пример в реальном времени: https://jsfiddle.net/b8vLg0ny/

Можно использовать функции CSS scale и translate для масштабирования элемента.

Взять этот пример из 4-х ящиков в сетке 2x2.

HTML:

<div id="container">
  <div id="zoom-container">
    <div class="box red">A</div>
    <div class="box blue">B</div>
    <div class="box green">C</div>
    <div class="box black">D</div>
  </div>
</div>

CSS

* { margin: 0; }

body, html { height: 100%; }

#container {
  height: 100%;
  width: 50%;
  margin: 0 auto;
}

#zoom-container {
  height: 100%;
  width: 100%;
  transition: all 0.2s ease-in-out;
}

.box {
  float: left;
  width: 50%;
  height: 50%;
  color: white;
  text-align: center;
  display: block;
}

.red { background: red; }
.blue { background: blue; }
.green { background: green; }
.black { background: black; }

JavaScript:

window.zoomedIn = false;

$(".box").click(function(event) {
  var el = this;
  var zoomContainer = $("#zoom-container");

  if (window.zoomedIn) {
    console.log("resetting zoom");
    zoomContainer.css("transform", "");
    $("#container").css("overflow", "auto");
    window.zoomedIn = false;
  } else {
    console.log("applying zoom");
    var top = el.offsetTop;
    var left = el.offsetLeft - 0.25*zoomContainer[0].clientWidth;

    var translateY = 0.5*zoomContainer[0].clientHeight - top;
    var translateX = 0.5*zoomContainer[0].clientWidth - left;

    $("#container").css("overflow", "scroll");
    zoomContainer.css("transform", "translate(" + 2 * translateX + "px, " + 2 * translateY + "px) scale(2)");
    window.zoomedIn = true;
  }
});

Контролируя значения translateX и translateY, вы можете изменить способ масштабирования.

Первоначальный визуализированный вид выглядит примерно так:

начальный визуализированный просмотр

При нажатии на поле "А" вы будете соответствующим образом масштабировать:

масштабирование до A, а затем масштабирование

(Обратите внимание, что нажатие кнопки D в конце просто показывает reset путем изменения размера.)

Проблема: масштабирование в поле D будет масштабировать контейнер масштабирования таким образом, чтобы прокрутка вверх и вниз не работала, потому что содержимое переполнено. То же самое происходит при масштабировании до ящиков B (левая половина обрезана) и C (верхняя половина обрезана). Только с A содержимое не переполняется вне контейнера.

В подобных ситуациях, связанных с масштабированием (см. CSS3 Transform Scale и Container with Overflow), одним из возможных решений является указание transform-origin: top left (или 0 0). Из-за того, как масштабирование работает относительно верхнего левого угла, функция прокрутки остается. Это, кажется, не работает здесь, потому что это означает, что вы больше не перемещаете содержимое, которое должно быть сфокусировано на щелчке (A, B, C или D).

Другим возможным решением является добавление в контейнер масштабирования margin-left и margin-top, что добавляет достаточно места для восполнения переполненного содержимого. Но опять же: значения перевода больше не выстраиваются в линию.

Итак: есть ли способ как увеличить данный элемент, так и переполнить с помощью прокрутки, чтобы содержимое не обрезано?

Обновление. Там грубое почти решение, оживляя scrollTop и scrollLeft, похожее на qaru.site/info/240060/... (см. пример jsfiddle), но это не совсем правильное решение, потому что оно сначала масштабирует вверху слева, а не намеченную цель. Я начинаю подозревать, что это на самом деле невозможно, потому что он, вероятно, эквивалентен запросу scrollLeft быть отрицательным.

4b9b3361

Ответ 1

Почему бы просто не переместить TransformOrigin в 0 0 и использовать подходящую scrollTop/scrollLeft после анимацию?

Если вам не нужна анимация, TransformOrigin всегда остается 0 0, и только прокрутка используется для отображения окна.

Чтобы сделать анимацию менее загруженной, используйте переход только для transform porperty, в противном случае анимируется transform-origin. Я отредактировал пример с 4x4 элементами, но я думаю, что имеет смысл увеличить окно полностью, поэтому я изменил уровень масштабирования. Но если вы, например, остаетесь на уровне масштабирования 2 и размером сетки 15x15, то при таком подходе нужно точно вычислить происхождение для преобразования, а затем и правильную прокрутку.

Во всяком случае, я не знаю, если вы найдете этот подход полезным.

Фрагмент стопки

var zoomedIn = false;
var zoomContainer = $("#zoom-container");

$(".box").click(function(event) {
  var el = this;
  
  if (zoomedIn) {    
    zoomContainer.css({
    	transform: "scale(1)",
      transformOrigin: "0 0"
    });
    zoomContainer.parent().scrollTop(0).scrollLeft(0);
    zoomedIn = false;
    return;
  } 
  zoomedIn = true;
  var $el = $(el);
  animate($el);
  zoomContainer.on('transitionend', function(){
  	zoomContainer.off('transitionend');
  	reposition($el);
  })
});

var COLS = 4, ROWS = 4, 
  	COLS_STEP = 100 / (COLS - 1), ROWS_STEP = 100 / (ROWS - 1),
    ZOOM = 4;
  

function animate($box) {
  var cell = getCell($box);
  var col =  cell.col * COLS_STEP + '%',
      row =  cell.row * ROWS_STEP + '%';
  zoomContainer.parent().css('overflow', 'hidden');
	zoomContainer.css({
    transition: 'transform 0.2s ease-in-out',
  	transform: "scale(" + ZOOM + ")",
    transformOrigin: col + " " + row
  });
}
function reposition($box) {
  zoomContainer.css({
    transition: 'none',
  	transform: "scale(" + ZOOM + ")",
    transformOrigin: '0 0'
  });  
  zoomContainer.parent().css('overflow', 'auto');
  $box.get(0).scrollIntoView();
}
function getCell ($box) {
	var idx = $box.index();
  var col = idx % COLS,
      row =  (idx / ROWS) | 0;
  return { col: col, row: row };
}
* { margin: 0; }

body, html { height: 100%; }

#container {
  height: 100%;
  width: 50%;
  margin: 0 auto;
  overflow: hidden;
}

#zoom-container {
  height: 100%;
  width: 100%;
  will-change: transform;
}

.box {
  float: left;
  width: 25%;
  height: 25%;
  color: white;
  text-align: center;  
}

.red { background: red; }
.blue { background: blue; }
.green { background: green; }
.black { background: black; }
.l { opacity: .3 }
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>

<div id="container">
  <div id="zoom-container">
    <div class="box red">A</div>
    <div class="box blue">B</div>
    <div class="box green">C</div>
    <div class="box black">D</div>

    <div class="box red l">E</div>
    <div class="box blue l">F</div>
    <div class="box green l">G</div>
    <div class="box black l">H</div>

    <div class="box red">I</div>
    <div class="box blue">J</div>
    <div class="box green">K</div>
    <div class="box black">L</div>

    <div class="box red l">M</div>
    <div class="box blue l">N</div>
    <div class="box green l">O</div>
    <div class="box black l">P</div>
  </div>
</div>

Ответ 2

Update

Я застрял на полосах прокрутки, которые не показывались все время, поэтому мне нужно исследовать эту часть, чтобы код был прокомментирован, и вместо этого я использую задержку, чтобы переместить щелчок в поле зрения.

Вот моя демонстрация для скриптов, с которой я использую, чтобы выяснить, как решить проблему с полосой прокрутки.

Боковое примечание: в комментарии от @AVAVT я хотел бы ссылаться на его сообщение здесь, поскольку это может помочь кому-то другому, который я нахожу в качестве интересная альтернатива в некоторых случаях.

(function(zoomed) {
  
  $(".box").click(function(event) {
    
    var el = this, elp = el.parentElement;
    
    if (zoomed) {
      zoomed = false;
      $("#zoom-container").css({'transform': ''});
      
    } else {
      zoomed = true;
      /*  this zooms correct but show 1 or none scroll for B,C,D so need to figure out why
      
      var tro = (Math.abs(elp.offsetTop - el.offsetTop) > 0) ? 'bottom' : 'top';
      tro += (Math.abs(elp.offsetLeft - el.offsetLeft) > 0) ? ' right' : ' left';
      $("#zoom-container").css({'transform-origin': tro, 'transform': 'scale(2)'});
      */
      
      $("#zoom-container").css({'transform-origin': '0 0', 'transform': 'scale(2)'});
      /* delay needed before scroll into view */      
      setTimeout(function() {
        el.scrollIntoView();
      },250);
    }    
  });
})();
* { margin: 0; }

body, html { height: 100%; }

#container {
  height: 100%;
  width: 50%;
  overflow: auto;
  margin: 0 auto;
}

#zoom-container {
  height: 100%;
  width: 100%;
  transition: all 0.2s ease-in-out;
}

.box {
  float: left;
  width: 50%;
  height: 50%;
  color: white;
  text-align: center;
  display: block;
}

.red {
  background: red; 
}
.blue {
  background: blue;
}
.green {
  background: green;
}
.black {
  background: black;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<div id="container">
  <div id="zoom-container">
    <div class="box red">A</div>
    <div class="box blue">B</div>
    <div class="box green">C</div>
    <div class="box black">D</div>
  </div>
</div>

Ответ 3

Я отвечаю на свой вопрос, так как я достаточно уверен, что это невозможно с учетом данных требований. По крайней мере, не без хакера, который может вызвать проблемы визуально, например, прыгать прокруткой, анимируя scrollTop после переключения transform-origin на 0, 0 (который удаляет обрезку, возвращая все обратно в контейнер).

Мне бы хотелось, чтобы кто-то доказал, что я ошибаюсь, но похоже, что он просил scrollLeft = -10, что MDN скажет вам, что это невозможно. ( "Если установлено значение меньше 0 [...], scrollLeft устанавливается в 0." )

Если, однако, это приемлемо для изменения пользовательского интерфейса от прокрутки, масштабирования и перетаскивания/панорамирования, тогда оно достижимо: https://jsfiddle.net/jegn4x0f/5/

Здесь решение с тем же контекстом, что и моя оригинальная проблема:

zoom-pan

HTML:

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>

<button id="zoom-out">Zoom out</button>

<div id="container">
  <div id="inner-container">
    <div id="zoom-container">
      <div class="box red">A</div>
      <div class="box blue">B</div>
      <div class="box green">C</div>
      <div class="box black">D</div>
    </div>
  </div>
</div>

JavaScript:

//
// credit for the approach goes to
//
//   /info/417956/move-dragpan-and-zoom-object-image-or-div-in-pure-js#comment58224460_35253567
//
// and the corresponding example:
//
//  https://jsfiddle.net/j8kLz6wm/1/
//

// in a real-world setting, you
// wouldn't keep this information
// on window. this is just for
// the demonstration.
window.zoomedIn = false;

// stores the initial translate values after clicking on a box
window.translateY = null;
window.translateX = null;

// stores the incremental translate values based on
// applying the initial translate values + delta
window.lastTranslateY = null;
window.lastTranslateX = null;

// cursor position relative to the container, at
// the time the drag started
window.dragStartX = null;
window.dragStartY = null;

var handleDragStart = function(element, xCursor, yCursor) {
  window.dragStartX = xCursor - element.offsetLeft;
  window.dragStartY = yCursor - element.offsetTop;

  // disable transition animations, since we're starting a drag
  $("#zoom-container").css("transition", "none");
};

var handleDragEnd = function() {
  window.dragStartX = null;
  window.dragStartY = null;
  // remove the individual element styling for transitions
  // which brings back the stylesheet default of animating.
  $("#zoom-container").css("transition", "");

  // keep track of the translate values we arrived at
  window.translateY = window.lastTranslateY;
  window.translateX = window.lastTranslateX;
};

var handleDragMove = function(xCursor, yCursor) {
  var deltaX = xCursor - window.dragStartX;
  var deltaY = yCursor - window.dragStartY;

  var translateY = window.translateY + (deltaY / 2);
  // the subtracted value here is to keep the letter in the center
  var translateX = window.translateX + (deltaX / 2) - (0.25 * $("#inner-container")[0].clientWidth);

  // fudge factor, probably because of percentage
  // width/height problems. couldn't really trace down
  // the underlying cause. hopefully the general approach
  // is clear, though.
  translateY -= 9;
  translateX -= 4;

  var innerContainer = $("#inner-container")[0];

  // cap all values to prevent infinity scrolling off the page
  if (translateY > 0.5 * innerContainer.clientHeight) {
    translateY = 0.5 * innerContainer.clientHeight;
  }

  if (translateX > 0.5 * innerContainer.clientWidth) {
    translateX = 0.5 * innerContainer.clientWidth;
  }

  if (translateY < -0.5 * innerContainer.clientHeight) {
    translateY = -0.5 * innerContainer.clientHeight;
  }

  if (translateX < -0.5 * innerContainer.clientWidth) {
    translateX = -0.5 * innerContainer.clientWidth;
  }

  // update the zoom container translate values
  // based on the original + delta, capped to the
  // container width and height.
  $("#zoom-container").css("transform", "translate(" + (2*translateX) + "px, " + (2*translateY) + "px) scale(2)");

  // keep track of the updated values for the next
  // touchmove event.
  window.lastTranslateX = translateX;
  window.lastTranslateY = translateY;
};

// Drag start -- touch version
$("#container").on("touchstart", function(event) {
  if (!window.zoomedIn) {
    return true;
  }

  var xCursor = event.originalEvent.changedTouches[0].clientX;
  var yCursor = event.originalEvent.changedTouches[0].clientY;

  handleDragStart(this, xCursor, yCursor);
});

// Drag start -- mouse version
$("#container").on("mousedown", function(event) {
  if (!window.zoomedIn) {
    return true;
  }

  var xCursor = event.clientX;
  var yCursor = event.clientY;

  handleDragStart(this, xCursor, yCursor);
});

// Drag end -- touch version
$("#inner-container").on("touchend", function(event) {
  if (!window.zoomedIn) {
    return true;
  }

  handleDragEnd();
});

// Drag end -- mouse version
$("#inner-container").on("mouseup", function(event) {
  if (!window.zoomedIn) {
    return true;
  }

  handleDragEnd();
});

// Drag move -- touch version
$("#inner-container").on("touchmove", function(event) {
  // prevent pull-to-refresh. could be smarter by checking
  // if the page scroll y-offset is 0, and even smarter
  // by checking if we're pulling down, not up.
  event.preventDefault();

  if (!window.zoomedIn) {
    return true;
  }

  var xCursor = event.originalEvent.changedTouches[0].clientX;
  var yCursor = event.originalEvent.changedTouches[0].clientY;

  handleDragMove(xCursor, yCursor);
});

// Drag move -- click version
$("#inner-container").on("mousemove", function(event) {
  // prevent pull-to-refresh. could be smarter by checking
  // if the page scroll y-offset is 0, and even smarter
  // by checking if we're pulling down, not up.
  event.preventDefault();

  // if we aren't dragging from anywhere, don't move
  if (!window.zoomedIn || !window.dragStartX) {
    return true;
  }

  var xCursor = event.clientX;
  var yCursor = event.clientY;

  handleDragMove(xCursor, yCursor);
});

var zoomInTo = function(element) {
  console.log("applying zoom");

  var top = element.offsetTop;
  // the subtracted value here is to keep the letter in the center
  var left = element.offsetLeft - (0.25 * $("#inner-container")[0].clientWidth);

  var translateY = 0.5 * $("#zoom-container")[0].clientHeight - top;
  var translateX = 0.5 * $("#zoom-container")[0].clientWidth - left;

  $("#container").css("overflow", "scroll");
  $("#zoom-container").css("transform", "translate(" + (2*translateX) + "px, " + (2*translateY) + "px) scale(2)");
  window.translateY = translateY;
  window.translateX = translateX;

  window.zoomedIn = true;
}

var zoomOut = function() {
  console.log("resetting zoom");

  window.zoomedIn = false;
  $("#zoom-container").css("transform", "");
  $("#zoom-container").css("transition", "");
  window.dragStartX = null;
  window.dragStartY = null;
  window.dragMoveJustHappened = null;
  window.translateY = window.lastTranslateY;
  window.translateX = window.lastTranslateX;
  window.lastTranslateX = null;
  window.lastTranslateY = null;
}

$(".box").click(function(event) {
  var element = this;
  var zoomContainer = $("#zoom-container");

  if (!window.zoomedIn) {
    zoomInTo(element);
  }
});

$("#zoom-out").click(function(event) {
  zoomOut();
});

CSS

* {
  margin: 0;
}

body,
html {
  height: 100%;
}

#container {
  height: 100%;
  width: 50%;
  margin: 0 auto;
}

#inner-container {
  width: 100%;
  height: 100%;
}

#zoom-container {
  height: 100%;
  width: 100%;
  transition: transform 0.2s ease-in-out;
}

.box {
  float: left;
  width: 50%;
  height: 50%;
  color: white;
  text-align: center;
  display: block;
}

.red {
  background: red;
}

.blue {
  background: blue;
}

.green {
  background: green;
}

.black {
  background: black;
}

Я собрал это вместе из другого вопроса (Переместить (перетащить/панорамировать) и объект масштабирования (изображение или div) в чистые js), где width и height меняются. Это не совсем применимо в моем случае, потому что мне нужно масштабировать определенный элемент на странице (с большим количеством ящиков, чем в сетке 2x2). Решение этого вопроса (https://jsfiddle.net/j8kLz6wm/1/) показывает базовый подход в чистом JavaScript. Если у вас есть jQuery, вы можете просто использовать jquery.panzoom.