Масштабирование центрированного 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/).
Есть ли лучший способ сделать это?
Ответы
Ответ 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/...