Issue with transform origin animation in Safari 12.1 - javascript

Simple animation that changes the transform origin of a div based on horizontal mouse position.
It is not displaying the animation in Safari 12.1. Instead the div is stuck displaying the transform origin it had on page load (resizing the browser seems to update it).
The script is spitting out the correct values onto the element and no errors are showing up so need some help getting it animating.
It is working in latest Chrome, Firefox and Safari 11, just the latest Safari that is broken.
$(document).mousemove(function(getCurrentPos){
var xCord = getCurrentPos.clientX;
var xPercent = xCord/window.innerWidth * 100 + "%";
document.getElementById("p-beam").style.transformOrigin = xPercent + "100%";
});
.beams {
display: block;
position: relative;
overflow: hidden;
height: 600px;
background-color: #434343;
}
.p-beam {
position: absolute;
left: 0;
top: -360px;
right: 0;
bottom: auto;
width: 100px;
height: 360px;
margin-right: auto;
margin-left: auto;
background-image: -webkit-gradient(linear, left top, left bottom, from(transparent), to(#eef));
background-image: linear-gradient(180deg, transparent, #eef);
-webkit-transform: perspective(360px) rotateX(-104deg) rotateY(0) rotateZ(0);
transform: perspective(360px) rotateX(-104deg) rotateY(0) rotateZ(0);
-webkit-transform-origin: 50% 100%;
-ms-transform-origin: 50% 100%;
transform-origin: 50% 100%;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
box-sizing: border-box;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="beams">
<div id="p-beam" class="p-beam"></div>
</div>

This looks like a bug.
I could get it working by changing the innerHTML/innerText of the bean, I think that way it forces layout/paint.
I'm concerned that the area you're paint is maybe too big, even if this is a bug, this could be too expensive and you're hitting the limits of the renderer. But this is just speculation.
I know it is not the same, but maybe you could accomplish something visually similar with just transform: rotate() or maybe transform: matrix()
const beam = document.getElementById("p-beam");
let i=0;
setInterval(()=>{
i=(i+1)%100;
const x = `${i%100}% ${100}%`
beam.style.transformOrigin = x;
beam.innerHTML = i;
//console.log(x)
}, 16);
.p-beam {
position: absolute;
left: 0;
top: -360px;
right: 0;
bottom: auto;
width: 100px;
height: 360px;
margin-right: auto;
margin-left: auto;
background: yellow;
transform: perspective(360px) rotateX(-104deg) rotateY(0) rotateZ(0);
transform-style: preserve-3d;
box-sizing: border-box;
}
<div class="beams">
<div id="p-beam" class="p-beam"></div>
</div>

Related

Perfectly Center lightbox dynamically no matter screen size

I have a pop-up div that is able to stream video or show a document/image depending on the file attachment.
The issue I'm having is ensuring that it is centered vertically and horizontally, both for Desktop and mobile browsers/app.
I've been trying different attributes, but it's like I change from what I already have and suddenly it's just off the page.
if(extension === "mp4"){
document.body.innerHTML += '<div id="light"><a class="boxclose" id="boxclose" onclick="lightbox_close();">x</a><video id="VideoLauncher" width="600" controls controlsList="nodownload"><source src="'+file+' " type="video/mp4"><!--Browser does not support <video> tag --></video></div><div id="fade" onClick="lightbox_close();"></div>'
}
if(extension === "jpg"){
document.body.innerHTML += '<div id="light"><a class="boxclose" id="boxclose" onclick="lightbox_close();">x</a><img id="VideoLauncher" width="600" src="'+file+'" onclick="lightbox_close()"></img></div><div id="fade" onClick="lightbox_close();"></div>'
}
if(extension === "pdf" || extension === "doc" || extension === "docx"){
document.body.innerHTML += '<div id="light"><a class="boxclose" id="boxclose" onclick="lightbox_close();">x</a><iframe src="https://docs.google.com/gview?url='+file+'&embedded=true" style="height:800px; width:600px;" frameborder="0"></iframe></div><div id="fade" onClick="lightbox_close();"></div>'
}
var LightEle = document.querySelector("#light");
var FadeEle = document.querySelector("#fade");
var BoxCloseEle = document.querySelector("#boxclose");
LightEle.style.cssText = 'display: none; position: absolute; top: 50%; left: 50%; max-width: 600px; max-height: 100%px; margin-left: -200px; margin-top: -180px; border: 2px solid #FFF; background: #FFF; z-index: 1002; overflow: visible;';
FadeEle.style.cssText = 'display: none; position: fixed; top: 0%; left: 0%; width: 100%; height: 100%; background-color: black; z-index: 1001; -moz-opacity: 0.8; opacity: .80; filter: alpha(opacity=80);';
Ideally, what I'm looking for is the styling that will dynamically center the light element, horizontally and vertically, though the light element will be a different size depending on the file it is to show. Because of this I can't simply hard code the height and width.
There are two ways to doing this:
.lightbox {
margin: 0 auto;
}
This only centers horizontally within the parent component. It is a very simple way if that's what you want to do.
If not, try this:
.lightbox {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
A popular approach to vertically and horizontally center elements is to use:
top: 50%;
left: 50%;
transform: translate( -50%, -50% );
You can use this with absolute or fixed positioning . top and left moves the top left corner to the center of the screen while translate( -50%, -50% ) moves the element back to the left and top 50% of the elements width/height, placing the center of the element at the center of the page.
.box {
position: fixed;
top: 50%;
left: 50%;
transform: translate( -50%, -50% );
/* For Demo */
width: 75vw;
height: 75vh;
background-color: #ccc;
}
<div class="box"></div>
Use Flexbox:
main {
background-color: teal;
}
.modal-container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: gray;
}
.modal {
display: block;
max-width: 300px;
width: 90%;
max-height: 300px;
height: 90%;
background-color: white;
}
<main>
<div class="modal-container">
<div class="modal">
<h2>yes</h2>
</div>
</div>
</main>

RotateY transition not working properly

When i hover once, transition is proper, but on second time, transition becomes wierd, as if the perspective: 800px starts working after transition has taken place.
Please also tell how can i set rotation about an edge except center.
I know about transform-origin but nothing such as transform-axis.
I want that when i hover over the , these images should open like a window.
var left=document.getElementById("left");
var right=document.getElementById("right");
function curtain() {
left.style.transform="rotateY(70deg)";
right.style.transform="rotateY(-70deg)";
}
function back() {
left.style.transform="rotateY(0deg)";
right.style.transform="rotateY(0deg)";
}
#animate{
width: 400px;
height: 300px;
margin: auto;
position: relative;
perspective: 800px;
}
img {
width: 100%;
}
#left {
position:absolute;
top: 0;
right: 50%;
padding: 0;
margin: 0;
width: 50%;
transition: transform 0.5s;
}
#right {
position: absolute;
top: 0;
right: 0%;
padding: 0;
margin: 0;
width: 50%;
transition: transform 0.5s;
}
<html>
<head>
<link href="style/style.css" rel="stylesheet">
</head>
<body>
<div id="animate" onmouseover="curtain()" onmouseout="back()">
<div id="left"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e6/Ariyunda.JPG/200px-Ariyunda.JPG"></div>
<div id="right"><img src="http://www.cs.cornell.edu/courses/cs3110/2009sp/hw/ps4/beach_original.png"></div>
</div>
<script src="script/script.js"></script>
</body>
</html>
There seems to be an issue with perspective and the onmouseout. back() (in onmouseout) and curtain() (in onmouseover) are called quite inconsistently. onmouseout is called whenever the mouse moves outside the element (#animate in this case) or its children (the images). The children are animated - they move - and the onmouseout is thereby called multiple times.
I wouldn't recommend onmouseover / onmouseout for this - instead I would use CSS :hover.
That aside, transform-origin defines the center of rotation.
#animate:hover #left {
transform: rotateY(70deg);
}
#animate:hover #right {
transform: rotateY(-70deg);
}
#animate {
width: 400px;
height: 300px;
margin: auto;
position: relative;
perspective: 800px;
}
img {
width: 100%;
}
#left {
position: absolute;
top: 0;
right: 50%;
padding: 0;
margin: 0;
width: 50%;
transition: transform 0.5s;
transform-origin: left;
}
#right {
position: absolute;
top: 0;
right: 0%;
padding: 0;
margin: 0;
width: 50%;
transition: transform 0.5s;
transform-origin: right;
}
<div id = 'animate'>
<div id = 'left'><img src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e6/Ariyunda.JPG/200px-Ariyunda.JPG'></div>
<div id = 'right'><img src = 'http://www.cs.cornell.edu/courses/cs3110/2009sp/hw/ps4/beach_original.png'></div>
</div>
I don't know the origin of the problem, but it works ok if you are using CSS hover instead of JS hover.
And the transform origin is the way to go, it does what your wanted transform-axis would do.
#animate{
width: 400px;
height: 300px;
margin: auto;
position: relative;
perspective: 800px;
}
img {
width: 100%;
}
#left {
position:absolute;
top: 0;
right: 50%;
padding: 0;
margin: 0;
width: 50%;
transition: transform 0.5s;
transform: rotateY(0deg);
transform-origin: left center;
}
#right {
position: absolute;
top: 0;
right: 0%;
padding: 0;
margin: 0;
width: 50%;
transition: transform 0.5s;
transform: rotateY(0deg);
transform-origin: right center;
}
#animate:hover #left {
transform: rotateY(70deg);
}
#animate:hover #right {
transform: rotateY(-70deg);
}
<div id="animate" onmouseover="curtain()" onmouseout="back()">
<div id="left"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e6/Ariyunda.JPG/200px-Ariyunda.JPG"></div>
<div id="right"><img src="http://www.cs.cornell.edu/courses/cs3110/2009sp/hw/ps4/beach_original.png"></div>
</div>

Margin-left doesn't work properly in Firefox

In my code margin-left: is working in two different ways in Firefox and all the other browsers.
On Firefox, the margin is only like 20% of the "real" margin. I tried #-moz-document url-prefix() { }, but it didn't solve the issue, it moved both the image that is shown and the "real position from where the cars start moving" to a even bigger margin.
Here's my code:
<section id="home" >
<div id="home1inner">
<div id="header">
*lots of content here*
</div>
<img id="cars" src="images/cars.png" />
</div>
</section>
#home {
position: relative;
width: calc(100% + 25px);
overflow-y: scroll;
background-image: url('images/movie_6.jpg');
background-repeat: no-repeat;
background-position: center;
height: 690px;
margin-top: 40px;
}
#home1inner {
height: 1550px;
overflow: hidden;
position: relative;
}
#cars {
position: absolute;
height: 690px;
bottom: -500px;
margin-left: -300px;
pointer-events: none;
}
Here's the website itself, where you can check the difference between Firefox and any other browser: http://denea.comeze.com/
Any ideas how to fix it?
In Firefox, your cars ID is defaulting to be centered on the page.
Simply add left: 0, like so:
#cars {
position: absolute;
height: 690px;
bottom: -500px;
margin-left: -300px;
pointer-events: none;
left: 0;
}
and it will start off at the position on the page you want.

Zoom-out Effect Issues

I'm trying to get a full page (with nav at the top, but I don't mind the background going underneath it) zoom-out effect. However, I want it to execute once all assets are loaded, as it is the first thing seen when the page is loaded. So I wouldn't want it being executed early otherwise it may not even be seen or just the end of it would be caught.
I have seen several examples but I've had problems with them:
Animating (with jQuery) the background-size property - this made the animation 'choppy' and I read somewhere it was probably because it was being run on the CPU rather than the GPU.
Demo: https://jsfiddle.net/njj43kz4/
HTML
<body>
<div id="front"></div>
</body>
CSS
html {
width: 100%;
height: 100%;
}
body {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px;
}
#front {
position: relative;
top: 0;
width: 100%;
height: 100%;
opacity: 1;
background: #222 url("http://melleum.com/data/uploads/1/262040-1920x1080-wallpaper.jpg") no-repeat center center;
background-size: 110%;
}
JavaScript
$('#front').animate({ backgroundSize: '100%' }, 1000);
Using a setTimeout as shown in this previous question's answer: Slight background zoom on DOM load? - this worked smoothly, however I cannot get it working when I change the width and height values to 100%. The image starts oversized before zooming out, but the oversized view is shown. I want the fixed 100%x100% view, and no extra scaling visible. I tried overflow: hidden but that isn't hiding the overflow. You can see this is happening as the scrollbars are appearing and ruining the effect.
Demo: http://jsfiddle.net/eHAuh/15/
HTML
<body>
<div id="front" class="scaled"></div>
</body>
CSS
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
}
#front {
position: relative;
top: 0;
width: 100%;
height: 100%;
opacity: 1;
background: #222 url("http://melleum.com/data/uploads/1/262040-1920x1080-wallpaper.jpg") no-repeat center center;
background-position: 50% 50%;
overflow: hidden;
}
.animatable {
-webkit-transition:all 750ms ease-out;
transition:all 750ms ease-out;
}
.scaled {
-webkit-transform:scale(1.2);
transform:scale(1.2);
}
JavaScript
$(document).ready(function () {
$('#front').attr('class', 'animatable');
setTimeout(function () {
$('#front').removeClass('animatable');
}, 1000)
});
Any help would be great, and I hope the layout of this question is ok. I couldn't work out how to indent paragraphs without turning them into code indents. Thanks for reading and have a nice day.
Edit 1: The way this will execute when loaded is because the jQuery/JavaScript is in the $(window).load.
Edit 2: There was an answer suggesting to use keyframes, however these do not support IE9 and this would be preferable.
You can do it with css #keyframes if you want using scale
I have used pseudo class for adding background
/** after page has loaded*/
$(window).bind('load', function() {
$('#front').addClass('active')
})
html {
width: 100%;
height: 100%;
}
body {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px;
}
#front {
position: relative;
top: 0;
width: 100%;
height: 100%;
opacity: 1;
overflow: hidden;
}
#front:after {
content: '';
width: 100%;
height: 100%;
position: absolute;
background: #222 url("http://melleum.com/data/uploads/1/262040-1920x1080-wallpaper.jpg") no-repeat center center;
background-size: cover;
transform: scale(2);
}
#front.active:after {
animation: animation 5s;
/* change the value 5s to what you want */
animation-fill-mode: forwards;
/* added so that it doesn't return to its original state which is scale(2)*/
}
#keyframes animation {
0% {
transform: scale(2);
}
100% {
transform: scale(1)
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="front"></div>
As per your requirements it looks like you require this
/** after page has loaded*/
$(window).bind('load', function() {
$('#front').animate({
width: '100%',
height: '100%'
}, 5000);
})
html {
width: 100%;
height: 100%;
}
body {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#front {
position: relative;
top: 0;
width: 200%;
height: 200%;
opacity: 1;
background: #222 url("http://melleum.com/data/uploads/1/262040-1920x1080-wallpaper.jpg") no-repeat center center;
background-size: cover;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="front"></div>
in css change
#front
position: fixed;
or for body add
overflow: hidden;

scaling a centered div in a scrollable container

I'm trying to do a scale transformation on a div that is centered on a scrollable container div.
The trick i'm using to reflect the new div size after transformation, is using a wrapper and setting the new width/height to it so the parent can show the scrollbars correctly.
.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);
}
if the transformation origin was 0,0 the div is centered without animation jumps but the scrollbars are not correct. if the origin was in the middle both the div location and scrollbars are missed up
I have tried two ways to do the centering, using flexbox (http://jsfiddle.net/r3jqyjLz/1/) and using negative margins
(http://jsfiddle.net/roLf5tph/1/).
Is there a better way to do this ?
Is this what you are after?
I used CSS transitions for the scaling animation.
#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;
}
Update #1
How about this one?
Using old school absolute centering (position absolute)
I see your point about flexbox. It seems that flexbox centering has some limitations when the centered element is larger than its container.
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
.
The hard part of this problem is that, in the middle of the zoom in process, you need to change from a model (where you have still margins) to another model where you need to expand the size of the container.
Since this happens in the middle of the transform, it's difficult to handle with a single property transform
The way that I have found to solve this is to change the min-height and min-width properties. This will give the posibility to auto detect this point and handle it gracefully
I am keeping in JS only the basic functionality, everything else is done in 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>
The only issue left would be to set the scroll position, just in case you want it to keep in the center
I've managed to solve it with a touch of logic. I've used GSAP for the animation and updates, but you could easily get it working with vanilla JS:
http://codepen.io/theprojectsomething/pen/JdZWLV
... Because of the need to scroll the parent div to keep the element centred (and no way to animate the scroll) you're not going to be able to get a smooth transition with CSS alone.
Note: Even without the scroll, a pure CSS transition seems to have trouble syncing (the offset, whether top/left, margin, or translate, is always catching up with the scale) ... this may be due to sub-pixel positioning? Someone else may be able to provide further insight here.
Full code from 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>
Matthew King's fiddle was close to being correct.The only thing that needs fixing is the div's offset. If your inner div is exceeding the boundaries of the scrollable div (negative offset) you need to set the offset to 0. Else you want the div to be centered. Centering is however not that trivial. You are zooming the div's background with css, hence you are not affecting the actual width and height of your div and need to parse the scale matrix to calculate it's backgrounds dimension.
This solution works for me: https://jsfiddle.net/6e2g6vzt/11/ (at least for FF on Ubuntu, did not test on other browsers yet)It is basically just one function to set the new offset, you don't even have to call it manually due to this line of code
$("#centered").bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", setNewOffset);
which calls the setNewOffset function after the transformation is completed.
As you can see the image 'jumps' to it's correct position after the transformation, maybe you want to add a smooth effect to cover that but i just wanted to show you how to get the correct offset.
Have a look at jQuery's documentation to learn more about .offset()
Credits:
Thanks to Jim Jeffers for the nice callback after transistions https://stackoverflow.com/a/9255507/3586288
Thanks to Lea Verou for the great regex to parse the scale matrix https://stackoverflow.com/a/5604199/3586288

Categories

Resources