Here is my code to apply a scale effect (like a zoom) on a figure element while hovering a parent.
CSS (SCSS)
.card-link {
display: block;
overflow: hidden;
}
figure.card-figure {
will-change: transform;
transform-origin: center;
transform: scale3d(1, 1, 1) translateZ(0) rotate(0.001deg);
transition: transform var(--transition-easing) var(--transition-duration);
transition-delay: 0.1s;
perspective: 1000px; // Helps the jump in chrome
backface-visibility: hidden; // Helps the jump in chrome
// Hover
.card-link:hover & {
transform: scale3d(1.1, 1.1, 1) translateZ(0) rotate(0.001deg);
}
}
HTML
<a href="#" class="card-link">
<figure class="card-figure">
<img src="...">
</figure>
</a>
It obviously works, but at the end of the transition it makes a little jump, like the figure goes some pixels outside the parent (that it has overflow hidden).
This seems to only happens on Chrome (v 89.0.4389.114) and only at certain viewports (window sizes).
I tried all (I mean all) the solutions I found (you'll recognize them in the CSS above) but none of them worked. Please, anyone could give some ideas? Thanks
Related
In my project I have been using jQuery slideUp() to slide up an element in a 200 item list when the user clicks a button. But, as everyone knows, animating CSS height requires a reflow, making the animation jerky. This animation is an integral part of the application and I am willing to go to extensive work to make it work AND work smoothly.
I have decided that CSS transform is the way to make it work smoothly because of the fact that it is handled on the gpu and on some modern browsers, it is even off the main thread and heavy JS work won't affect the transform. (I do have heavy JS work).
I am looking for a clever solution with CSS transition: transform to replicate jQuery slideUp, which just animates the height property. Below was my attempt, but it seems scale and translate do not sync as expected.
$("button").on("click", function() {
$(".collapsible").addClass("collapsed")
setTimeout(function() {
$(".collapsible").removeClass("collapsed")
}, 5000);
});
.list-item {
width: 400px;
height: 100px;
background-color: blue;
margin-top: 10px;
overflow-y: hidden;
}
.collapsible.collapsed .content {
transition: transform 3s linear;
transform: scale(1, 1) translate(0, -100%);
}
.collapsible.collapsed {
transition: transform 3s linear;
transform: translate(0, -50%) scale(1, 0);
}
.collapsible.collapsed ~ .list-item {
transition: transform 3s linear;
transform: translate(0, -100%);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<button>Collapse</button>
<div class="list-item collapsible">
<div class="content">
Just an example <br>
Just an example <br>
Just an example <br>
Just an example <br>
Just an example <br>
</div>
</div>
<div class="list-item">
</div>
<div class="list-item">
</div>
I played with the values some and got it closer by changing the content transform to transform: scale(1, 3) translate(0, -50%);.
It seems I am so close to achieving, but never quite succeeding. Is there any cut and dried trick out there for this?
Requirements:
Preferably no JS
Off the main thread
I have hard time making scaling animation that is not choppy in Safari (mobile). By choppy I mean you can clearly see that it's not 60FPS fluid animation. I need to say that element is absolutely positioned so it should not affect layout, should it?
First thing that I tried is animating element by creating CSS animation with 2 keyframes, going from transform: scale(0); to transform: scale(1);. For the effect I want to achieve I also used transform-origin: top right;. I also tried to optimize it by setting will-change: transform; but it was choppy.
I scrapped it and reworked it so it uses transition: transform. So by default element has transform: scale(0);, when certain class attaches to it, it gets transform: scale(1); which transition is animating, but it is still choppy.
After some research I found out that you want to avoid animating properties that require browsers to recalculate layout. I found out this site which says that WebKit (which Safari is using as far as I know) pretty much recalculates layout for every property change. Is that true, and if it is how do you make 60FPS fluid animations on mobile Safari (on other platforms using Safari it's not that noticeable because they have much more resources to recalculate everything and it appears a bit smoother than on mobile)?
There are 2 things that you can try:
avoid using scale(0). In some browsers this gives problems. scale(0.01) is almost the same, and will be better for the browser.
Try to make the animation handled by the GPU instead of the CPU. this can be done with the following code
from: {transform: scale(0.01) translateZ(1px);}
to: {transform: scale(1) translateZ(1px);}
In reference to this article section Animate Changes in CSS Properties, you can animate with keyframes, but need use a percentage like this:
More examples in w3s article.
PD: if you add an example, i'll try to help you.
function animateStart(){
document.getElementById('ball').classList.add('bounce');
}
function animationPause(){
document.getElementById('ball').style.webkitAnimationPlayState='paused';
}
function animationContinue(){
document.getElementById('ball').style.webkitAnimationPlayState='running';
}
#-webkit-keyframes bounce {
0% {top: 100px; left: 1px; -webkit-animate-timing-function: ease-in;}
25% {top: 150px; left: 76px; -webkit-animate-timing-function: ease-out;}
50% {top: 100px; left: 151px -webkit-animate-timing-function: ease-in;}
75% {top: 150px; left: 226px -webkit-animate-timing-function: ease-out;}
100% {top:100px; left: 301px;}
}
.bounce {
-webkit-animation-name: bounce;
-webkit-animation-duration: 2s;
-webkit-animation-timing-function: linear;
-webkit-animation-iteration-count: infinite;
-webkit-animation-direction: alternate;
}
.ball-style {
position:absolute; top: 100px; left: 10px;
height:100px; width:100px; border-radius:50px;
background:-webkit-radial-gradient(30% 30%, white, red 10%, black);;
}
.wall {
position:absolute; left: 400px; top: 100px;
height: 150px;
background: black;
}
<input type="button" value="Animate alternative"
onclick="document.getElementById('ball').classList.add('bounce');">
<input type="button" value="Animate" onclick="animateStart()">
<input type="button" value="Pause" onclick="animationPause()">
<input type="button" value="Continue" onclick="animationContinue()">
<div id="ball" class="ball-style"></div>
<div class="wall"> </div>
I don't know if this helps but are you using javascript to add a class and start the animation with the class change?
jQuery for example can cause long depth analysis in the dom when manipulating elements, this is likely to interfere with the performance you need for achieving the desired fps.
I'm currently trying to implement some CSS transition effects on a set of different panels, to give the appearance that they are "sliding in" from off screen. I want them each to appear in a different direction (e.g. top to bottom, left to right, etc.)
Presumably, to do bottom to top and right to left, I would just want to set translateX or translateY, but with a positive value, rather than negative. And then for all of them, I would just translate the value to 0 when I want them to appear on screen.
Here is a really simplified version of what I am trying to do:
HTML:
<div class="container">
<div id="about" class="panel">
<h2>About</h2>
<p>I'm Mike and I don't know!</p>
Close
</div>
<div id="projects" class="panel">
<h2>Projects</h2>
<p>Here are some projects I have worked on.</p>
Close
</div>
<div id="contact" class="panel">
<h2>Contact</h2>
<p>You can find me all over the Internet!</p>
Close
</div>
<div id="blog" class="panel">
<h2>Blog</h2>
<p>Here are some blog posts.</p>
Close
</div>
projects
blog
about
contact
</div>
CSS:
.container {
overflow:hidden;
position:relative;
width:100vw;
height:100vh;
}
.panel{
min-width: 100%;
height: 100%;
position: absolute;
box-shadow: 0px 4px 7px rgba(0,0,0,0.6);
z-index: 2;
-webkit-transition: transform .8s ease-in-out;
-moz-transition: transform .8s ease-in-out;
-o-transition: transform .8s ease-in-out;
transition: transform .8s ease-in-out;
}
.panel:target {
-webkit-transition: transform .8s ease-in-out;
-moz-transition: transform .8s ease-in-out;
-o-transition: transform .8s ease-in-out;
transition: transform .8s ease-in-out;
}
.panel#contact {
transform: translateX(110%);
background-color:whitesmoke;
}
.panel#about {
transform: translateX(-110%);
background-color: red;
}
.panel#projects {
transform: translateY(110%);
background-color: blue;
}
.panel#blog {
transform: translateY(-110%);
background-color: gold;
}
.panel#contact:target{
transform: translateX(0%);
}
.panel#about:target{
transform: translateX(0%);
}
.panel#projects:target{
transform: translateY(0%);
}
.panel#blog:target{
transform: translateY(0%);
}
Note that the "About" and "Blog" links are transitioning as expected, whereas the "Projects" and "Contact" links appear to cause weirdness. Those are the two with positive values.
I'm having a really difficult time:
a.) understanding what the problem is exactly
b.) how can I fix it (if at all)
If someone could both explain what the browser is doing right now with those erroneous transitions and provide a solution, I would be much obliged. AFAIK, this is happening in every browser (that supports the :target pseudo element, anyway).
Let me know if you need clarification. Thanks a lot!
UPDATE: Not fixed, but I noticed that if you set the values to <98% for the offending element panels (Projects + Contact), the page transitions properly, though it isn't hidden from the screen. Not sure what that means, but if it helps...
UPDATE 2.0: Thanks for the comments, folks! I tried adding a container, and have updated the HTML and CSS to reflect that. The changes in question are a container div that wraps the panels, as well as the following CSS for that container:
.container {
overflow:hidden;
position:relative;
width:100vw;
height:100vh;
}
I'm still having similar behavior -- this is also updated in the Codepen. I think I understand now the issue, but I'm not quite sure why the page is still jumping to that element in question before applying the transformation. I can imagine there is some hacky way to get around this, but I would rather do it the "right" way.
I decided that using :target and trying to have this be pure CSS wasn't really the way to go, and wound up just using Javascript in a fairly straightforward manner.
Lesson learned: Don't try to do things w/ Pure CSS unless you really have to.
I'm wondering, if CSS position animations with "transition" and "transform" really don't affect the position of objects around the animated object, or if I'm missing something. I'm trying to get it to affect the sibling.
-webkit-transition: all 0.5s ease;
-webkit-transform: translateY(10em);
http://jsfiddle.net/W2L7B/6/
Thanks for your help!
For translate3d you need to have perspective I believe. Try this:
.slide {
-webkit-perspective: 1000;
-webkit-transform: translate3d(0,10em,0);
}
If you want both of them to move, just add the transition to #second as well, then add this css:
.slide, .slide + div { /* or use ~ if you want it to affect all next siblings */
-webkit-transform: translateY(10em);
-webkit-perspective: 1000;
}
FIDDLE
I am trying to implement a CSS-based animation involving a single Unicode character:
HTML
<div class="spinner">⊗</div>
CSS
.spinner {
display: inline-block;
font-size: 42pt;
margin: 50px;
animation: spin 0.5s infinite linear;
}
#keyframes spin {
to { transform: rotate(360deg); }
}
* I've omitted the vendor-specific prefixes in this example.
However, when I view the page in my browser (Firefox 20.0), the character is slightly off-center, resulting in a "wobbly" animation.
You can see it live here: http://jsfiddle.net/bBaVN/77/
How can I completely center the character?
Proof of Concept using SVG
Consider the following:
<div class="wrap">
<span class="spinner">
<svg {...many other attributes...} class="logo">
...path code...
</svg>
</span>
</wrap>
See the fiddle: http://jsfiddle.net/audetwebdesign/3G3U7/
I found a SVG version of the symbol at:
http://www.fileformat.info/info/unicode/char/2297/index.htm
I had to take the SVG, open it in Adobe Illustrator and then reset the view port (bounding box?) using object->artboards->fit to artwork bounds.
I then saved as SVG and then cut-paste the <svg> block into the demo, and finally, I added the class="logo" attribute to the <svg> tag.
For styling, I used the following CSS:
.spinner {
display: block;
width: 50px;
height: 50px;
position: relative;
top: 75px;
left: 75px;
animation: spin 1s infinite linear;
}
.logo {
display: block;
width: 50px;
height: 50px;
}
I set the display type to block for both .logo and .spinner, and both have the same height and width (use a square box for best results.
Seems to work. The hardest part was learning how to set up the SVG inline image. I found the following reference useful: http://css-tricks.com/using-svg/
You could set the line-height to 45px on .spinner, this will ensure that the containing span element is as high as it is wide. Here's a jsFiddle. Now there is a little less movement, but it still doesn't look like it is not moving around at all.
Another way to get it to rotate around the center of character instead of around the center of the containing span would be to play around with -vendorspecificprefix-transform-origin. You could set it to rotate around another point e.g. setting it to: 23px 34px would set the x and y coordinates for the point to rotate around.
I think the fact that it still looks like it moves around a little bit might be due to the character not being rendered as a perfect circle, you could try rendering it in a different font, changing point sizes of the font, or even turning text-rendering: optimizelegibility; on or off might make a difference there.
The default value for -vendorspecificprefix-transform-origin is 50% 50%, this suggests that if you make sure that the character inside the element is perfectly centered, and you set the animation on the containing element, it should rotate exactly around the the center, and then playing with -vendorspecificprefix-transform-origin would only make things worse.
Another thought I'm having, by setting the point size of the text to 42pt, the width of the containing span becomes 45px, now 50% of that would be 22.5px, maybe it would work if you made the point size of the text a bit bigger, or just set the width and the height of the containing span to 46px, then 50% would be 23px, which might make the difference with the current movement.
Update:
I was able to get it to be centered perfectly in Chrome (and in FireFox) by using a mono-space font Courier, manually setting the line-height, height and width of the span to center the character, and then forcing the character to look more like a circle by moving it over by 0.5px using -webkit-transform: translate().
.spinner {
display: inline-block;
font-size: 42pt;
line-height: 50px;
height: 46px;
width: 46px;
margin: 50px;
-webkit-animation: spin 1s infinite linear;
-moz-animation: spin 1s infinite linear;
-ms-animation: spin 1s infinite linear;
-o-animation: spin 1s infinite linear;
animation: spin 1s infinite linear;
font-family: courier;
}
#-webkit-keyframes spin {
from { -webkit-transform: rotate(0deg) translate(0.5px, 0px)}
to { -webkit-transform: rotate(360deg) translate(0.5px, 0px)}
}
I think the fact that I need 2 jsFiddle's to demonstrate for different browsers kind of answers the question about if you should be doing it this way, I think the differences in font-rendering between browsers will ensure that you can't do this reliably without browser detection.