I am trying to animate a growing ellipsis appended to some text when hovered over, and then vanish on mouseout. I have managed to create the effect, but only if the user is very delicate about moving the cursor over the effected elements. How can I get this to perform better, so that if the user moves the cursor all over the elements I don't get the buggy behavior you see below (try running the cursor across the elements quickly)? I've already tried setInterval and saw that the problems were even worse. Any help is appreciated. Thank you.
var i=1;
var $test=$();
var mousedOver=0;
function test() {
if(i!==0) {
$test.append('<span class="a">.</span>');
} else {
$('.a').remove();
}
if(mousedOver===1){
i=(i+1)%4;
setTimeout(test,1000);
}
}
$('.nav>p').on('mouseover',function() {
var $test2=$(this);
setTimeout(function() {
$test=$test2;
mousedOver=1;
test();
},1000);
})
$('.nav>p').on('mouseout',function() {
$test=$();
mousedOver=0;
$('.a').remove();
i=1;
})
.nav {
display: flex;
height: 100vh;
width:30%;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius:40px;
border-style: solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<head>
</head>
<body>
<div class="nav">
<p>text1</p>
<p>text2</p>
<p>text3</p>
</div>
</body>
The main issue with your code is that you are only using one flag variable (mousedOver) to determine when any of the 3 animations should be active. So if someone moves their mouse over one of the elements it waits 1000ms and sets the flag to 1, then says "ok I'll wait 1000ms and check again if mousedOver is still 1". If the user moves their mouse away (setting mousedOver to 0) then moves onto another element (setting the mousedOver to 1) before that 1000ms is up then when the first element checks again and sees that mousedOver is still 1, it has no reason to stop the animation.
There are few ways to fix this:
First of all, you could use a different flag for each element you can determine when that specific element should cancel its timeouts. This is a little more work, but might keep things easier to read and understand.
Another JS solution uses clearTimeout method: store each timeout ID in a variable, so that you can "clear"/cancel them onmouseout:
JavaScript
var timeoutID = null;
// Whenever you set a timeout, store its index to be cleared if necessary
timeoutID = setTimeout(test,1000);
// inside the "mouseout" handler
clearTimeout(timeoutID);
Note, you only need one timeoutID variable, as you would be clearing any existing timeout (onmouseout) before a new one is created.
Finally, a CSS-only method. Since you are using CSS flex, I assume you can use CSS3. Instead of adding/removing these ellipses, you can consider always having them there and changing the color or opacity, that is changing the CSS color from rgba(0, 0, 0, 0) to rgba(0, 0, 0, 1) or opacity from 0 to 1. This might even a good idea when using one of the JS processes, because at least then you know the text won't move around when the dots are shown.
The main difference visually between this option and above is that these will show some "fading in", which might not be what you want. The code below shows how to set up all the "first" dots (setting up the second and third is similar).
CSS
#keyframes show-first-dot {
/* Start all the animations transparent */
0% {
color: rgba(0, 0, 0, 0);
}
/* End transparency at a different % for each dot to control when it fades in */
50% {
color: rgba(0, 0, 0, 0);
}
/* End all the animations opaque */
100% {
color: rgba(0, 0, 0, 1);
}
}
/* keep dot transparent by default */
.nav > p a {
color: rgba(0, 0, 0, 0);
}
/* Keep each dot opaque after animation ends */
.nav > p:hover a {
color: rgba(0, 0, 0, 1);
}
/* Use CSS selectors to assign animations to each dot */
.nav > p:hover a:first-of-type {
animation-name: show-first-dot;
animation-duration: 1s;
}
/* ... set up an animation for nth-of-type(2), etc. for as many dots as you want */
Related
I have this element that starts hidden and then gets animated with a css transition on a click event.
I know the display property cannot be animated, so what I do is remove the class that applies the display:none, and then make the change that triggers the css transition, like so:
popin.classList.remove('hidden') // removes the display:none property
setTimeout(() => {
popin.classList.remove('closed') // triggers transition
}, 10)
See this fiddle: http://jsfiddle.net/wre2674p/6/ for a full example.
I've found out that in order to work, the 2nd step must be done asynchronously. Put it in a setTimeout and it works...sort of. In Chrome, any timeout duration works (even 0).
For Firefox and Edge, the behavior varies. For 100ms, it works every time. But for a timeout of e.g. 10ms, the transition works only maybe 50% of times. Since it delays the animation, I wish to keep it as low as possible, while ensuring it works consistently.
I suspect it is related to reflow/repaint occurring when changing display property from none to block, but I lack details on these subjects to full understand what's happening and how to prevent it. Any idea?
Remove the hidden class from CSS and HTML, remove timeout from js. There is no need to display none the #popin since you already have overflow hidden. The transition can be triggered directly, you are over complicating things
document.getElementById('toggle').addEventListener('click', function(e){
let source = e.currentTarget
source.disabled = true
let popin = document.getElementById('popin')
if (popin.classList.contains('closed')){
popin.classList.remove('closed')
}
else{
popin.classList.add('closed')
}
setTimeout(() => {
source.disabled = false
}, 850)
})
body{
overflow: hidden;
}
#popin{
display: block;
position: absolute;
top: 0;
right: 0;
width: 400px;
height: 100vh;
/*transform: translate(0, 0);*/
transition: opacity 800ms;
opacity: 1;
background: lightgreen;
}
#popin.closed{
opacity: 0;
z-index: -1;
pointer-events: none;
}
<button id="toggle">toggle</button>
<div id="popin" class="closed">
<h1>Popin</h1>
</div>
I cannot figure out why a composite transition (opacity and height) on the same element does not follow the expected duration. However, this issue happens only the very first time is run, then begins to work perfectly.
UPDATE:
I found a slimmer way to demonstrate the problem.
By click the "start" button, the box at the right turns immediately transparent, and does not fade slowly as the left one.
$("button").on("click", function() {
doNative();
doJQuery();
});
function doNative() {
const elem = document.getElementById("bn");
elem.style.opacity = 0;
elem.style.height = 0;
elem.style.transitionDuration = "2s";
elem.style.transitionProperty = "opacity, height";
}
function doJQuery() {
const elem = $("#bj");
elem.css({
opacity: 0,
height: 0,
"transition-duration": "2s",
"transition-property": "opacity, height",
});
}
.block {
width: 200px;
font-size: 24px;
font-family: Tahoma;
display: inline-block;
margin: 10px;
border: 1px solid gray;
}
.initial {
opacity: 1;
height: 200px;
}
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<div>
<button>start</button>
</div>
<div>
<div class="block">
<div id="bn" class="initial">This box is collapsed using the native transition API</div>
</div>
<div class="block">
<div id="bj" class="initial">This box is collapsed using the jQuery transition API</div>
</div>
</div>
LEGACY CONTENT (no relevant anymore):
Here is just a snippet to depict the context:
items.css({
opacity: 0,
height: 0,
"transition-duration": transitionDuration + "ms",
"transition-property": "opacity, height"
});
To better clarify the actual and the expected behavior, have a look at this pen: https://codepen.io/highfield/pen/dKLKKo
Once run, by pressing the "hide" button, the "Items" block disappears immediately, but the expected behavior is to fade along a certain interval.
After this initial weird phase, the "show" and "hide" functions behave perfectly as expected.
I also noticed that by removing the "height" from the "transition-property" CSS field, the opacity will fade correctly.
How to patch this problem?
The transition properties must be set on the target element before performing any transitions. To make that initial animation work too, you should set a basic transition definition to the #s1 element in CSS.
Finally, I've found where the problem is.
It seems that the order of the fields matters, but I don't understand why the native version works fine, even in a random order. By changing the jQuery version as follows, the transitions behave correctly:
function doJQuery() {
const elem = $("#bj");
elem.css({
"transition-duration": "2s",
"transition-property": "opacity, height",
opacity: 0,
height: 0,
});
}
I was reading this article http://semisignal.com/?p=5298 and the author wrote that
"Reflow needs to be triggered before the invisible class is removed in order for the transition to work as expected. "
My questions are :
1) Why does reflow need to be triggered?
2) I understand that we should avoid using reflow, if that is true why is the author suggesting to use reflow in order to make the transition work?
3) Instead of using reflow, is there a different method to make the transition work?
Thank you.
(Effectively: "Why can't I easily use transitions with the display property")
Short Answer:
CSS Transitions rely on starting or static properties of an element. When an element is set to display: none; the document (DOM) is rendered as though the element doesn't exist. This means when it's set to display: block; - There are no starting values for it to transition.
Longer Answer:
Reflow needs to be triggered because elements set to display: none; are not drawn in the document yet. This prevents transitions from having a starting value/initial state. Setting an element to display: none; makes the document render as if the element isn't there at all.
He suggest reflowing because it's generally accepted to hide and show elements with display: none; and display: block; - typically after the element has been requested by an action (tab or button click, callback function, timeout function, etc.). Transitions are a huge bonus to UX, so reflowing is a relatively simple way to allow these transitions to occur. It doesn't have an enormous impact when you use simple transitions on simple sites, so for general purposes you can trigger a reflow, even if technically you shouldn't. Think of the guy's example like using unminified JavaScript files in a production site. Can you? Sure! Should you? Probably not, but for most cases, it won't make a hugely noticeable difference.
There are different options available that prevent reflowing, or are generally easier to use than the method in the link you provided. Take the following snippet for a few examples:
A: This element is set to height: 0; and overflow: hidden;. When shown, it's set to height: auto;. We apply the animation to only the opacity. This gives us a similar effect, but we can transition it without a reflow because it's already rendered in the document and gives the transitions initial values to work with.
B: This element is the same as A, but sets the height to a defined size.
A and B work well enough for fading in elements, but because we set the height from auto/100px to 0 instantly, they appear to collapse on "fade out"
C: This element is hidden and we attempt to transition the child. You can see that this doesn't work either and requires a reflow to be triggered.
D: This element is hidden and we animate the child. Since the animation keyframes give a defined starting and ending value, this works much better. However note that the black box snaps into view because it's still attached to the parent.
E: This works similarly to D but we run everything off the child, which doesn't solve our "black box" issue we had with D.
F: This is probably the best of both worlds solution. We move the styling off the parent onto the child. We can trigger the animation off of the parent, and we can control the display property of the child and animate the transition as we want. The downside to this being you need use animation keyframes instead of transitions.
G: While I don't know if this triggers a reflow inside the function as I haven't parsed it myself, you can just simply use jQuery's .fadeToggle() function to accomplish all of this with a single line of JavaScript, and is used so often (or similar JS/jQuery fadeIn/fadeOut methods) that the subject of reflowing doesn't come up all that often.
Examples:
Here's a CodePen: https://codepen.io/xhynk/pen/gerPKq
Here's a Snippet:
jQuery(document).ready(function($){
$('button:not(#g)').click(function(){
$(this).next('div').toggleClass('show');
});
$('#g').click(function(){
$(this).next('div').stop().fadeToggle(2000);
});
});
* { box-sizing: border-box; }
button {
text-align: center;
width: 400px;
}
div {
margin-top: 20px;
background: #000;
color: #fff;
}
.a,
.b {
overflow: hidden;
height: 0;
opacity: 0;
transition: opacity 3s;
}
.a.show {
height: auto;
opacity: 1;
}
.b.show {
height: 100px;
opacity: 1;
}
.c,
.d {
display: none;
}
.c.show,
.d.show {
display: block;
}
.c div {
opacity: 0;
transition: 3s all;
}
.c.show div {
opacity: 1;
}
.d div {
opacity: 0;
}
.d.show div {
animation: fade 3s;
}
#keyframes fade {
from { opacity: 0; }
to { opacity: 1; }
}
.e div {
display: none;
}
.e.show div {
display: block;
animation: fade 3s;
}
.f {
background: transparent;
}
.f div {
background: #000;
display: none;
}
.f.show div {
display: block;
animation: fade 3s;
}
.g {
display: none;
}
<button id="a">A: Box Height: Auto</button>
<div class="a">This<br/>Has<br/>Some Strange<br/><br/>Content<br>But<br>That doesn't really<br>Matter<br/>Because shown,<br/>I'll be<br/>AUTO</div>
<button id="b">B: Box Height: 100px</button>
<div class="b">Content For 2</div>
<button id="c">C: Hidden - Child Transitions (bad)</button>
<div class="c"><div>Content<br/>For<br/>3<br/></div></div>
<div style="clear: both;"></div>
<button id="d">D: Hidden - Child Animates (Better)</button>
<div class="d"><div>Content<br/>For<br/>4<br/></div></div>
<div style="clear: both;"></div>
<button id="e">E: Hidden - Child Hidden & Animates</button>
<div class="e"><div>Content<br/>For<br/>5<br/></div></div>
<button id="f">F: Child Has BG & Animates (Works)</button>
<div class="f"><div>Content<br/>For<br/>5<br/></div></div>
<div style="clear: both;"></div>
<button id="g">G: This uses fadeToggle to avoid this</button>
<div class="g">I animate with<br/>JavaScript</div>
<footer>I'm just the footer to show the bottom of the document.</footer>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I have quite a few page effects on the load, and they can cause the page to disfigure on load. To stop this, I'm fading the body in and out. However, it only works when display:none; is attributed to the body section of the CSS.
CSS
body {
position: relative;
min-height: 100%;
margin: 0 0 0 0;
padding: 0 0 0 0;
border-collapse: collapse;
font-size: 12px;
display: none;
}
JS
window.onload = function () {
$('body').fadeIn();
main(); // my effect function
};
window.onbeforeunload = function () {
$('body').fadeOut();
};
I'm using a the design editor in VS 2015 and I can't see my pages because display:none; is set.
Is there any reason it's not working without display:none;?
fadeIn animates the opacity from its current state to 1 (i.e. no transparency).
If the element is completely hidden, that will be a transition from 0 to 1. If the element has a 50% opacity, it will be a transition from 0.5 to 1.
If, on the other hand, the element is already visible, there will be no animation at all, as the element is already at the target state.
I was wondering if someone could help me change the color of a div from black to white after two seconds, then from white to black after two seconds, back again and so on as long as the div exists. Putting it other way, the div is only shown whenever a user clicks and drags a surface (just like the desktop selection area) and my objective is to change the color of the borders as described above while the user is still making his selection on the surface just as resizing the div.
If your browser requirements allow you to use css3 you don't need any javascript at all.
HTML:
<div class="blinkdiv">
</div>
CSS:
#-webkit-keyframes blackWhite {
0% { background-color: white; }
50% { background-color: white; }
51% { background-color: black; }
100% { background-color: black; }
}
.blinkdiv {
height: 100px;
background-color: black;
-webkit-animation-name: blackWhite;
-webkit-animation-iteration-count: infinite;
-webkit-animation-duration: 2s;
}
Here's an example: http://jsfiddle.net/tommcquarrie/w3Qy9/1/
This will transition the background color from black to white after every 2 seconds, and repeat..
body {
-webkit-animation:name 2s infinite;
}
#-webkit-keyframes name {
0% {background-color:black;}
100% {background-color:white;}
}
jsFiddle demo.. haven't tested it in many browsers.. works in Chrome.
There are a couple of ways to do this. You could either use a setTimeout that always spawns another setTimeout (until the div disappears), or you could use a setInterval and toggle the color (with a clearInterval to stop the transition if the element should be removed). I feel like the latter approach is a little more straightforward, so I'll use that one:
var toggleIntervalId = setInterval( function() {
var $div = $('#toggleMe');
if( $div.length ) {
$div.attr( 'background-color', $div.attr('background-color')=='black'
? 'white' : 'black' );
} else {
clearInterval( toggleIntervalId );
}, 2000 );
Depending on your requirements and usage of CSS3 (or lack thereof), you may want to do this with Javascript/jQuery, and in particular with jQuery, you will likely want to look into jQueryUI.
I suggest you take a look at this StackOverflow question.
However, as for some instant gratification, here's a cross-browser Fiddle modified from the above question that will probably suffice, or at least give you a good starting point (needless, I still suggest you research this a bit further and look into the links provided as well...)
The Fiddle's code:
JQuery:
function changeColor(element, curNumber){
curNumber++;
if(curNumber > 2){
curNumber = 1;
}
console.log(curNumber);
element.addClass('color' + curNumber, 500);
element.attr('class', 'color' + curNumber);
setTimeout(function(){changeColor(element, curNumber)}, 1000);
}
changeColor($('#testElement'), 0);
CSS:
.color1{
background: white;
color: black;
}
.color2{
background: black;
color: white;
}
HTML:
<div id="testElement">This will change colors</div>