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
Related
Result looked for:
MODE 1: when the window is large (say >465px) the TOC items is displayed to the left of the content page
MODE 2: when the window's width gets smaller than 465px, reduce the width of the TOC item using transition
MODE 3: when the window's width gets greater than 465px, increase the width of the TOC item using a transition
finally, when the window's width < 465px and that the TOC is therefore hidden as a result of the mechanism described above, show some text on top that users can click on. When they click on this text, display the TOC item as an overlay. When you click on this text again, hide the TOC item as an overlay.
How to see the problem I try to get rid of:
increase the window to a large width and then back to small width. See the transitions when you go from one to the other. This is good.
make the window small so that the "Show Table of Content" text shows up. Click on the text. The TOC is displayed as an overlay. This is good. Then click again, to HIDE the TOC as an overlay. The cyan TOC disappears, but a transition is played right after. That's the problem. I want to get rid of this transition.
This behavior doesn't make sense to me, because the media query specifies that when the window < 465px the width of the TOC is 0. So why it is reset to 150px is a mystery to me. But the most important for me is, how do I get rid of this unwanted transition when the TOC as an overlay is removed (when the the 'overlay' class is toggled (off)?
function showMenuAsOverlay(caller) {
var node = document.getElementById("toc");
node.classList.toggle('overlay');
if (node.classList.contains('overlay'))
caller.innerHTML = "Hide Table of Content";
else
caller.innerHTML = "Show Table of Content";
}
.wrapper {
display: flex;
flex-direction: row;
border: 3px solid black;
z-index: -1;
position: relative;
}
.container-left {}
#toc {
border: 1px solid green;
flex: 0 0 auto;
white-space: pre;
z-index: -1;
width: 150px;
background-color: red;
transition: width 1s ease-out;
box-sizing: border-box;
}
.container-right {
display: flex;
border: 1px solid red;
flex 1 1 auto;
max-width: 400px;
background-color: white;
z-index:-1;
box-sizing: border-box;
}
.myicon {
cursor: pointer;
visibility: hidden;
}
#media
screen and (max-width: 465px) {
#toc {
width: 0;
background-color: purple;
transition: width 1s ease-out;
}
#toc.overlay {
z-index: 999;
position: absolute;
left: 0px;
background-color: cyan;
bottom: 0;
top: 0;
width: 150px;
transition: left 1s ease-out;
}
.myicon {
visibility: visible;
}
}
<body>
<div onclick="showMenuAsOverlay(this)" class="myicon" id="myicon">Show Table of Content</div>
<div class="wrapper">
<div class="container-left" id="toc" data-state="0">This is some text in the TOC</div>
<div class="container-right">
This is some content this is some content this is some more content, and this is content again and again.
</div>
</div>
</body>
The transition is happening when <div id="toc"> loses the class overlay.
That means you go from applying this rule:
#toc.overlay {
z-index: 999;
position: absolute;
left: 0px;
background-color: cyan;
bottom: 0;
top: 0;
width: 150px;
transition: left 1s ease-out;
}
to applying this rule:
#toc {
width: 0;
background-color: purple;
transition: width 1s ease-out;
}
This makes it clear why the transition is happening. You're going from width: 150px to width: 0 with this transition applied from #toc: width 1s ease-out;
Also, you've got this applied without a media query:
#toc {
border: 1px solid green;
flex: 0 0 auto;
white-space: pre;
z-index: -1;
width: 150px;
background-color: red;
transition: width 1s ease-out;
box-sizing: border-box;
}
This means the transition applies whatever the screen size. I don't think that's what you want. Put a media query around that block to only apply when you really want it to.
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
I am trying to do a very simple quiz like this one:
https://www.sitepoint.com/simple-javascript-quiz/
I tried to make it more responsive and added this line:
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1.0"/>
It worked fine but when I add a long text on the alternatives of each question, the button "Next Question" stay behind the text, only on mobile (iphone 6, safari).
I tried to add a z-index: 1000; and nothing changed:
button{
font-family: 'Work Sans', sans-serif;
font-size: 22px;
background-color: #279;
color: #fff;
border: 0px;
border-radius: 3px;
padding: 20px;
cursor: pointer;
margin-bottom: 20px;
z-index:1000;
}
So, there's a few things wrong here. As said above you need to remove the height from .quiz-container and remove the absolute positioning from .slide.
What I would suggest is that you add display: none; to slide then your active style to display:block - this will correctly display the button where it should be. With that said, you will lose the fade effect. You'd need to add this CSS to get it back. Hope this helps!
.quiz-container {
position: relative;
margin-top: 40px;
}
.slide {
width: 100%;
opacity: 0;
display: none;
}
.active-slide {
opacity: 1;
display: block;
}
You set position: absolute to your quizz questions, so they will ignore the space of every element you set in HTML.
A large z-index will only put an element above another, that's the why you see the quizz questions above the button.
The problem will be solved if you increment the height of quiz-container on mobile screen (try use #media screen).
https://www.w3schools.com/cssref/css3_pr_mediaquery.asp
I recomend you to find another way to put your questions at the top of page instead using position: absolute
The problem really is that your quiz-container div has a fixed height of 200px, and you can't make it fluid because your slides have position:absolute, which removes them from the flow and prevents the parent growing in height accordingly.
So you need to re-think how to go about this.
An interesting approach would be to use flexbox, controlling which slide to show with the order property.
.quiz-container {
margin-top: 40px;
overflow: hidden;
}
#quiz{
display: flex;
}
.slide {
opacity: 0;
transition: opacity 0.5s;
/*gives each slide 100% width and prevents it from growing or shrinking*/
flex: 0 0 100%;
}
.active-slide {
opacity: 1;
/*sets the order to -1, so it's positioned before all other flex-items*/
order: -1;
}
I'm trying to switch images in a div every few seconds
the current code works, but there are 2 things that I want to change and I need your help for that:
The div resizes to the current image displaying in it, I want it to always use the size of the bigger image
I want to fade between the images instead of just switching
Thanks for reading, I hope you can help me.
This is what I got so far:
var imgIndex = 0;
setInterval(function() {
images[imgIndex].style.display = "none";
imgIndex++;
if (imgIndex >= images.length) {
imgIndex = 0;
}
images[imgIndex].style.display = "block";
}, 5000);
.imageDisplay {
display: inline-block;
width: 100%;
background-Color: white;
color: black;
border-radius: 5%;
margin: 2px;
padding: 1px;
opacity: 0.5;
transition-duration: 0.5s;
}
.imageDisplay:hover {
opacity: 1;
}
.Image {
width: 99%;
height: auto;
margin: 1px;
padding: 1px;
border-radius: 5%;
cursor: pointer;
display: none;
}
<div class="imageDisplay">
<p>Description</p>
<img class="Image"></img>
<img class="Image"></img>
</div>
Firstly you could set the div to be equal to the size of the large image and then use:
object-fit: cover;
object-position:center;
As for switching you could use:
transition: opacity .3s linear;
Read here on how to exactly implement the transitions. My suggestion would be stacking them all up and fading them in one by one every few seconds.
https://www.w3schools.com/css/css3_transitions.asp
http://css3.bradshawenterprises.com/cfimg/
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.