【问题标题】:Using CSS transform scale() to zoom into an element without cropping, maintaining scrolling使用 CSS 变换 scale() 放大元素而不裁剪,保持滚动
【发布时间】:2016-04-03 17:10:51
【问题描述】:

现场示例:https://jsfiddle.net/b8vLg0ny/

可以使用 CSS scaletranslate 函数来放大元素。

以 2x2 网格中的 4 个框为例。

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;
  }
});

通过控制translateXtranslateY的值,可以改变缩放的工作方式。

初始渲染视图如下所示:

点击 A 框会适当放大:

(请注意,最后单击 D 只是通过缩小显示重置。)

问题是:缩放到框 D 将缩放缩放容器,这样滚动到顶部和左侧不起作用,因为内容溢出。当缩放到框 B(左半部分被裁剪)和 C(上半部分被裁剪)时,也会发生同样的情况。只有 A 的内容才不会溢出到容器外。

在与缩放相关的类似情况下(请参阅CSS3 Transform Scale and Container with Overflow),一种可能的解决方案是指定transform-origin: top left(或0 0)。由于缩放相对于左上角的工作方式,滚动功能保持不变。但这似乎在这里不起作用,因为这意味着您不再将内容重新定位以将焦点放在单击的框(A、B、C 或 D)上。

另一种可能的解决方案是在缩放容器中添加margin-leftmargin-top,这会增加足够的空间来弥补溢出的内容。但同样:翻译值不再排列。

那么:有没有办法放大给定元素,滚动溢出,这样内容就不会被裁剪?

更新:通过动画scrollTopscrollLeft 有一个粗略的几乎解决方案,类似于https://stackoverflow.com/a/31406704/528044(请参阅the jsfiddle example),但这不是一个非常合适的解决方案,因为它首先缩放到左上角,而不是预期的目标。我开始怀疑这实际上是不可能的,因为这可能相当于要求scrollLeft 是否定的。

【问题讨论】:

  • 如果我说,所有的框都被放大了,但点击的那个应该在视图中,对吧?然后,通过滚动,可以移动到任何框,对吧?
  • 点击一个框应该放大被点击的框。然后应该可以滚动到任何其他框以将它们显示出来。
  • 我正在一点一点地工作,已经走到这一步了,jsfiddle.net/LGSon/b8vLg0ny/5 ...我可能会在可能的情况下提供赏金(发布后 2 天),以增加对您的吸引力我认为这是一个很好的问题,我也对类似的解决方案感兴趣
  • 我已经用stackoverflow.com/a/31406704/528044 的链接更新了我的问题,它有一个粗略的半解决方案,但动画时间有点笨拙。
  • 我之前遇到的另一个类似问题可能会对您有所帮助:stackoverflow.com/questions/34196639/zooming-in-overflow-scroll 我自己的答案无法满足您的所有要求。

标签: javascript html css css-transforms scaletransform


【解决方案1】:

为什么不只是将TransformOrigin 重新定位为0 0 并在动画之后使用正确的scrollTop/scrollLeft

如果你不需要动画,TransformOrigin可以一直保持0 0,只用滚动来显示框。

为了让动画不那么跳跃,只对transform 属性使用过渡,否则transform-origin 也会被动画化。我已经使用4x4 元素编辑了示例,但我认为将一个框完全缩放到视图中是有意义的,这就是我更改缩放级别的原因。但是,如果您保持缩放级别 2 和网格大小 15x15 例如,那么使用这种方法应该计算出真正精确的原点以进行变换,然后还要计算正确的滚动。

反正我不知道,如果你觉得这种方法有用。

堆栈sn-p

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>

【讨论】:

  • 主要是因为每当我尝试这样做时,它似乎都无法用于大网格(例如,10x10 或 15x15)。例如,当单击右下角区域中的框时,这对我来说似乎真的很跳跃:jsfiddle.net/b8vLg0ny/9(尽管我承认我可能在 JS 中遗漏了一些我没有更新足够值的东西)。放大到0 0 的动画最终与滚动动画不同步。
  • @AdamPrescott 在 tenbits 更新后,它现在可以按您的意愿工作吗? ...问是因为我认为进一步改进我没有任何意义,因为这一次很棒。
  • 这看起来是最好的解决方案!
【解决方案2】:

我正在回答我自己的问题,因为我相当有信心在给定的要求下这实际上是不可能的。至少不会没有一些会导致视觉问题的黑客行为,例如,在将 transform-origin 切换为 0, 0 后,通过动画 scrollTop 进行跳跃滚动(通过将所有内容放回容器中来消除裁剪)。

我希望有人能证明我错了,但这似乎等同于要求scrollLeft = -10,即MDN will tell you is not possible。 (“如果设置为小于 0 [...] 的值,则 scrollLeft 设置为 0。”)

但是,如果将 UI 从滚动更改为缩放和拖动/平移是可以接受的,那么它是可以实现的:https://jsfiddle.net/jegn4x0f/5/

这是与我原来的问题具有相同上下文的解决方案:

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
//
//   https://stackoverflow.com/questions/35252249/move-drag-pan-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's styling for transitions
  // which brings back the stylesheet's 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's translate values
  // based on the original + delta, capped to the
  // container's 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's 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's 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;
}

我从另一个问题 (Move (drag/pan) and zoom object (image or div) in pure js) 拼凑出这一点,其中 widthheight 正在更改。这在我的情况下不太适用,因为我需要放大页面上的特定元素(有很多框而不是 2x2 网格)。该问题 (https://jsfiddle.net/j8kLz6wm/1/) 的解决方案显示了纯 JavaScript 的基本方法。如果您有可用的 jQuery,您可能只需要使用 jquery.panzoom

【讨论】:

    【解决方案3】:

    更新

    我卡在滚动条上并没有一直显示,所以我需要调查那部分,以便将代码注释掉,而是使用延迟将点击的框移到视图中。

    这是我用来玩的my fiddle demo,想知道如何解决滚动条问题。

    旁注:在@AVAVT 发表的评论中,我想链接到his post here,因为这可能对其他人有所帮助,我认为在某些情况下这是一个有趣的替代方案。

    (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>

    【讨论】:

    • 这不太行。将在我的原始示例中单击 D 与在此示例中单击 D 进行比较。它们没有正确对齐,因此位置已关闭。与 B 类似:它偏离视图宽度的一半。
    • @AdamPrescott 是的,你必须调整它,我没有,只是展示了如何同时获得滚动条和缩放。
    • 当然。它确实缩放,但在错误的部分,这是这里的主要问题。
    猜你喜欢
    • 1970-01-01
    • 2022-07-27
    • 2012-10-17
    • 1970-01-01
    • 1970-01-01
    • 2011-12-20
    • 1970-01-01
    • 2016-07-03
    • 2014-03-04
    相关资源
    最近更新 更多