web-dev-qa-db-ja.com

CSS変換scale()を使用して、スクロールを維持しながら、トリミングせずに要素にズームインします

実例: https://jsfiddle.net/b8vLg0ny/

CSS scaleおよびtranslate関数を使用して要素を拡大することができます。

この例では、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の値を制御することで、ズームの動作を変更できます。

最初にレンダリングされたビューは次のようになります。

initial rendered view

Aボックスをクリックすると、適切にズームインされます。

zooming to A and then zooming out

(最後にDをクリックすると、ズームアウトしてリセットが表示されることに注意してください。)

問題はです。ボックスDにズームすると、内容がオーバーフローするため、上下にスクロールできないようにズームコンテナが拡大縮小されます。ボックスB(左半分がトリミングされている)とC(上半分がトリミングされている)にズームした場合も同じことが起こります。 Aを使用した場合にのみ、コンテンツがコンテナの外にオーバーフローすることはありません。

スケーリングに関連する同様の状況( CSS3 Transform Scale and Container with Overflow を参照)では、考えられる解決策の1つは、transform-Origin: top left(または0 0)を指定することです。左上を基準にしたスケーリングの動作方法により、スクロール機能は維持されます。これは、クリックされたボックス(A、B、C、またはD)に焦点を合わせるためにコンテンツの位置を変更しなくなったことを意味するため、ここでは機能しないようです。

別の可能な解決策は、ズームコンテナにmargin-leftmargin-topを追加することです。これにより、オーバーフローしたコンテンツを補うのに十分なスペースが追加されます。しかし、繰り返しになりますが、変換値はもはや整列していません。

つまり、両方で特定の要素を拡大し、をスクロールでオーバーフローさせて、コンテンツがオーバーフローしないようにする方法はありますか?トリミング?

UpdatescrollTopscrollLeftをアニメーション化することで、ほぼ解決策があります。これは https: //stackoverflow.com/a/31406704/528044jsfiddleの例 を参照)が、最初に目的のターゲットではなく左上にズームするため、適切なソリューションではありません。おそらくscrollLeftを負にするように要求するのと同じであるため、これは実際には不可能であると私は考え始めています。

16
Adam Prescott

TransformOrigin0 0に再配置し、アニメーションの後に適切なscrollTop/scrollLeftafterを使用しないのはなぜですか?

アニメーションが必要ない場合、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>
7
tenbits

更新

スクロールバーが常に表示されないので、その部分を調査する必要があります。コードがコメントアウトされ、代わりに遅延を使用してクリックされたボックスが表示されます。

これが 私のフィドルデモ で、スクロールバーの問題を解決する方法を理解するために使用しています。

補足:@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>
1
LGSon

与えられた要件では実際には不可能であるとかなり確信しているので、私は自分の質問に答えています。少なくとも、視覚的に問題を引き起こす可能性のあるハッカーがないわけではありません。たとえば、transform-Origin0, 0に切り替えた後にscrollTopをアニメーション化してジャンプするスクロール(すべてをコンテナに戻すことでトリミングを削除します) 。

誰かに私が間違っていることを証明してもらいたいのですが、それはscrollLeft = -10を要求するのと同じように思えます。これは、 MDNは不可能だと教えてくれます 。 ( "0 [...]未満の値に設定すると、scrollLeftは0に設定されます。")

ただし、Iをスクロールからズームおよびドラッグ/パンに変更するが許容される場合は、次のように実行できます。 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
//
//   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;
}

私はこれを別の質問( 純粋なjsでオブジェクト(画像またはdiv)を移動(ドラッグ/パン)およびズーム )からつなぎ合わせました。ここで、widthheightは変更されています。私の場合、これはまったく当てはまりません。ページ上の特定の要素にズームインする必要があるためです(2x2グリッドよりも多くのボックスがあります)。その質問の解決策( https://jsfiddle.net/j8kLz6wm/1/ )は、純粋なJavaScriptでの基本的なアプローチを示しています。 jQueryを利用できる場合は、おそらく jquery.panzoom を使用できます。

1
Adam Prescott