So I am making a clicker game and am kind of stuck. I want a popup like cookieClicker has when you get an achievement. It pops up and tells you what happened, you can click the x or it will just fade away after a few seconds.
I tried making something with pure javascript and CSS to no avail, it would fade away nicely but not automatically.
So how do I make it so whenever X element is made/displayed then it goes away after 3 seconds?
Also, if it matters the element would be created by a javascript function, and multiples might be created at the same time.
P.S. I tried searching and found something about auto-fading in javascript but nothing in there seemed to work either.
EDIT: After trying to view cookieclicker source and playing the game again it appears it doesn't even have this functionality. The closest thing I can compare it to is when you would add something to your cart on a website, then it alerts you the item was added and then fades away.
Here is one approach which uses Javascript to trigger a CSS transition:
var button = document.getElementsByTagName('button')[0];
var div = document.getElementsByTagName('div')[0];
function autoFader() {
if (window.getComputedStyle(div).getPropertyValue('display') === 'none') {
div.style.display = 'block';
setTimeout(function(){
div.style.opacity = '0';
},10);
setTimeout(function(){
div.removeAttribute('style');
},4010);
}
}
button.addEventListener('click',autoFader,false);
div {
display: none;
width: 200px;
height: 100px;
margin: 20px auto;
padding: 6px;
font-size: 20px;
color: rgb(255,255,255);
text-align: center;
border: 3px solid rgb(127,0,0);
background-color: rgb(255,0,0);
opacity: 1;
transition: opacity 3s linear 1s;
}
<button type="button">Click Me</button>
<div>
<p>Hi, I'm an auto-fading pop-up.</p>
</div>
So, your openPopup function might look like this:
function openPopup(/* options here */) {
const popup = actuallyOpenPopup();
waitSomeTimeAndCloseIfNotClosedYet(popup);
}
where 2nd function should take a popup instance (which has .close method probably, or dismiss)
and start a timeout. You need to keep that timeout, so if close was called, you need to cancel it.
Something like this:
function waitSomeTimeAndCloseIfNotClosedYet(popup) {
const originalClose = popup.close;
/* monkey patching, decorating,
separate method - whatever you prefer */
popup.close = function () {
clearTimeout(this.timeout);
originalClose.call(this);
};
popup.timeout = setTimeout(() => popup.close(), 3000);
}
So, if was closed manually - it wont be called twice, if not, will fire up a timeout and close automatically.
Via CSS you can only achieve visible closing, but not removal of nodes. (Google for transition visibility, fade out modal etc.)
Hope this helps!
Related
When I want to yield execution of my javascript and allow the browser to apply styles etc. before I continue, I tend to use the common technique of setTimeout with a delay of 0 to have a callback queued at the end of the event loop. However, I came across a situation where this doesn't seem to be working reliably.
In the snippet below, I have an active class that applies a transition to the chaser element.
When I hover over a target div, I want to remove the active class from the chaser element, move the chaser to a new location, then reapply the active class. The effect should be that the o should immediately vanish, and then fade in its new location. Instead, both the opacity and top have the transition applied, so the o slides from position to position, most of the time.
If I increase the inner timeout's delay to 10, it starts to behave as I originally intended. If I set it to 5, then it sometimes does and sometimes doesn't.
I would have expected any setTimeout to have queued my callback until after the style updates have been applied, but there's a clear race condition here. Am I missing something? Is there a way to guarantee the order of updates?
I'm on Chrome 56 on macOS and Windows, haven't tested other browsers yet.
(I know I can achieve this in other ways such as applying the transition to only the opacity property - please consider this a contrived example to demonstrate the particular question about ordering style updates).
var targets = document.querySelectorAll('.target');
var chaser = document.querySelector('#chaser');
for (var i = 0; i < targets.length; i++) {
targets[i].addEventListener('mouseenter', function(event) {
chaser.className = '';
setTimeout(function() {
// at this point, I'm expecting no transition
// to be active on the element
chaser.style.top = event.target.offsetTop + "px";
setTimeout(function() {
// at this point, I'm expecting the element to
// have finished moving to its new position
chaser.className = 'active';
}, 0);
}, 0);
});
}
#chaser {
position: absolute;
opacity: 0;
}
#chaser.active {
transition: all 1s;
opacity: 1;
}
.target {
height: 30px;
width: 30px;
margin: 10px;
background: #ddd;
}
<div id="chaser">o</div>
<div class="target">x</div>
<div class="target">x</div>
<div class="target">x</div>
<div class="target">x</div>
<div class="target">x</div>
What you need to listen for is transitionend event before doing anything else. You can read up on MDN about transitionend event. Btw, setTimeout should never be used to guarantee timing.
EDIT: This is for reference after clarification from OP. Whenever a style change occurs to an element, there is either a reflow and/or repaint. You can read more about them here. If the second setTimeout is ran before the first reflow, then you get the sliding effect. The reason why 10ms will lead to the desired effect is because .active class is added after the offsetTop property has been adjusted (leading to transition property applied after the element has changed it's offsetTop). Usually, there are 60fps (i.e: ~16ms per frame) which means that you have a 16 ms window to do anything before the new styles are applied. This is why a small delay of 5ms will sometimes lead different results.
TL:DR - The browser asks JS and CSS every 16ms for any updates, and calculates what to draw. If you miss the 16ms window, you can have completely different results.
You are calling a setTimeout() timing method inside another setTimeout() timing method.
My thought is, why not call the both setTimeout() method separately like so:
The first setTimeout() method should execute first, then at end of execution, it should call the second setTimeout() method.
Here is a working script for moving the chaser:
function _( id ) { return document.getElementById( id ); }
window.addEventListener( 'load', function() {
var targets = document.querySelectorAll('.target');
var chaser = document.querySelector('#chaser');
setTopPosition( targets );
function setTopPosition( targets ) {
for (var i = 0; i < targets.length; i++) {
targets[i].addEventListener('mouseenter', function(event) {
chaser.className = '';
_( 'status' ).innerText = chaser.className; // to inspect the active class
setTimeout(function() {
/* at this point, I'm expecting no transition // to be active on the element */
chaser.style.top = event.target.offsetTop + "px";
}, 0);
// check if charser.className == ''
if ( chaser.className == '') {
setClassName();
} else {
alert( 0 );
}
}); // addEventListener
} // for
} //function setTopPosition( targets )
function setClassName() {
setTimeout(function() {
/* at this point, I'm expecting the element to have finished moving to its new position */
chaser.className = 'active';
_( 'status' ).innerText = chaser.className;
}, 0);
} // function setClassName()
});
HTML:
<div id="chaser">o</div>
<div class="target">x</div>
<div class="target">x</div>
<div class="target">x</div>
<div class="target">x</div>
<div class="target">x</div>
<div id="status">status</div>
CSS:
#chaser { position: absolute; opacity: 0; }
#chaser.active { transition: all 1s; opacity: 1; }
.target { height: 30px; width: 30px; margin: 10px; background: #ddd; }
This code sample works, it does not work with animations of the same duration instead of just an alert.
<script>
var timeoutOne = null,
timeoutTwo = null,
timeoutThree = null;
function alertOne() {
alert('first message');
timeoutOne = setTimeout(alertTwo, 3000);
}
function alertTwo() {
alert('second message');
timeoutTwo = setTimeout(alertThree, 1000);
}
function alertThree() {
alert('third message');
timeoutThree = setTimeout(alertFour, 1000);
}
function alertFour() {
alert('fourth message');
}
function startCountdown() {
alertOne();
}
function stopCountdown() {
clearTimeout(timeoutOne);
clearTimeout(timeoutTwo);
clearTimeout(timeoutThree);
}
</script>
I intended to have multiple links but I'm limited to two. The first link is the working demo of the project. The second is all of the code discussed in the question
working-demo
different-code
I'm working on this project that involves animation and re-calculation of elements/repositioning in the event of a rescale and I can't seem to get it to work. I've been working on this for over a month now, and I've tried at least 20 different iterations.
Note: the links are highlighted code (except for the actual demo of the interactive display).
The timeout-testing link is a short demonstration of four, sequential-alerts being stopped by a single button. This is the effect I'm trying to accomplish with regard to the animation. note that this is just code, not an actual alert/button interface
The shortened-script is the specific problematic-code taken out of the problematic script.
My solution so far is to use three scripts, the calculated/positioned/animated elements are removed, re-added, the three main functions of setup,position,dimensions are recalled as if it was a new page... but I have three scripts so it breaks after the fourth re-scale while the animation is running. I've tried to switch back and forward between two scripts but it doesn't work going from script 2 to 1.
The alert example and shortened-script is my new attempt which tries not to use the three scripts method done in the working demo.
I haven't tried promises/deferred yet. I'm wondering if I'm missing something obvious.
The three scripts used in the working demo are first,second,third-script respectively.
They are pretty much copies of each other with minor differences.
The animationend event is more reliable for what I think you're trying to do. What's nice is that you don't have to try and time the exact moment an animation finishes; you instead rely on the browser. Have a look at this example.
var step_one = document.querySelector('.step.one');
var step_two = document.querySelector('.step.two');
function stepOneHandler() {
step_two.classList.add('go');
}
step_one.classList.add('go');
step_one.addEventListener('animationend', stepOneHandler);
#keyframes move-one-right {
to { transform: translateX(3em); }
}
#keyframes move-two-right {
to { transform: translateX(6em); }
}
.step {
background: red;
display: block;
width: 3em;
height: 1.5em;
margin-bottom: 1em;
}
.step.one.go {
animation: 1s move-one-right forwards;
}
.step.two.go {
animation: 1s move-two-right forwards;
}
<div class="step one"></div>
<div class="step two"></div>
There are a bunch of div elements on the page.
I have a nested div inside of them.
I want to be able to add a class to the clicked element, and .show() the child div.
$('.container').on('click', function(){
$(this).toggleClass('red').children('.insideItem').slideToggle();
});
I can click on it, it drops down.
Click again, it goes away.
So, now I need some method to removeClass() and slideUp() all of the other ones in the event of a click anywhere except the open div. Naturally, I tried something like this:
$('html').on('click', function(){
$('.container').removeClass('red').children('div').slideUp();
});
Well, that just stops the effect from staying in the first place. I've read around on event.Propagation() but I've read that should be avoided if possible.
I'm trying to avoid using any more prebuilt plugins like accordion, as this should be a pretty straightforward thing to accomplish and I'd like to know a simple way to make it work.
Would anyone be able to show a quick example on this fiddle how to resolve this?
Show only one active div, and collapse all others if clicked off
https://jsfiddle.net/4x1Lsryp/
One way to go about it is to update your code with the following:
1) prevent the click on a square from bubbling up to the parent elements
2) make sure to reset the status of all the squares when a new click is made anywhere.
$('.container').on('click', function(){
$this = $(this);
$('.container').not($this).removeClass('red').children('div').slideUp();
$this.toggleClass('red').children('div').slideToggle();
return false;
});
See the updated JSfiddle here: https://jsfiddle.net/pdL0y0xz/
You need to combine your two approaches:
for (var i = 0; i < 10; i++) {
$('#wrap').append("<div class='container'>" + i + "<div class='insideDiv'>Inside Stuff</div></div>");
}
$('.container').on('click', function() {
var hadClassRed = $(this).hasClass('red');
$('.container').removeClass('red').children('div').slideUp();
if (!hadClassRed) {
$(this).toggleClass('red').children('div').slideToggle();
}
});
.container {
width: 200px;
height: 200px;
float: left;
background: gray;
margin: 1em;
}
.insideDiv {
display: none;
}
.red {
background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="wrap"></div>
I have an hidden div, and I want to show the hidden div only when user's mouse over another a trigger element for several seconds instead show the hidden div once the user hover the trigger element
here is my javascript code
$('.c_like_icon').mouseover(
function() {
var timeout = setTimeout(function(){
var comment_id=$(this).attr('data-commentId');
$.ajax({
url: 'ajax_c_like_user.php',
method:'post',
data:{comment_id:comment_id},
success:function(data){
var like_num=$('#'+comment_id+'c_like_number').text();
if(like_num>=1){
$('#'+comment_id+'like_user_w').html(data);
$('#'+comment_id+'like_user_w').show();
}
else{
$('#'+comment_id+'like_user_w').hide();
}
}
})
}, 2000); //2 seconds
},
function(){
var comment_id=$(this).attr('data-commentId');
clearTimeout(timeout); //cancel the timeout if they hover off
$('#'+comment_id+'like_user_w').hide();
// do stuff when hover off
}
)
define a timeout in your hover in function and clear in the hover out function, to prevent it being fired if they leave before the time runs out, like this:
var timeout;
$('#trigger').hover(
function() {
timeout = setTimeout(function(){
// do stuff on hover
$('#hiddenDiv').show();
}, 2000); //2 seconds
},
function(){
clearTimeout(timeout); //cancel the timeout if they hover off
// do stuff when hover off
$('#hiddenDiv').hide();
}
);
You can very easily do this CSS only. No jquery is required which presents a huge benefit as it is a big library to download.
Just use delayed transitions. Here is my example (live demo here: http://codepen.io/anon/pen/jbGhi):
HTML
<div id="first"></div>
<div id="second"></div>
In this example, the ids are not necessary but I find it better to understand what happens.
CSS
for the purpose of this example, I'll stylize the divs (to make the hover effect more obvious) but none of the following really matters:
div{
height: 50vmin;
width: 50vmin;
border: solid 5px black;
float: left;
margin-right: 10vmin;
}
and this is where the magic happens:
div#first:hover ~ div#second{
transition: all 0.2s ease 1s;
background-color: green;
}
We are using the css selector "~" that means "any sibling element after (and their children)". In that example it means "a div called #second that is sibling and after a div called #first that is hovered". Basically, as long as the second div is a sibling and after or contained within a sibling (that is after) of the first one, you'll get the desired effect.
And there you go. You can add more delay (change "1s" to whatever duration) before the change occurs, and you can smoothen the transition itself (change "0.2s" to whatever duration).
PS: in the CSS, don't forget to add all vendor prefixes for transition and transform. Always check caniuse.com to know which prefixes are needed. Example:
-webkit-transition: all 1s;
transition: all 1s;
i know its an old question, but i think it should have a vanilla solution
// Element will be the triggerer
let timeOut;
element.addEventListener('mouseover', (e) => {
timeOut = setTimeout(() => {
// Do your stuff here
}, 400);
});
element.addEventListener('mouseout', (e) => {
clearTimeout(timeOut);
});
Pleasantries
I've been playing around with this idea for a couple of days but can't seem to get a good grasp of it. I feel I'm almost there, but could use some help. I'm probably going to slap myself right in the head when I get an answer.
Actual Problem
I have a series of <articles> in my <section>, they are generated with php (and TWIG). The <article> tags have an image and a paragraph within them. On the page, only the image is visible. Once the user clicks on the image, the article expands horizontally and the paragraph is revealed. The article also animates left, thus taking up the entire width of the section and leaving all other articles hidden behind it.
I have accomplished this portion of the effect without problem. The real issue is getting the article back to where it originally was. Within the article is a "Close" <button>. Once the button is clicked, the effect needs to be reversed (ie. The article returns to original size, only showing the image, and returns to its original position.)
Current Theory
I think I need to retrieve the offset().left information from each article per section, and make sure it's associated with its respective article, so that the article knows where to go once the "Close" button is clicked. I'm of course open to different interpretations.
I've been trying to use the $.each, each(), $.map, map() and toArray() functions to know avail.
Actual Code
/*CSS*/
section > article.window {
width:170px;
height:200px;
padding:0;
margin:4px 0 0 4px;
position:relative;
float:left;
overflow:hidden;
}
section > article.window:nth-child(1) {margin-left:0;}
<!--HTML-->
<article class="window">
<img alt="Title-1" />
<p><!-- I'm a paragraph filled with text --></p>
<button class="sClose">Close</button>
</article>
<article class="window">
<!-- Ditto + 2 more -->
</article>
Failed Attempt Example
function winSlide() {
var aO = $(this).parent().offset()
var aOL = aO.left
var dO = $(this).offset()
var dOL = dO.left
var dOT = dO.top
var adTravel = dOL-aOL
$(this).addClass('windowOP');
$(this).children('div').animate({left:-(adTravel-3)+'px', width:'740px'},250)
$(this).children('div').append('<button class="sClose">Close</button>');
$(this).unbind('click', winSlide);
}
$('.window').on('click', winSlide)
$('.window').on('click', 'button.sClose', function() {
var wW = $(this).parents('.window').width()
var aO = $(this).parents('section').offset()
var aOL = aO.left
var pOL = $(this).parents('.window').offset().left
var apTravel = pOL - aOL
$(this).parent('div').animate({left:'+='+apTravel+'px'},250).delay(250, function() {$(this).animate({width:wW+'px'},250); $('.window').removeClass('windowOP');})
$('.window').bind('click', winSlide)
})
Before you go scratching your head, I have to make a note that this attempt involved an extra div within the article. The idea was to have the article's overflow set to visible (.addclass('windowOP')) with the div moving around freely. This method actually did work... almost. The animation would fail after it fired off a second time. Also for some reason when closing the first article, the left margin was property was ignored.
ie.
First time a window is clicked: Performs open animation flawlessly
First time window's close button is clicked: Performs close animation flawlessly, returns original position
Second time SAME window is clicked: Animation fails, but opens to correct size
Second time window's close button is clicked (if visible): Nothing happens
Thank you for your patience. If you need anymore information, just ask.
EDIT
Added a jsfiddle after tinkering with Flambino's code.
http://jsfiddle.net/6RV88/66/
The articles that are not clicked need to remain where they are. Having problems achieving that now.
If you want to go for storing the offsets, you can use jQuery's .data method to store data "on" the elements and retrieve it later:
// Store offset before any animations
// (using .each here, but it could also be done in a click handler,
// before starting the animation)
$(".window").each(function () {
$(this).data("closedOffset", $(this).position());
});
// Retrieve the offsets later
$('.window').on('click', 'button.sClose', function() {
var originalOffset = $(this).data("originalOffset");
// ...
});
Here's a (very) simple jsfiddle example
Update: And here's a more fleshed-out one
Big thanks to Flambino
I was able to create the effect desired. You can see it here: http://jsfiddle.net/gck2Y/ or you can look below to see the code and some explanations.
Rather than having each article's offset be remembered, I used margins on the clicked article's siblings. It's not exactly pretty, but it works exceptionally well.
<!-- HTML -->
<section>
<article>Click!</article>
<article>Me Too</article>
<article>Me Three</article>
<article>I Aswell</article>
</section>
/* CSS */
section {
position: relative;
width: 404px;
border: 1px solid #000;
height: 100px;
overflow:hidden
}
article {
height:100px;
width:100px;
position: relative;
float:left;
background: green;
border-right:1px solid orange;
}
.expanded {z-index:2;}
//Javascript
var element = $("article");
element.on("click", function () {
if( !$(this).hasClass("expanded") ) {
$(this).addClass("expanded");
$(this).data("originalOffset", $(this).offset().left);
element.data("originalSize", {
width: element.width(),
height: element.height()
});
var aOffset = $(this).data("originalOffset");
var aOuterWidth = $(this).outerWidth();
if(!$(this).is('article:first-child')){
$(this).prev().css('margin-right',aOuterWidth)
} else {
$(this).next().css('margin-left',aOuterWidth)
}
$(this).css({'position':'absolute','left':aOffset});
$(this).animate({
left: 0,
width: "100%"
}, 500);
} else {
var offset = $(this).data("originalOffset");
var size = $(this).data("originalSize");
$(this).animate({
left: offset + "px",
width: size.width + "px"
}, 500, function () {
$(this).removeClass("expanded");
$(this).prev().css('margin-right','0')
$(this).next().css('margin-left','0')
element.css({'position':'relative','left':0});
});
}
});