I am assuming that parts of the problem you are having can be summarized as:
- You have a complex structure in your inner
div which you want to scale as a group. (If it were a single element, this would've been easy with a matrix).
- When the inner
div is scaled beyond the bounds of the container, you don't get scrollbars without controlling the width/height.
- You want the inner
div to remain centered and at the same time, scrollbars should reflect the correct position.
With this, there are two (three actually) easy options.
Option 1:
Using the same markup in the question, you could keep the div centered when the scale factor is below 1. And for scale factors above 1, you change it to top-left. the overflow: auto on the container will take care of the scroll on its own because the div being scaled (via transform) is wrapped inside of another div. You don't really need Javascript for this.
This solves your problems 1 and 2.
Fiddle 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>
But this creates another problem. The overflow:auto will cause a jump/flicker when the div is scaled beyond the container. This can be easily solved by making it overflow:scroll to show the scrollbars at all times (the way you are doing it already). Although, it works like a charm in Firefox, Chrome falters here and doesn't update the scrollbar position. The trick here is to use Javascript to force a reflow by changing the overflow to auto once your scaling completes. So you need to delay it a bit.
Fiddle 2: http://jsfiddle.net/abhitalks/u7sfef0b/
Snippet 2:
$("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 }); }
* { 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) ; }
<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>
Option 2:
In order to solve problem 3 of keeping the div centered and at the same time maintaining the correct scrollbar position, you have to fallback on Javascript.
The principle remains the same that for scale factors above 1 you need to reset the top-left and translate positions. You would also need to recalc the scaled width/height and then re-assign that to your wrapper div. Then setting the scrollTop and scrollLeft on the container will be as easy as just getting the difference of the wrapper div and the container div.
This solves your problems 1, 2, and 3.
Fiddle 3: http://jsfiddle.net/abhitalks/rheo6o7p/1/
Snippet 3:
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);
}
* { 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; }
<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>
All scripts tested with IE-11, GC-43, and FF-38
.