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

Масштабирование центрированного div в прокручиваемом контейнере

Я пытаюсь преобразовать масштаб в 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/).

Есть ли лучший способ сделать это?

4b9b3361

Ответ 1

Это то, что вы после?

Я использовал переходы 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

Как насчет этого?

  • Использование абсолютной централизации старой школы (абсолютная позиция)
  • Я вижу вашу мысль о flexbox. Кажется, что центрирование flexbox имеет некоторые ограничения, когда центрированный элемент больше, чем его контейнер.

Ответ 2

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

  • У вас есть сложная структура в вашей внутренней div, которую вы хотите масштабировать как группу. (Если бы это был один элемент, это было бы легко с помощью матрицы).
  • Когда внутренний div масштабируется за пределы контейнера, вы не получаете полосы прокрутки без контроля ширины/высоты.
  • Вы хотите, чтобы внутренний div оставался центрированным, и в то же время полосы прокрутки должны отражать правильную позицию.

При этом есть два (три действительно) простых варианта.

Вариант 1:

Используя ту же разметку в вопросе, вы можете сохранить div в центре, когда масштабный коэффициент ниже 1. И для масштабных коэффициентов выше 1 вы измените его на верхний левый. overflow: auto на контейнере позаботится о прокрутке самостоятельно, потому что div масштабируется (через преобразование) обернуто внутри другого div. Вам не нужен Javascript для этого.

Это решает ваши проблемы 1 и 2.

Fiddle 1: http://jsfiddle.net/abhitalks/c0okhznc/

Фрагмент 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>

Ответ 3

Жесткая часть этой проблемы заключается в том, что в середине процесса масштабирования вам нужно перейти от модели (где у вас все еще есть поля) к другой модели, где вам нужно расширить размер контейнера.

Так как это происходит в середине преобразования, с ним трудно справиться с единственным преобразованием свойства

То, что я нашел для решения этой проблемы, заключается в изменении свойств min-height и min-width. Это даст возможность автоматически обнаружить эту точку и обработать ее изящно

Я сохраняю в JS только базовую функциональность, все остальное сделано в CSS

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

Ответ 4

Мне удалось решить эту проблему с помощью логики. Я использовал GSAP для анимации и обновлений, но вы можете легко заставить его работать с vanilla JS:

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

... Из-за необходимости прокрутки родительского div, чтобы поддерживать элемент в центре (и никоим образом не анимировать прокрутку), вы не сможете получить плавный переход только с помощью CSS.

Примечание. Даже без прокрутки чистый переход CSS, похоже, имеет проблемы с синхронизацией (смещение, будь то верхнее/левое, поле или перевод, всегда догоняет масштаб). это может быть связано с подпиксельным позиционированием? Возможно, кто-то еще сможет дать дополнительную информацию.

Полный код от Codepen:

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

Ответ 5

Матфей Мэтью Кинга был близок к правильности. Единственное, что нужно исправить - это смещение div. Если ваш внутренний div превышает границы прокручиваемого div (отрицательное смещение), вам нужно установить смещение в 0. Еще вы хотите, чтобы div был центрирован.

Однако центрирование не является тривиальным. Вы масштабируете фон div css, следовательно, вы не влияете на фактическую ширину и высоту своего div, и вам нужно проанализировать матрицу масштабирования, чтобы вычислить ее размер фона.

Это решение работает для меня: https://jsfiddle.net/6e2g6vzt/11/
(по крайней мере для FF на Ubuntu, не тестировал в других браузерах)

В основном это только одна функция для установки нового смещения, вам даже не нужно вызывать его вручную из-за этой строки кода

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

который вызывает setNewOffset функцию после, трансформация завершена.
Как вы можете видеть, что изображение "перескакивает" на правильную позицию после преобразования, возможно, вы хотите добавить гладкий эффект для покрытия, но я просто хотел показать вам, как получить правильное смещение.

Посмотрите документацию по jQuery, чтобы узнать больше о . offset()


Credits:
Благодаря Джим Джефферсу за хороший обратный вызов после транзисторов
fooobar.com/info/35714/...
Благодаря Lea Verou для отличного регулярного выражения для анализа масштабной матрицы fooobar.com/info/124345/...