I am trying to scale a div and make its children look like it stays in the same place while the parent scales. To achieve it I am scaling the parent, and the children inversely:
Children-scale Parent-scale
start 2 0.5 -> 2 * 0.5 = 1
middle 1.5 0.75 -> 1.5 * 0.75 = 1.125 [It should be 1]
end 1 1 -> 1
As can be seen in the demo, the initial and final values are correct but not the intermediates. At first I thought it could be the easing function, but assigning a linear (constant speed) makes no difference. Possibly assigning an inverse easing could do it, but I am asking if there is a non-mathematical way to achieve this (as I am not confident with bezier curves).
How could I achieve a smooth transition using the transform scale property?
var $div = $('div');
$div.on('click', function() {
$div.toggleClass('scaled');
});
div {
width: 100%;
height: 200px;
overflow:hidden;
}
img {
width: auto;
height: 100%;
}
div, img {
transition: transform 1s linear;/* Other easing function make no difference */
/* transform-origin:left top; Makes no difference */
}
div.scaled {
transform:scaleY(0.5);
}
div.scaled img {
transform:scaleY(2);/* 1 / 0.5 */
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span>Click the image</span>
<div><img src="http://i.imgur.com/sbyPaAsl.jpg" alt=""></div>
JSFiddle.
Edit:
Using CSS animations and assigning the correct values each x%, I get something close but still not perfect. (This also needs to know beforehand the scale value or otherwise create the animation and assign it with JavaScript.)
var $div = $('div');
$div.on('click', function() {
$div.toggleClass('scaled');
});
div {
width: 100%;
height: 200px;
overflow:hidden;
}
img {
width: auto;
height: 100%;
}
div.scaled {
animation: ddiv 1s linear;
}
div.scaled img {
animation: iimg 1s linear;
}
#keyframes iimg {
0% {transform:scaleY(2);}
20% {transform:scaleY(1.8);}
40% {transform:scaleY(1.6);}
60% {transform:scaleY(1.4);}
80% {transform:scaleY(1.2);}
100% {transform:scaleY(1);}
}
#keyframes ddiv {
0% {transform:scaleY(0.5);}
20% {transform:scaleY(0.555);}/* 1 / 1.8 */
40% {transform:scaleY(0.625);}/* 1 / 1.6 */
60% {transform:scaleY(0.714);}/* 1 / 1.4 */
80% {transform:scaleY(0.833);}/* 1 / 1.2 */
100% {transform:scaleY(1);}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span>Click the image</span>
<div><img src="http://i.imgur.com/sbyPaAsl.jpg" alt=""></div>
I am posting an answer to the question as I found a bezier curve by testing values which is close to the desired result; but I am still open to other answers if someone has a better approach.
The function is 1/x. The scale-up element needs a linear easing, and the scale-down one a cubic-bezier(.25,.48,.52,.75) (it is not the exact curve). This way the children element almost looks like it is not scaling.
JSFiddle
var $div = $('div');
$div.on('click', function() {
$div.toggleClass('scaled');
});
div {
width: 100%;
height: 200px;
overflow:hidden;
}
img {
width: auto;
height: 100%;
}
div {
transition: transform 1s linear;
}
img {
transition: transform 1s cubic-bezier(.25,.48,.52,.75);
}
div.scaled {
transition: transform 1s cubic-bezier(.25,.48,.52,.75);
transform:scaleY(0.5);
}
div.scaled img {
transition: transform 1s linear;
transform:scaleY(2);/* 1 / 0.5 */
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div><img src="http://i.imgur.com/sbyPaAsl.jpg" alt=""></div>
Would an approach using pseudoelements work for you instead?
Here's an idea:
var $div = $('div');
$div.on('click', function() {
$div.toggleClass('scaled');
});
div {
width: 100%;
height: 200px;
overflow: hidden;
position: relative;
}
img {
width: auto;
height: 100%;
}
div:after,
div:before {
content: '';
position: absolute;
height: 0;
width: 100%;
display: block;
background: white;
}
div:before {
top: 0;
}
div:after {
bottom: 0;
}
div.scaled:after,
div.scaled:before {
transition: height 1s linear;
}
div.scaled:after,
div.scaled:before {
height: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span>Click the image</span>
<div><img src="http://i.imgur.com/sbyPaAsl.jpg" alt=""></div>
I dont know if it's useful for your situation or not:
But if you don't need the overflow-hidden in the container, making it and the child rotate could give you a posibility:
var $div = $('div');
$div.on('click', function() {
$div.toggleClass('scaled');
});
div {
width: 100%;
height: 200px;
border: solid 1px green;
}
img {
width: auto;
height: 50%;
top: 25%;
}
div, img {
transition: transform 1s linear;
transform-style: preserve-3d;
position: relative;
}
div.scaled {
transform: rotateX(60deg);
}
div.scaled img {
transform: rotateX(-60deg);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span>Click the image</span>
<div><img src="http://i.imgur.com/sbyPaAsl.jpg" alt=""></div>
Related
<!--BG Photo-->
<div class="pic1"><img src="1.svg"></div>
<!--SVG that will rotate-->
<div class="pic2"><img src="img/vec/gz4.svg" alt=""></div>
.pic1 img{/*Bg Photo*/
width: 100%;
height: auto;
top: 0;
left: 0;
}
.pic2{
position: absolute;
transform: translate(45px,-75px);
}
.pic2 img{
transform-origin:center;
width: 50px;
height: 50px;
animation: rotation 2s infinite linear;
}
#keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
The problem is what when i zoon out/in or resize the brower .pic2
is moving not stay fixed on his original point
Your idea is right yet both SVG images will adjust to the proportion of the available space (do you have only viewBox defined in them, removing the height and width attributes?). So the second image (pic2) will always "bounce" when resizing (but how many web users really do that?).
Maybe define styles for both SVGs in their DIV parent (or "container" element if you wish) by using vw and vh units - instead of pixels, possibly percentages as well - and this will at least give you more predictable result:
<style>
body {
border: 0;
margin: 0;
}
.pic1 {
position: absolute;
width: 100%;
border: 0;
margin: 0;
}
.pic1 img{/*Bg Photo*/
width: 100%;
height: auto;
top: 0;
left: 0;
}
.pic2{
position: absolute;
z-index: -1;
transform: translate(2.5vw,88vh);
}
.pic2 img{
transform-origin:center;
width: 50px;
height: 50px;
animation: rotation 2s infinite linear;
}
#keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
</style>
<body>
<!--BG Photo-->
<div class="pic1"><img src="1.svg"></div>
<!--SVG that will rotate-->
<div class="pic2"><img src="img/vec/gz4.svg" alt=""></div>
</body>
<!--BG Photo-->
<div class="pic1"><img src="1.svg"></div>
<!--SVG that will rotate-->
<div class="pic2"><img src="img/vec/gz4.svg" alt=""></div>
.pic1 img{/*Bg Photo*/
width: 100%;
height: auto;
top: 0;
left: 0;
}
.pic2{
position: absolute;
transform: translate(45px,-75px);
}
.pic2 img{
transform-origin:center;
width: 50px;
height: 50px;
animation: rotation 2s infinite linear;
}
#keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
The problem is what when i zoon out/in or resize the brower .pic2
is moving not stay fixed on his original point
Your idea is right yet both SVG images will adjust to the proportion of the available space (do you have only viewBox defined in them, removing the height and width attributes?). So the second image (pic2) will always "bounce" when resizing (but how many web users really do that?).
Maybe define styles for both SVGs in their DIV parent (or "container" element if you wish) by using vw and vh units - instead of pixels, possibly percentages as well - and this will at least give you more predictable result:
<style>
body {
border: 0;
margin: 0;
}
.pic1 {
position: absolute;
width: 100%;
border: 0;
margin: 0;
}
.pic1 img{/*Bg Photo*/
width: 100%;
height: auto;
top: 0;
left: 0;
}
.pic2{
position: absolute;
z-index: -1;
transform: translate(2.5vw,88vh);
}
.pic2 img{
transform-origin:center;
width: 50px;
height: 50px;
animation: rotation 2s infinite linear;
}
#keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
</style>
<body>
<!--BG Photo-->
<div class="pic1"><img src="1.svg"></div>
<!--SVG that will rotate-->
<div class="pic2"><img src="img/vec/gz4.svg" alt=""></div>
</body>
I'm sure this must have been asked before and I've found related questions but I can't quite seem to crack this.
I have an element which receives a class and, on doing so, expands. Later, when that class is removed, it should revert (animate) back to its original width.
let el = document.querySelector('#side-bar');
el.addEventListener('click', evt => el.classList.toggle('contracted'));
#side-bar {
height: 100%;
width: 75px;
background: red;
position: fixed;
left: 0;
top: 0;
}
#side-bar.contracted {
animation: .5s side-bar-contract forwards;
}
#side-bar:not(.contracted) {
animation: .5s side-bar-expand forwards;
}
#keyframes side-bar-expand {
to {
width: 350px;
}
}
#keyframes side-bar-contract {
to {
width: 75px;
}
}
<div id='side-bar' class='contracted'></div>
The expansion animation works fine. But the reversion animation doesn't happen; it just snaps back to its original properties, no anim.
Fiddle
What am I doing wrong?
[ EDIT ]
OK I should obviously have mentioned why I'm not doing this with transition. This is part of a wider set of dependent animations which run in a sequence, one after another. My understanding is that this sort of chronologically non-trivial situation is better for animation rather than transition.
UPDATE: (Removing the animation at the beginning)
let init = 0,
el = document.querySelector('#side-bar');
el.addEventListener('click', function() {
if (init < 1) {
init++;
el.classList.remove("init");
el.classList.add('contracted');
}
el.classList.toggle('contracted');
});
#side-bar {
height: 100%;
width: 75px;
background: #d4653c;
position: fixed;
left: 0;
top: 0;
padding: .8rem;
}
#side-bar:not(.init) {
animation: .5s side-bar-expand forwards;
}
#side-bar.contracted {
animation: .5s side-bar-contract forwards;
}
#keyframes side-bar-expand {
to {
width: 350px;
}
}
#keyframes side-bar-contract {
from {
width: 350px;
}
}
<div id='side-bar' class='init'>Click me</div>
Just change to to from in side-bar-contract
#keyframes side-bar-expand { to { width: 350px; } }
#keyframes side-bar-contract { from { width: 350px; } }
let el = document.querySelector('#side-bar');
el.addEventListener('click', evt => el.classList.toggle('contracted'));
#side-bar {
height: 100%;
width: 75px;
background: #d4653c;
position: fixed;
left: 0;
top: 0;
padding: .8rem;
}
#side-bar:not(.contracted) {
animation: .5s side-bar-expand forwards;
}
#side-bar.contracted {
animation: .5s side-bar-contract forwards;
}
#keyframes side-bar-expand {
to {
width: 350px;
}
}
#keyframes side-bar-contract {
from {
width: 350px;
}
}
<div id='side-bar' class='contracted'>Click me</div>
Why not just use a transition animation:
let el = document.querySelector('#side-bar');
el.addEventListener('click', evt => el.classList.toggle('contracted'));
#side-bar {
height: 100%;
width: 350px; /* have width at 350px when not contracted */
background: #d4653c;
position: fixed;
left: 0;
top: 0;
padding: .8rem;
transition: width .5s; /* animate the width */
}
#side-bar.contracted {
width: 75px;
}
<div id='side-bar' class='contracted'>Click me</div>
If you need to use keyframes then you need to start the second one off at 350px - you start it at 75 to 75 which is why it doesn't animate:
let el = document.querySelector('#side-bar');
el.addEventListener('click', evt => el.classList.toggle('contracted'));
#side-bar {
height: 100%;
width: 75px;
background: #d4653c;
position: fixed;
left: 0;
top: 0;
padding: .8rem;
}
#side-bar:not(.contracted) {
animation: .5s side-bar-expand forwards;
}
#side-bar.contracted {
animation: .5s side-bar-contract forwards;
}
#keyframes side-bar-expand {
to {
width: 350px;
}
}
#keyframes side-bar-contract {
0% {
width: 350px;
}
100% {
width: 75px;
}
}
<div id='side-bar' class='contracted'>Click me</div>
First, I would recommend you do this with hover styles and css transition instead of an animation for something as simple as animating a single property.
.class {
width: 400px;
transition: width 1500ms ease-in-out;
}
.class:hover {
width: 100px;
}
CSS transition will actually stop part way through the transition and reverse to the initial size for you.
Second, I would recommend that you do not animate or transition the width property in CSS. Here's a great article about what properties you should avoid animating.
If you need to delay a transition from happening on other elements, you can use the transition-delay property. This property can also be applied in hover effects... including with hover effects on parent elements. So you may potentially have multiple hover effects in play at a given time to accomplish your desired effect.
I have a page in my website where I display to panels, side by side. I'm displaying these 2 panels in 2 views: Horizontal and Vertical. I have a button that switches between these 2 views. I'm trying to add some CSS animation on the transition between the two views. However my animation work only in one direction (from Vertical to Horizontal), the reverse animation appear in the wrong order.
var isVertical = false;
var boxes = $(".box");
function toggleViews()
{
isVertical = !isVertical;
if (isVertical)
{
boxes.addClass("vertical-box");
}
else
{
boxes.removeClass("vertical-box");
}
}
.container
{
display: block;
width: 400px;
height: 150px;
border: 2px solid black;
overflow: hidden;
}
.box
{
-webkit-transition-property: width, height;
-webkit-transition-duration: 2s, 2s;
-webkit-transition-delay: 0s, 2s;
-webkit-transition-timing-function: ease;
display: inline-block;
width: 50%;
height: 100%;
}
.vertical-box
{
width: 100%;
height: 50%;
}
.a { background-color: darkred; }
.b { background-color: darkorchid; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<body>
<button onclick="toggleViews()">toggle</button>
<div class="container">
<div class="a box">A</div><div class="b box">B</div>
</div>
</body>
</html>
var isVertical = false;
var boxes = $(".box");
function toggleViews()
{
isVertical = !isVertical;
if (isVertical)
{
boxes.addClass("vertical-box");
}
else
{
boxes.removeClass("vertical-box");
}
}
.container
{
display: block;
width: 400px;
height: 150px;
border: 2px solid black;
overflow: hidden;
}
.box
{
-webkit-transition-property: height, width; /* swapped */
-webkit-transition-duration: 0.5s, 0.5s;
-webkit-transition-delay: 0s, 0.5s;
-webkit-transition-timing-function: ease;
display: block; /* TRY THIS */
float: left; /* AND THIS */
width: 50%;
height: 100%;
}
.vertical-box
{
-webkit-transition-property: width, height; /* added */
width: 100%;
height: 50%;
}
.a { background-color: darkred; }
.b { background-color: darkorchid; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<body>
<button onclick="toggleViews()">toggle</button>
<div class="container">
<div class="a box">A</div><div class="b box">B</div>
</div>
</body>
</html>
Explained
Added transition-property: width, height; to .vertical-box
Desired behavior: expand width, shink height; expand height shrink width.
.box has transition-property first height then width
.vertical-box overwrites and flippes transition-property: first width, then height
You might think this is the wrong order, but as soon as you click the class is applied immideately, but the transition takes time. So you transition from .box to .vertical-box with the transition-property of .vertical-box and vise versa.
EDIT Answer using animation (little hacky, since i found no way to reset current keyframe)
var isVertical = false;
var boxes = $(".box");
function toggleViews()
{
isVertical = !isVertical;
if (isVertical)
{
boxes.removeClass("vertical-box-reverse");
window.requestAnimationFrame(function() { // apply to forget animation state
window.requestAnimationFrame(function() { // re-apply animation
boxes.addClass("vertical-box");
});
});
}
else
{
boxes.removeClass("vertical-box");
window.requestAnimationFrame(function() { // apply to forget animation state
boxes.addClass("vertical-box-before-reverse"); // apply to set animation end-like state
window.requestAnimationFrame(function() { // re-apply animation
boxes.removeClass("vertical-box-before-reverse");
boxes.addClass("vertical-box-reverse");
});
});
}
}
.container
{
display: block;
width: 400px;
height: 150px;
border: 2px solid black;
overflow: hidden;
}
.box
{
display: block;
float: left;
width: 50%;
height: 100%;
}
.a.vertical-box { animation: boxAnimationA 1s normal forwards; }
.b.vertical-box { animation: boxAnimationB 1s normal forwards; }
.a.vertical-box-reverse { animation: boxAnimationA 1s ease-in reverse forwards; }
.b.vertical-box-reverse { animation: boxAnimationB 1s ease-in reverse forwards; }
.vertical-box-before-reverse { width: 100%; height: 50%; }
.a { background-color: darkred; }
.b { background-color: darkorchid; }
/* Keyframes */
#keyframes boxAnimationA {
0% { width: 50%; }
50% { width: 100%; height: 100%; }
100% { width: 100%; height: 50%; }
}
#keyframes boxAnimationB {
0% { width: 50%; }
50% { width: 0%; height: 100%; }
51% { width: 100%; height: 100%; }
100% { width: 100%; height: 50%; }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<body>
<button onclick="toggleViews()">toggle</button>
<div class="container">
<div class="a box">A</div><div class="b box">B</div>
</div>
</body>
</html>
I want to add 2 transition transforms
But I want to start the second transform after the end of the first transform
the element should go to a point slowly and after that it should go to another point
transform: translate(0%, 300%), translate(15%, -136%);
You cannot do this with just a single element using transition because when you put more than one translate within the transform, the transform property on the whole is transitioned and not one by one.
With pure CSS transition using an extra wrapper element:
If you add an extra wrapper element around the actual element and put one of the transforms on the wrapper element you could achieve the effect that you are looking for. It would also produce the exact reverse effect on the hover out (hover the body and hover out in the below snippet).
.wrapper {
position: relative;
height: 100px;
width: 100px;
transition: all 1s 1s;
}
.content {
position: absolute;
height: 100%;
width: 100%;
border: 1px solid;
transition: all 1s;
}
body:hover .content {
transform: translate(15%, -136%);
transition: all 1s 1s;
}
body:hover > .wrapper {
transform: translate(0%, 300%);
transition: all 1s;
}
body {
min-height: 100vh;
}
<div class='wrapper'>
<div class='content'>Some text</div>
</div>
Transition with a bit of JS/jQuery without any extra elements:
If you add an extra wrapper element around the actual element and put one of the transforms on the wrapper element you could achieve the effect that you are looking for. It would also produce the exact reverse effect on the hover out (hover the body and hover out in the below snippet).
$(document).ready(function() {
var isHover; /* variable to track state */
$('body').hover(function() {
isHover = !isHover; /* invert the state */
$('.content').css('transform', 'translate(0%, 300%)');
}, function() {
isHover = !isHover; /* invert the state */
$('.content').css('transform', 'translate(0%, 300%)');
});
$('.content').on('transitionend', function() {
if (isHover) {
$('.content').css('transform', 'translate(0%, 300%) translate(15%, -136%)');
} else {
$('.content').css('transform', 'none');
}
});
});
.content {
height: 100px;
width: 100px;
border: 1px solid;
transition: all 1s;
}
body {
min-height: 100vh;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='content'>Some text</div>
With animation and no extra element:
Using animations this can be done using a single element but the reverse effect is tough to achieve. We would have to write extra code for this and even then it will be complex.
.content {
position: relative;
height: 100px;
width: 100px;
border: 1px solid;
}
body:hover > .content {
animation: move 1s forwards;
}
#keyframes move {
0% {
transform: none;
}
50% {
transform: translate(0%, 300%);
}
100% {
transform: translate(0%, 300%) translate(15%, -136%);
}
}
body {
min-height: 100vh;
}
<div class='content'>Some text</div>
Animations with reverse effect:
Below is a snippet which produces the reverse effect also using CSS animations. But as you can see it is a bit complex. We can do this using a single animation also but it would become more complex.
$(document).ready(function() {
$('body').hover(function() {
$('.content').css('transform', 'none');
$('.content').removeClass('hover-out').addClass('hover-in');
}, function() {
$('.content').css('transform', 'translate(0%, 300%) translate(15%, -136%)'); /* as soon as an animation is removed, the element would snap back to original state, to avoid that we have to add final state via inline style */
$('.content').removeClass('hover-in').addClass('hover-out');
});
});
.content {
height: 100px;
width: 100px;
border: 1px solid;
}
.hover-in {
animation: hover-in 1s forwards;
}
.hover-out {
animation: hover-out 1s forwards;
}
#keyframes hover-in {
0% {
transform: none;
}
50% {
transform: translate(0%, 300%);
}
100% {
transform: translate(0%, 300%) translate(15%, -136%);
}
}
#keyframes hover-out {
0% {
transform: translate(0%, 300%) translate(15%, -136%);
}
50% {
transform: translate(0%, 300%);
}
100% {
transform: none;
}
}
body {
min-height: 100vh;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='content'>Some text</div>