With the following CSS, I am preparing my segment message to slide across the viewport:
.Segment {
position: absolute;
width: 100%;
overflow: hidden;
top: -5px;
top: 0;
outline: 1px solid orange;
}
.Segment__message {
display: inline-block;
margin-top: 15px;
left: 100%;
transform: translateX(0);
position: relative;
padding-left: 10px;
will-change: transform;
font-size: 30px;
}
If I apply the following styles dynamically, I am getting some very slight jank:
var message = document.querySelector(".Segment__message");
message.style = "transition: all 20s linear; transform: translateX(calc(-100vw - 100%))"
It is pretty subtle, but is much more noticeable on the 75" screen this will be displayed on.
Using Chrome's perf tools, I can see some FPS degradation, with it dropping to 8 FPS at one point. Is there anything I can do to smooth this out further?
https://codepen.io/anon/pen/OrOvdP
I removed the position property from the .Segment__message, and positioned it using only transform.
I've also used translate3d, which forces hardware acceleration and has improved animation performance for me in the past.
I don't see jank in Firefox, Chrome, or Safari with the code below.
var link = document.querySelector(".slide");
var message = document.querySelector(".Segment__message");
var styleStr = `transition: all 10s linear; transform: translate3d(-100%, 0, 0)`;
link.onclick = () => {
message.style = styleStr;
}
.Segment {
position: absolute;
width: 100%;
overflow: hidden;
top: 0;
outline: 1px solid orange;
}
.Segment__message {
display: inline-block;
margin-top: 15px;
transform: translate3d(100vw, 0, 0);
padding-left: 10px;
will-change: transform;
font-size: 30px;
}
.Segment__message::after {
content: "/";
color: blue;
display: block;
float: right;
padding-left: 15px;
}
.slide {
display: block;
margin-top: 50px;
}
<div class="Segment">
<div class="Segment__message">I am a message</div>
</div>
<a class="slide" href="#">Slide left</a>
You could do some enhancements to make sure your message will be drawn on a new, separate layer, like:
.Segment {
// ...
perspective: 600px;
z-index:2;
}
.Segment__message {
// ...
z-index:3;
will-change: transform;
transform-style: preserve-3d;
font-size: 30px;
}
But there is one little nasty trick that you can do along with will-change property, if you will apply some really small delay (like 0.1s) your animation will be prerendered before it fires, thus should be smoother:
message.style = "transition: all 10s linear .1s; transform: translateX(calc(-100vw - 100%))"
On first view, it could be the calc() section with vw and %. This mix caused sometimes trouble in my projects, for you get non-integers, which will be rounded automatically by the browser. So I changed the 100% to 100vw in your codepen. The result was a much smoother animation - at least in Chrome.
In addition to using translate3d instead of translateX as pointed out by #sol, I was able to improve the performance by using position: absolute and a fixed width for .Segment__message (plus a fixed height for the .Segment).
On my machine the performance degradation is very minor (even with 6x CPU slowdown) so it was difficult to test accurately, however my guess is that since an item is positioned using position: relative; (or position: static as per #sol's example) then it might cause some style recalculations as the item's (and the adjacent DOM element - in this cause a pseudo element) position shifts within it's parent container.
https://codepen.io/anon/pen/XoZRwr
Related
I have an element that scales using transform when hovered over. This is great but scaling it on a static number isn't very useful if I want the website to be viewable on a variety of browser/screen sizes. The code looks like this right now:
<!DOCTYPE html>
<html>
<head>
<script>
{
const w = window.innerWidth/200;
const h = window.innerHeight/200;
}
</script>
<style>
.botright
{
width: 100px;
height: 100px;
top: 50%;
left: 50%;
position: absolute;
background: pink;
transition: transform 500ms ease-in-out;
transform-origin: left top;
display: flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
}
.botright:hover
{
transform: scaleX(w) scaleY(h);
font-size: 0;
}
The notable bits are at the top and bottom of the code (the js and the :hover). I'm unable to use the w or h constants created in the javascript anywhere else in the css and the js doesn't work inside or encompassing the :hover so I'm sort of at a loss on how to make the element expand to a size relative to the length and width of the view window.
A static number in the scale() would work if the element itself was scaled to the window size instead but that resulted in some other problems and more importantly, isn't what I'm looking to do.
The current functional version does use a simple number to scale the element by but because the element is always 100px x 100px (and I am intentionally using pixels because I always want the element to be a square when not hovered over no matter what), scaling just makes a bigger square.
I am looking for a way to scale the square so that when hovered over, it will always go exactly to the very edge of the available window space.
I think you can do it like this:
.box {
background: #05273D;
border-radius: 5px;
height: 100px;
width: 100px;
margin: 100px;
transition: transform 1s;
}
.box:hover {
transform: scale(2);
}
<div class='box'></div>
You can learn more from here: https://www.w3schools.com/css/css3_2dtransforms.asp
You cannot pass Javascript variables to a style tag like this. A solution would be to create the style tag with javascript and concatenate the calculated values.
<script>
const w = window.innerWidth/200;
const h = window.innerHeight/200;
var styleTag = document.createElement("style");
styleTag.innerHTML = '.botright:hover { transform: scaleX(' + w + ') scaleY(' + h + '); font-size: 0; }';
document.head.appendChild(styleTag);
</script>
<style>
.botright
{
width: 100px;
height: 100px;
top: 50%;
left: 50%;
position: absolute;
background: pink;
transition: transform 500ms ease-in-out;
transform-origin: left top;
display: flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
}
</style>
<div class="botright">TEST</div>
However note that this will not be updated on window's resize. If you need so, you can put the js code in a function that you bind to window resize event. However if you so, your function should give an id to the style tag, and remove it if it exists before adding it again.
I have a list that scrolls with a CSS transform, but I want to have the same speed no matter how long the list is. How can I control the speed with jQuery?
Here is the CSS, and a link to a pen: https://codepen.io/disco_p/pen/BvWdqX?editors=1100
section {
height: 90vh;
background: #000;
}
ul {
margin: 0;
padding: 0;
list-style: none;
color: #fff;
font-size: em(18);
text-align: center;
font-weight: 500;
column-count: 4;
column-width: 200px;
column-gap: 50px;
animation: floatTextUp 3s infinite linear;
}
li {
margin-bottom: 1.1em;
}
.scroll {
height: 200px;
overflow: hidden;
position: relative;
}
#keyframes floatTextUp {
to {
transform: translateY(100%);
}
}
You can set the animation duration with jquery based on the length of the list.
function calcDuration(length) {
/* For every ten items take 1s */
return length / 10 + 's';
}
const listLength = $('ul li').length;
$('ul').css('animation-duration', calcDuration(listLength));
Hi Elizabeth and welcome :)
You travel 100%(this is relative) in 3s. So you will need to have a fixed(absolute) height so get a constant speed.
Is there a maximum List size? If yes, you can just use this as default height and adjust your animation time until you like the speed.
You have to use some type of absolute height, so the speed is deterministic according to the pixel to travel:
min-height: 200px; for your .ul
This will work until all space is used.
codepen
I'm making a very unique progress bar that visually looks like a glass orb filling up with liquid. Unfortunately, because of the rounded shape, the traditional method of modifying the height doesn't work so well (as demonstrated with this fiddle https://jsfiddle.net/usuwvaq5/2/).
As you can see, having the div height "slide up" is not the desired visual. I have also tried playing a bit with css clip, but was unable to get it to work for me. How can I create the visual effect of the glass "filling" with the second image?
Simply add background-position:bottom; to #inner-progress:
#inner-progress {
background-image: url(https://www.novilar.com/img/battle/ui/purification_meter_bar.png);
background-color: transparent;
background-position:bottom;
width: 100%;
overflow: hidden;
position: absolute;
bottom: 0;
height: 0%;
}
JSFiddle Demo
Jacob Gray probably has the best answer, but here's an alternative:
Fiddle
This approach uses css for the animation, instead of javascript. JS is only used here to trigger the animation, the rest is css.
This uses the css transition property to "animate" the height as it changes from 100% to 0%. The only notable change in the html is that I swapped the background of the inner with the outer.
Perhaps this answer will be a better solution to a future reader of this thread - depending on their implementation and/or preferences.
$(document).ready(function() {
$('#inner-progress').addClass("load");
});
#outer-progress {
background-image: url(https://www.novilar.com/img/battle/ui/purification_meter_bar.png);
background-color: transparent;
border: 0;
height: 100px;
width: 100px;
position: relative;
}
#inner-progress {
background-image: url(https://www.novilar.com/img/battle/ui/purification_meter_background.png);
background-color: transparent;
width: 100%;
overflow: hidden;
position: relative;
bottom: 0;
height: 100%;
transition: height 3s;
-webkit-transition: height 3s;
}
.progress-value {
color: #FFF !important;
font-weight: bold;
position: absolute;
top: 40%;
left: 40%;
}
.load{
height: 0% !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="outer-progress">
<div id="inner-progress" value="0" max="100"></div>
<span class="progress-value">0%</span>
</div>
If you see the code sample I have shared, you can see the overlay going outside the box. I traced the issue down to the transition attribute.
I want to remove the content outside of the div. Overflow isn't working as it is supposed to. (removing transition works, but I would like to keep it if possible)
Any help is appreciated
Codepen Link
CODE
var timer = setInterval(function() {
document.querySelector(".qs-timer-overlay").style.opacity = (document.querySelector(".qs-timer-overlay").style.opacity * 1) + 0.1;
if (document.querySelector(".qs-timer-overlay").style.opacity * 1 == 1) {
clearInterval(timer);
}
}, 1000);
.qs-main-header .qs-timer {
padding: 13px 10px;
min-width: 130px;
text-align: center;
display: inline-block;
background-color: #dd8b3a;
color: #FFF;
font-size: 20px;
border-radius: 50px;
text-transform: uppercase;
float: right;
cursor: pointer;
position: relative;
overflow: hidden;
}
.qs-main-header .qs-timer-overlay {
z-index: 1;
width: 10%;
max-width: 100%;
position: absolute;
height: 100%;
top: 0;
left: 0;
background-color: #c7543e;
opacity: 0.0;
/* border-radius: 50px 50px 0px 50px; */
}
.qs-main-header .qs-timer-content {
z-index: 2;
position: relative;
}
.scale-transition {
-webkit-transition: all 1s;
transition: all 1s;
}
<div class="qs-main-header">
<div class="qs-timer scale-transition ng-hide" ng-show="visibility.timer">
<div class="scale-transition qs-timer-overlay"></div>
<div class="qs-timer-content ng-binding">0 <span class="ng-binding">Sec(s)</span>
</div>
</div>
</div>
Actually it is the border-radius that is not getting respected when the transition is happening. This is because of creation of compositing layers for accelerated rendering and can be explained by having a look at the following articles:
HTML5 Rocks - Accelerated Rendering in Chrome
GPU Accelerated Compositing in Chrome.
Why does the issue not happen when transition is disabled?
When styles change but none of the criteria that necessitates the creation of a compositing layer is satisfied (that is, no animation or transition or 3D transform etc):
There is no compositing layer and so the whole area seems to get repainted at every change. Since a full repaint happens there is no issue.
View the below snippet (in full screen mode) after enabling "Show paint rects" and "Show composited layer borders" from Dev tools and observe the following:
No areas with an orange border (compositing layer) are created.
Every time the styles are modified by setting the focus on one of the a tags, the whole area gets repainted (a red or green blinking area).
.outer {
position: relative;
height: 100px;
width: 100px;
margin-top: 50px;
border: 1px solid red;
overflow: hidden;
}
.border-radius {
border-radius: 50px;
}
.inner {
width: 50px;
height: 50px;
background-color: gray;
opacity: 0.75;
}
a:focus + .outer.border-radius > .inner {
transform: translateX(50px);
height: 51px;
opacity: 0.5;
}
<a href='#'>Test</a>
<div class='outer border-radius'>
<div class='inner'>I am a strange root.
</div>
</div>
Why does adding a transition create a problem?
Initial rendering has no compositing layer because there is no transition yet on the element. View the below snippet and note how when the snippet is run a paint (red or green blinking area) happens but no compositing layer (area with orange border) is created.
When transition starts, Chrome splits them into different compositing layers when some properties like opacity, transform etc are being transitioned. Notice how two areas with orange borders are displayed as soon as the focus is set on one of the anchor tags. These are the compositing layers that got created.
The layer splitting is happening for accelerated rendering. As mentioned in the HTML5 Rocks article, the opacity and transform changes are applied by changing the attributes of the compositing layer and no repainting occurs.
At the end of the transition, a repaint happens to merge all the layers back into a single layer because compositing layers are no longer applicable (based on criteria for creation of layers).
.outer {
position: relative;
height: 100px;
width: 100px;
margin-top: 50px;
border: 1px solid red;
overflow: hidden;
}
.border-radius {
border-radius: 50px;
}
.inner {
width: 50px;
height: 50px;
background-color: gray;
transition: all 1s 5s;
/*transition: height 1s 5s; /* uncomment this to see how other properties don't create a compositing layer */
opacity: 0.75;
}
a:focus + .outer.border-radius > .inner {
transform: translateX(50px);
opacity: 0.5;
/*height: 60px; */
}
<a href='#'>Test</a>
<div class='outer border-radius'>
<div class='inner'>I am a strange root.
</div>
</div>
This illustrates that when the layers are merged back and full repaint happens, the border-radius on the parent also gets applied and respected. However, during transition only the compositing layer's properties are changed, so the layer seems to become unaware of the properties of other layers and thus doesn't respect the border-radius of the parent.
I would assume this to be because of the way rendering of layers work. Each layer is a software bitmap and so it kind of becomes equivalent to having a circular image and then placing a div on top of it. That would obviously not result in any clipping of content.
The comment in this bug thread also seems to confirm that a repaint happens when a separate layer is no longer required.
We want to repaint if "gets own layer" is going to change
Note: Though they are Chrome specific, I think the behavior should be similar in others also.
What is the solution?
The solution seems to be to create a separate stacking context for the parent (.qs-timer) element. Creating a separate stacking context seems to result in a separate compositing layer being created for the parent and this solves the issue.
As mentioned by BoltClock in this answer, any one of the following options would create a separate stacking context for the parent and doing one of them seems to resolve the issue.
Setting a z-index on the parent .qs-timer to anything other than auto.
var timer = setInterval(function() {
document.querySelector(".qs-timer-overlay").style.opacity = (document.querySelector(".qs-timer-overlay").style.opacity * 1) + 0.1;
if (document.querySelector(".qs-timer-overlay").style.opacity * 1 == 1) {
clearInterval(timer);
}
}, 1000);
.qs-main-header .qs-timer {
padding: 13px 10px;
min-width: 130px;
text-align: center;
display: inline-block;
background-color: #dd8b3a;
color: #FFF;
font-size: 20px;
border-radius: 50px;
text-transform: uppercase;
float: right;
cursor: pointer;
position: relative;
overflow: hidden;
z-index: 1; /* creates a separate stacking context */
}
.qs-main-header .qs-timer-overlay {
z-index: 1;
width: 10%;
max-width: 100%;
position: absolute;
height: 100%;
top: 0;
left: 0;
background-color: #c7543e;
opacity: 0.0;
/* border-radius: 50px 50px 0px 50px; */
}
.qs-main-header .qs-timer-content {
z-index: 2;
position: relative;
}
.scale-transition {
-webkit-transition: all 1s;
transition: all 1s;
}
<div class="qs-main-header">
<div class="qs-timer scale-transition ng-hide" ng-show="visibility.timer">
<div class="scale-transition qs-timer-overlay"></div>
<div class="qs-timer-content ng-binding">0 <span class="ng-binding">Sec(s)</span>
</div>
</div>
</div>
Setting opacity to anything less than 1. I have used 0.99 in the below snippet as it doesn't cause any visual difference.
var timer = setInterval(function() {
document.querySelector(".qs-timer-overlay").style.opacity = (document.querySelector(".qs-timer-overlay").style.opacity * 1) + 0.1;
if (document.querySelector(".qs-timer-overlay").style.opacity * 1 == 1) {
clearInterval(timer);
}
}, 1000);
.qs-main-header .qs-timer {
padding: 13px 10px;
min-width: 130px;
text-align: center;
display: inline-block;
background-color: #dd8b3a;
color: #FFF;
font-size: 20px;
border-radius: 50px;
text-transform: uppercase;
float: right;
cursor: pointer;
position: relative;
overflow: hidden;
opacity: 0.99; /* creates a separate stacking context */
}
.qs-main-header .qs-timer-overlay {
z-index: 1;
width: 10%;
max-width: 100%;
position: absolute;
height: 100%;
top: 0;
left: 0;
background-color: #c7543e;
opacity: 0.0;
/* border-radius: 50px 50px 0px 50px; */
}
.qs-main-header .qs-timer-content {
z-index: 2;
position: relative;
}
.scale-transition {
-webkit-transition: all 1s;
transition: all 1s;
}
<div class="qs-main-header">
<div class="qs-timer scale-transition ng-hide" ng-show="visibility.timer">
<div class="scale-transition qs-timer-overlay"></div>
<div class="qs-timer-content ng-binding">0 <span class="ng-binding">Sec(s)</span>
</div>
</div>
</div>
Adding a transform to the element. I have used translateZ(0px) in the below snippet as this also doesn't create any visual difference.
var timer = setInterval(function() {
document.querySelector(".qs-timer-overlay").style.opacity = (document.querySelector(".qs-timer-overlay").style.opacity * 1) + 0.1;
if (document.querySelector(".qs-timer-overlay").style.opacity * 1 == 1) {
clearInterval(timer);
}
}, 1000);
.qs-main-header .qs-timer {
padding: 13px 10px;
min-width: 130px;
text-align: center;
display: inline-block;
background-color: #dd8b3a;
color: #FFF;
font-size: 20px;
border-radius: 50px;
text-transform: uppercase;
float: right;
cursor: pointer;
position: relative;
overflow: hidden;
transform: translateZ(0px) /* creates a separate stacking context */
}
.qs-main-header .qs-timer-overlay {
z-index: 1;
width: 10%;
max-width: 100%;
position: absolute;
height: 100%;
top: 0;
left: 0;
background-color: #c7543e;
opacity: 0.0;
/* border-radius: 50px 50px 0px 50px; */
}
.qs-main-header .qs-timer-content {
z-index: 2;
position: relative;
}
.scale-transition {
-webkit-transition: all 1s;
transition: all 1s;
}
<div class="qs-main-header">
<div class="qs-timer scale-transition ng-hide" ng-show="visibility.timer">
<div class="scale-transition qs-timer-overlay"></div>
<div class="qs-timer-content ng-binding">0 <span class="ng-binding">Sec(s)</span>
</div>
</div>
</div>
The first two approaches are more preferable than the third because the third one works only on a browser that supports CSS transforms.
Yes, adding opacity: 0.99; to .qs-timer issue will fixed.
When opacity: 1 OR NOT define:
In this special case, there is no transparency involved so that gfx could avoid doing the expensive things.
In case Opacity: 0.99:
nsIFrame::HasOpacity() decides that there is an opacity, so gfx include valuable things. ( likes opacity with border-radius)
For more help Special case opacity:0.99 to treat it as opacity:1 for graphics , This ticket is not providing the opinion of our actual goal, but giving the idea about what is happening inside of CSS.
Could someone have a look at my code. what it's suppose to do is animate the img tags using fadeIn and fadeOut but it only fades out the first img and doesn't fade in the second img. I think my css could be wrong and that's why the second image isn't showing Im not getting any errors
its an image on top of another image
jQuery
$(document).ready(function() {
$('.social-media a').on('mouseenter', function(e) {
$(this).find("img:nth-child(2)").fadeIn();
$(this).find("img:nth-child(1)").fadeOut()
});
})
HTML
<div class="social-media">
<a title="Share On Twitter" href="#">
<img alt="" src="images/icon_twitter.png" />
<img class="test" alt="" src="images/icon_twitter_active.png" />
</a>
</div>
CSS
.social-media {
padding-top: 20px;
width: 166px;
margin: 0 auto 10px auto;
}
.social-media a {
position: relative;
width: 55px;
height: 51px;
}
.social-media a img:nth-child(1) {
opacity: 1;
}
.social-media a img:nth-child(2) {
position: absolute;
left: 0; top: -33px;
opacity: 0;
z-index: 2;
}
Instead of hiding the second <img> element with zero opacity, you should use display: none instead:
.social-media a img:nth-child(2) {
position: absolute;
left: 0; top: -33px;
display: none;
z-index: 2;
}
http://jsfiddle.net/8vH4E/
However, I would strongly recommend using a simple CSS image sprite to achieve this effect, which doesn't require JS.
Update: Since OP asked if it is possible to do with CSS, I have modified the Fiddle to exclude the use of JS and simply rely on the use of CSS and pseudo-elements: http://jsfiddle.net/8vH4E/2/
.social-media a {
display: block;
position: relative;
width: 100px;
height: 100px;
background-image: url(http://placehold.it/200x200);
background-size: cover;
}
.social-media a::before {
background-image: url(http://placehold.it/200x200/4a7298/eeeeee);
background-size: cover;
content: '';
display: block;
opacity: 0;
pointer-events: none;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
-webkit-transition: all .25s ease-in-out;
transition: all .25s ease-in-out;
}
.social-media a:hover::before {
opacity: 1;
}
My strategy is rather simple:
Use background images instead. For sizing, I have used cover but you are free to use any sizing (absolute pixel/point sizes, relative percentage sizes or dynamically-computed sizes like cover, contain)
For the hover state, use an absolutely-positioned pseudo element that covers the entire <a> (by positioning it absolutely and with zero offset from all four directions). We don't need pointer events on the pseudo element, so we set it to pointer-events: none
When the <a> element is hovered on (targeted with the :hover selector), we toggle the opacity of the pseudo-element from 0 to 1. We declare the transition property on the pseudo-element to allow for smooth, browser-computed and JS-agnostic transition.
the sprite is good but does not give smooth fading animation (think that was the main reason, KDM, wasn't it?).
So let's fix existing code:
as the fadeOut() turns the element to the display: none; state, as the fadeIn() starts working when the element is display: none;. So let's turn the 2nd image in display: none; first;
We can omit the opacity at all for both images (relying on 1.0 as default); $.fadeIn/Out() use the opacity from the CSS as the start/end point of the animation. Of course you can set the opacity explicitly for each image if it's designed in such way;
display: inlibe-block; for the <a> is a good point because it contains inline elements which possibly can disappear (display: none;); that causes the the whole <a> disappearing and the mouseleave event firing with unexpected UI bugs.
Enjoy http://jsfiddle.net/8vH4E/1/ and thanks to Terry for the fiddle :)