缩放可滚动容器中的居中div

时间:2015-07-05 01:49:10

标签: javascript html css

我正在尝试对以可滚动容器div为中心的div进行缩放转换。

我用来反映转换后的新div大小的技巧是使用包装器并设置新的宽度/高度,以便父级可以正确显示滚动条。

.container {
    position: relative;
    border: 3px solid red;
    width: 600px; height: 400px;
    background-color: blue;
    overflow-x: scroll; overflow-y: scroll;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
}
.wrapper {
    order: 1;
    background-color: yellow;
}
.content-outer {
    width: 300px;
    height: 200px;
    transform-origin: 50% 50%;
    /*transform-origin: 0 0;*/
}
.content-outer.animatted {
    animation: scaleAnimation 1s ease-in forwards;
}
.content-outer.animatted2 {
    animation: scaleAnimation2 1s ease-in forwards;
}
.content-inner {
    width: 300px;
    height: 200px;
    background: linear-gradient(to right, red, white);
}

如果转换原点为0,0 div在没有动画跳跃的情况下居中,但滚动条不正确。如果原点位于中间,则div位置和滚动条都会被错过

我尝试了两种方法来进行居中,使用flexbox(http://jsfiddle.net/r3jqyjLz/1/)并使用负边距 (http://jsfiddle.net/roLf5tph/1/)。

有更好的方法吗?

5 个答案:

答案 0 :(得分:7)

Is this what you are after

我使用CSS过渡来进行缩放动画。

#centered {
    background-image: linear-gradient(to bottom,yellow,red);
    height: 200px;
    transform: scale(1);
    transition: transform 1s ease-out;
    width: 200px;
}

#scrollable {
    align-items: center;
    border: 1px solid red;
    display: flex;
    height: 300px;
    justify-content: center;
    overflow: auto;
    width: 300px;
}

更新#1

How about this one?

  • 使用旧学校绝对居中(绝对位置)
  • 我看到了关于flexbox的观点。当居中元素大于其容器时,似乎flexbox居中有一些限制。

答案 1 :(得分:5)

我假设您遇到的部分问题可归纳为:

  1. 您的内部div中有一个复杂的结构,您希望将其作为一个组进行扩展。 (如果它是单个元素,那么使用矩阵就可以轻松实现)。
  2. 当内部div缩放超出容器范围时,您不会在不控制宽度/高度的情况下获得滚动条。
  3. 您希望内部div保持居中,同时,滚动条应该反映正确的位置。
  4. 有了这个,有两个(实际上是三个)简单选项。

    选项1:

    在问题中使用相同的标记,当比例因子低于1时,您可以使div居中。对于高于1的比例因子,您可以将其更改为左上角。容器上的overflow: auto将自己处理滚动,因为被缩放的div(通过变换)被包裹在另一个div内。你真的不需要Javascript。

    这解决了你的问题1和2.

    小提琴1:http://jsfiddle.net/abhitalks/c0okhznc/

    Snippet 1

    
    
    * { box-sizing: border-box; }
    #container {
        position: relative;
        border: 1px solid red; background-color: blue;
        width: 400px; height: 300px;
        overflow: auto;
    }
    .wrapper {
        position: absolute; 
        background-color: transparent; border: 2px solid black;
        width: 300px; height: 200px;
        top: 50%; left: 50%;
        transform-origin: top left;
        transform: translate(-50%, -50%); 
        transition: all 1s;
    }
    .box {
        width: 300px; height: 200px;
        background: linear-gradient(to right, red, white);
        border: 2px solid yellow;
    }
    .inner { width:50px; height: 50px; background-color: green; }
    
    #s0:checked ~ div#container .wrapper { transform: scale(0.5) translate(-50%, -50%); }
    #s1:checked ~ div#container .wrapper { transform: scale(0.75) translate(-50%, -50%); }
    #s2:checked ~ div#container .wrapper { transform: scale(1) translate(-50%, -50%); }
    #s3:checked ~ div#container .wrapper { top: 0%; left: 0%; transform-origin: top left; transform: scale(1.5) translate(0%, 0%); }
    #s4:checked ~ div#container .wrapper { top: 0%; left: 0%; transform-origin: top left; transform: scale(2) ; }
    #s5:checked ~ div#container .wrapper { top: 0%; left: 0%; transform-origin: top left; transform: scale(3) ; }
    
    <input id="s0" name="scale" data-scale="0.5" type="radio" />Scale 0.5
    <input id="s1" name="scale" data-scale="0.75" type="radio" />Scale 0.75
    <input id="s2" name="scale" data-scale="1" type="radio" checked />Scale 1
    <input id="s3" name="scale" data-scale="1.5" type="radio" />Scale 1.5
    <input id="s4" name="scale" data-scale="2" type="radio" />Scale 2
    <input id="s5" name="scale" data-scale="3" type="radio" />Scale 3
    <hr>
    <div id="container">
        <div id="wrapper" class="wrapper">
            <div id="box" class="box">
                <div class="inner"></div>
            </div>
        </div>
    </div>
    &#13;
    &#13;
    &#13;

    但这会产生另一个问题。当overflow:auto缩放到容器之外时,div将导致跳转/闪烁。这可以通过使overflow:scroll始终显示滚动条(您已经这样做的方式)来轻松解决。 虽然它在Firefox中很有魅力,Chrome在这里踌躇不前并且不会更新滚动条位置。这里的诀窍是在缩放完成后,通过将overflow更改为auto来使用Javascript强制重排。所以你需要稍微延迟一下。

    小提琴2:http://jsfiddle.net/abhitalks/u7sfef0b/

    Snippet 2

    &#13;
    &#13;
    $("input").on("click", function() {
        var scroll = 'scroll', 
            scale = +($(this).data("scale"));
        if (scale > 1) { scroll = 'auto'; }
        setTimeout(fixScroll, 300, scroll);
    });
    
    function fixScroll(scroll) { $("#container").css({ overflow: scroll }); }
    &#13;
    * { box-sizing: border-box; }
    #container {
        position: relative;
        border: 1px solid red; background-color: blue;
        width: 400px; height: 300px;
        overflow: scroll;
    }
    .wrapper {
        position: absolute; 
        background-color: transparent; border: 2px solid black;
        width: 300px; height: 200px;
        top: 50%; left: 50%;
        transform-origin: top left;
        transform: translate(-50%, -50%); 
        transition: all 1s;
    }
    .box {
        width: 300px; height: 200px;
        background: linear-gradient(to right, red, white);
        border: 2px solid yellow;
    }
    .inner { width:50px; height: 50px; background-color: green; }
    
    #s0:checked ~ div#container .wrapper { transform: scale(0.5) translate(-50%, -50%); }
    #s1:checked ~ div#container .wrapper { transform: scale(0.75) translate(-50%, -50%); }
    #s2:checked ~ div#container .wrapper { transform: scale(1) translate(-50%, -50%); }
    #s3:checked ~ div#container .wrapper { top: 0%; left: 0%;transform-origin: top left; transform: scale(1.5) translate(0%, 0%); }
    #s4:checked ~ div#container .wrapper { top: 0%; left: 0%;transform-origin: top left; transform: scale(2) ; }
    #s5:checked ~ div#container .wrapper { top: 0%; left: 0%;transform-origin: top left; transform: scale(3) ; }
    &#13;
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <input id="s0" name="scale" data-scale="0.5" type="radio" />Scale 0.5
    <input id="s1" name="scale" data-scale="0.75" type="radio" />Scale 0.75
    <input id="s2" name="scale" data-scale="1" type="radio" checked />Scale 1
    <input id="s3" name="scale" data-scale="1.5" type="radio" />Scale 1.5
    <input id="s4" name="scale" data-scale="2" type="radio" />Scale 2
    <input id="s5" name="scale" data-scale="3" type="radio" />Scale 3
    <hr>
    <div id="container">
        <div id="wrapper" class="wrapper">
            <div id="box" class="box">
                <div class="inner"></div>
            </div>
        </div>
    </div>
    &#13;
    &#13;
    &#13;

    选项2:

    为了解决问题3保持div居中并同时保持正确的滚动条位置,您必须回退Javascript。

    原则保持不变,对于大于1的比例因子,您需要重置左上角和translate位置。您还需要重新调整缩放的宽度/高度,然后将其重新分配给包装器div。然后在容器上设置scrollTopscrollLeft就像获取包装器div和容器div之间的差异一样简单。

    这解决了你的问题1,2和3。

    小提琴3:http://jsfiddle.net/abhitalks/rheo6o7p/1/

    代码段3

    &#13;
    &#13;
    var $container = $("#container"), 
        $wrap = $("#wrapper"), 
        $elem = $("#box"), 
        originalWidth = 300, originalHeight = 200;
    
    $("input").on("click", function() {
        var factor = +(this.value);
        scaler(factor);
    });
    
    function scaler(factor) {
        var newWidth = originalWidth * factor,
            newHeight = originalHeight * factor;
        $wrap.width(newWidth); $wrap.height(newHeight);
        if (factor > 1) {
            $wrap.css({ left: 0, top: 0, transform: 'translate(0,0)' }); 
            $elem.css({ transform: 'scale(' + factor + ')' }); 
            setTimeout(setScroll, 400);
        } else {
            $elem.css({ transform: 'scale(' + factor + ') ' });        
            $wrap.css({ left: '50%', top: '50%', transform: 'translate(-50%, -50%)' });
        }
    }
    
    function setScroll() {
        var horizontal, vertical;
        horizontal = ($wrap.width() - $container.width()) / 2;
        vertical = ($wrap.height() - $container.height()) / 2;
        $container.stop().animate({scrollTop: vertical, scrollLeft: horizontal}, 500);
    }
    &#13;
    * { box-sizing: border-box; }
    #container {
        position: relative;
        border: 1px solid red; background-color: blue;
        width: 400px; height: 300px;
        overflow: scroll;
    }
    .wrapper {
        position: absolute; 
        background-color: transparent; border: 2px solid black;
        width: 300px; height: 200px;
        top: 50%; left: 50%;
        transform: translate(-50%, -50%); 
        transition: all 0.5s;
    }
    .box {
        width: 300px; height: 200px;
        background: linear-gradient(to right, red, white);
        border: 2px solid yellow;
        transform-origin: top left;
        transition: all 0.5s;
    }
    .inner { width:50px; height: 50px; background-color: green; }
    &#13;
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <label for="slider1">Scale: </label>
    <input id="slider1" type="range" min="0.5" max="3" value="1" step="0.25" list="datalist" onchange="scaleValue.value=value" />
    <output for="slider1" id="scaleValue">1</output>
    <datalist id="datalist">
    	<option>0.5</option>
    	<option>0.75</option>
    	<option>1</option>
        <option>1.5</option>
        <option>2</option>
        <option>3</option>
    </datalist>
    <hr>
    <div id="container">
        <div id="wrapper" class="wrapper">
            <div id="box" class="box">
                <div class="inner"></div>
            </div>
        </div>
    </div>
    &#13;
    &#13;
    &#13;

    使用IE-11,GC-43和FF-38测试的所有脚本

答案 2 :(得分:3)

这个问题的难点在于,在放大过程中,您需要从模型(仍有边距)更改为需要扩展容器大小的另一个模型。 / p>

由于这发生在转换过程中,单个属性转换很难处理

我找到解决此问题的方法是更改​​min-height和min-width属性。这样就可以自动检测这一点并优雅地处理它

我只在JS中保留基本功能,其他一切都在CSS中完成

&#13;
&#13;
function setZoom3() {
    var ele = document.getElementById("base");
    ele.className = "zoom3";
}

function setZoom1() {
    var ele = document.getElementById("base");
    ele.className = "";
}

function setZoom05() {
    var ele = document.getElementById("base");
    ele.className = "zoom05";
}
&#13;
.container {
    width: 300px;
    height: 300px;
    overflow: scroll;
    position: relative;
}

#base {
    width: 300px;
    height: 300px;
    top: 0px;
    left: 0px;
    position: absolute;
    min-width: 300px;
    min-height: 300px;
    transition: min-width 5s, min-height 5s;
}

.inner {
    width: 200px;
    height: 200px;
    background-color: lightgreen;
    background-image: linear-gradient(-45deg, red 5%, yellow 5%, green 95%, blue 95%);
    top: 50%;
    left: 50%;
    position: absolute;
    transform: translate(-50%, -50%); 
    transform-origin: top left;
    transition: transform 5s;
}

#base.zoom3 {
    min-width: 600px;
    min-height: 600px;
}
.zoom3 .inner {
    transform: scale(3) translate(-50%, -50%);
}

.zoom05 .inner {
    transform: scale(0.5) translate(-50%, -50%);
}

.trick {
    width: 20px;
    height: 20px;
    background-color: red;
    position: absolute;
    
    transform: translate(0px);
}
&#13;
<div class="container">
    <div id="base">
        <div class="inner"></div>
    </div>
</div>
<button onclick="setZoom3();">zoom 3</button>
<button onclick="setZoom1();">zoom 1</button>
<button onclick="setZoom05();">zoom 0.5</button>
&#13;
&#13;
&#13;

唯一的问题是设置滚动位置,以防万一你希望它保持在中心位置

答案 3 :(得分:2)

我设法用一点逻辑来解决它。我已经使用GSAP进行动画和更新,但您可以轻松地使用vanilla JS:

http://codepen.io/theprojectsomething/pen/JdZWLV

...由于需要滚动父div以保持元素居中(并且无法为滚动设置动画),因此单独使用CSS无法实现平滑过渡。

注意:即使没有滚动,纯CSS转换也似乎无法同步(偏移,无论是上/左,边距还是平移,总是赶上比例)。这可能是由于子像素定位?其他人可能会在这里提供进一步的见解。

Codepen的完整代码:

&#13;
&#13;
var $inner = document.querySelector('aside'),
    $outer = document.querySelector('main'),
    $anim = document.querySelector('[type="checkbox"]'),
    $range = document.querySelector('[type="range"]'),
    data = {
      width: $outer.clientWidth,
      value: 1
    };

$range.addEventListener('input', slide, false);

function slide () {
  $anim.checked ? animate(this.value) : transform(this.value);
}

function animate (value) {
  TweenLite.to(data, 0.4, {
    value: value,
    onUpdate: transform
  });
}

function transform (value) {
  if( !isNaN(value) ) data.value = value;
  
  var val = Math.sqrt(data.value),
      offset = Math.max(1 - val, 0)*0.5,
      scroll = (val - 1)*data.width*0.5;

  TweenLite.set($inner, {
    scale: val,
    x: offset*100 + "%",
    y: offset*100 + "%"
  });
  
  TweenLite.set($outer, {
    scrollLeft: scroll,
    scrollTop: scroll
  });
}

window.onresize = function (){
  data.width = $outer.clientWidth;
  transform(data.value);
};
&#13;
main, aside:before, footer {
  position: absolute;
  top: 50%;
  left: 50%;
  -webkit-transform: translate(-50%, -50%);
      -ms-transform: translate(-50%, -50%);
          transform: translate(-50%, -50%);
}

main {
  width: 50vh;
  height: 50vh;
  min-width: 190px;
  min-height: 190px;
  background: black;
  overflow: scroll;
  box-sizing: border-box;
}

aside {
  position: relative;
  width: 100%;
  height: 100%;
  border: 1vh solid rgba(0, 0, 0, 0.5);
  background-image: -webkit-linear-gradient(top, yellow, red);
  background-image: linear-gradient(to bottom, yellow, red);
  box-sizing: border-box;
  -webkit-transform-origin: 0 0;
      -ms-transform-origin: 0 0;
          transform-origin: 0 0;
}
aside:before {
  content: "";
  background: black;
  border-radius: 50%;
  width: 10%;
  height: 10%;
  display: block;
  opacity: 0.5;
}

input {
  display: block;
  margin: 3em auto;
}
input:after {
  content: attr(name);
  color: white;
  text-transform: uppercase;
  position: relative;
  left: 2em;
  display: block;
  line-height: 0.9em;
}
&#13;
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.16.1/TweenMax.min.js"></script>
<main>
  <aside></aside>
</main>
<footer>
  <input type="checkbox" name="animate" checked>
  <input type="range" min="0.1" step="0.005" max="3">
</footer>
&#13;
&#13;
&#13;

答案 4 :(得分:1)

马修金的小提琴接近正确。
唯一需要修正的是div的偏移量。如果您的内部div超出了可滚动div的边界(负偏移),则需要将偏移设置为0.否则,您希望div居中。

居中不是那么简单。您正在使用css缩放div的背景,因此您不会影响div的实际宽度和高度,需要解析缩放矩阵以计算其背景尺寸。

此解决方案适用于我: https://jsfiddle.net/6e2g6vzt/11/
(至少对于Ubuntu上的FF,尚未在其他浏览器上测试)

它基本上只是一个函数来设置新的偏移量,你甚至不必手动调用它,因为这行代码

$("#centered").bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", setNewOffset);

在转换完成后调用setNewOffset函数
正如你可以看到图像在转换后“跳转”到正确的位置,也许你想要添加一个平滑的效果来覆盖它,但我只想告诉你如何获得正确的偏移。

查看jQuery的文档以了解有关.offset()

的更多信息


现金:
感谢Jim Jeffers在过渡后的良好回调中https://stackoverflow.com/a/9255507/3586288
感谢Lea Verou正确的正则表达式解析比例矩阵https://stackoverflow.com/a/5604199/3586288