I want the second function (blockdone) to run after the animation has finished running in the first function.
I know I can do this by putting blockdone in a callback on the animate method. But for reasons specific to the project I'm working on, I can't do this.
Is there some way to queue functions in someway in javaScript or jQuery?
https://jsfiddle.net/bazzle/mdawnrtq/
function blockmove(){
$('.block').animate({top: '100px'},{duration: 1000} ).animate({width: '0'});
};
function blockdone(){
$('p').text('Done');
};
blockmove();
blockdone();
Under the assumption that you know the selection on which the animations run, you can do this via the jQuery queue function. It literally allows you to specify a function that should be called when all other functions in the queue have completed.
You can specify a name of queue, as more than one queue can be created for a selection, but the default queue this function works on is the queue in which effects (.animate()) are stored. That is precisely what you want, hooray!
function blockmove() {
$('.block').animate({
top: '100px'
}, {
duration: 1000
}).animate({
width: '0'
});
};
function blockdone() {
$('p').text('Done');
};
blockmove();
$('.block').queue(blockdone); // <-- CHANGED
html,
body {
margin: 0;
}
.block {
background-color: red;
width: 100px;
height: 100px;
display: block;
position: relative;
}
p {
position: absolute;
top: 1em;
right: 1em;
text-align: right;
display: block;
margin: 0;
}
<div class="block"></div>
<p></p>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"
integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
crossorigin="anonymous"></script>
Maybe this is not very ambitious, but since you know the duration of your animation, you could use setTimeout function to delay appearing the text?
function blockmove(){
$('.block').animate({top: '100px'}, {duration: 1000}).animate({width: '0'});
setTimeout(() => {
$('p').text('Done');
}, 1400);
};
blockmove();
html, body {
margin: 0;
}
.block {
background-color: red;
width: 100px;
height: 100px;
display: block;
position: relative;
}
p {
position: absolute;
top: 1em;
right: 1em;
text-align: right;
display: block;
margin: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="block">
</div>
<p></p>
I think you recive callback argument.
You have to change code below
function blockmove(callback){
$('.block').animate({top: '100px'},{duration: 1000} ).animate({width: '0'},
function(){
if(typeof callback === "function") callback();
});
};
Related
I'm trying to locate which element newly rendered is under mouse pointer. (*)
Here is my code:
btn.addEventListener('click', function () {
btn.remove();
for (let i = 0; i < 10; i++) {
lst.appendChild(document.createElement('li')).textContent = 'Element ' + i;
}
requestAnimationFrame(function () { requestAnimationFrame(function () {
const chosen = document.querySelector('li:hover');
alert(chosen && 'Your mouse on ' + chosen.textContent); // do something more with chosen
}); });
});
#btn { width: 200px; height: 200px; }
#lst { width: 200px; line-height: 20px; display: block; padding: 0; }
#lst li { display: block; height: 20px; width: 200px; overflow: hidden; }
#lst li:hover { background: #ccc; }
<button id=btn>Click Me</button>
<ul id=lst><ul>
I'm confused that I need 2 requestAnimationFrame to make my code execute correctly. Removing one raf, the alert will show null instead.
The code also seems ugly to me. How to implement it more elegantly?
In case anyone care about: I'm running my code on Firefox. And the code, as a part of my Firefox extension, only need to target to Firefox 60+.
(*): The story behind may be more complex. But to keep it simple...
That's quite an interesting behavior you found here, browsers seem to not update the :hover before that second frame, even if we force a reflow or what else.
Even worse, in Chrome if you hide the <button> element using display:none, it will stay the :hover element until the mouse moves (while normally display:none elements are not accessible to :hover).
The specs don't go into much details about how :hover should be calculated, so it's a bit hard to tell it's a "bug" per se.
Anyway, for what you want, the best is to find that element through the document.elementsFromPoints method, which will work synchronously.
btn.addEventListener('click', function ( evt ) {
btn.remove();
for (let i = 0; i < 10; i++) {
lst.appendChild(document.createElement('li')).textContent = 'Element ' + i;
}
const chosen = document.elementsFromPoint( evt.clientX, evt.clientY )
.filter( (elem) => elem.matches( "li" ) )[ 0 ];
alert(chosen && 'Your mouse on ' + chosen.textContent); // do something more with chosen
});
#btn { width: 200px; height: 200px; }
#lst { width: 200px; line-height: 20px; display: block; padding: 0; }
#lst li { display: block; height: 20px; width: 200px; overflow: hidden; }
#lst li:hover { background: #ccc; }
<button id=btn>Click Me</button>
<ul id=lst><ul>
I cannot exactly answer the question why you need 2 rafs.
But i can provide you an more elegant way with async / await. Create a small function called nextTick that returns an promise. So you await for the next frame.
So you can first wait till the button is gone, create your elemens, then await again for the next painting cycle to be sure the elements are accessible
btn.addEventListener('click', async function () {
btn.remove();
await nextTick();
for (let i = 0; i < 10; i++) {
lst.appendChild(document.createElement('li')).textContent = 'Element ' + i;
}
await nextTick()
const chosen = document.querySelector('li:hover');
alert(chosen && 'Your mouse on ' + chosen.textContent); // do something more with chosen
});
function nextTick() {
return new Promise(requestAnimationFrame)
}
#btn { width: 200px; height: 200px; }
#lst { width: 200px; line-height: 20px; display: block; padding: 0; }
#lst li { display: block; height: 20px; width: 200px; overflow: hidden; }
#lst li:hover { background: #ccc; }
<button id=btn>Click Me</button>
<ul id=lst><ul>
I'm trying to make a game simulation like the Voltage game just for fun, it has 4 main components, including the background, the character image, the character name and the text. I want to create a big function called startStory() which has smaller functions in there to represent different parts of the story.
How it works is that when the user clicks on the game screen the text/character name/image/background will change in order to create a story for the users. But when I tried to create the function startStory() and tried to run the inner function in it nothing happens.
Can someone help me explain why? And do you think that making different parts of the story smaller code a good idea or should I do something else? Here is my code so far
<style>
* {
margin: 0;
padding: 0;
font-family: 'Lora', serif;
}
.container {
top: 10px;
margin: 0 auto;
width: 70vw;
position: relative;
}
.background {
width: 70vw;
position:absolute;
top: 0px;
left: 0px;
z-index: 1;
border: 2px solid black;
}
.character {
width: 15vw;
position:absolute;
left: 400px;
top: 120px;
z-index: 2;
}
.label {
border: 2px solid black;
background-color: white;
padding: 30px;
position:absolute;
top: 400px;
left: 40px;
z-index: 2;
}
.text {
width: 60vw;
border: 2px solid black;
background-color: white;
padding: 30px;
position:absolute;
top: 470px;
left: 40px;
z-index: 2;
text-align: center;
}
</style>
<body>
<div class="container">
<img class="background" src="https://img.wallpapersafari.com/desktop/1920/1080/48/39/DtNh51.jpg">
<img class="character" src="https://www-en.voltage.co.jp/wp-content/uploads/sites/3/2019/07/190704_voltage_press2.jpg">
<div class="label">Leon</div>
<div class="text">Hi there</div>
</div>
</body>
<script>
var container = document.getElementsByClassName('container')[0];
var textHolder = document.getElementsByClassName('text')[0]
container.addEventListener('click', startStory)
function startStory() {
function introduceCharacter() {
textHolder.innerHTML = 'I\'m Leon. What\'s your name?';
}
}
</script>
Keep in mind that nested functions are local. So if you want to call the function introduceCharacter you have to add the event listener inside the scope of the function startStory().
function startStory() {
container.addEventListener('click', introduceCharacter) //local scope.
function introduceCharacter() {
textHolder.innerHTML = 'I\'m Leon. What\'s your name?';
}
}
However a better way is to simply remove the introduceCharacter function:
function startStory() {
textHolder.innerHTML = 'I\'m Leon. What\'s your name?';
}
In short: a function within a function is available in the scope it is declared. Nice game by the way, good luck!
Why is it that if I give a variable a "0", then in the html the number is "10"? I'm using jQuery and JavaScript both, but as you can see the number in the middle is "10" after I reload the page. It's always "10" and not changing.
I'm trying so that that purple square goes in circles 10 times and I want the middle number to change every round up by one. How can I achieve that?
let calc = 0;
for (let i = 0; i < 11; i++) {
$('#animate').click(function() {
$(this).animate({
top: '350px'
});
$(this).animate({
left: '350px'
});
$(this).animate({
top: '0px'
});
$(this).animate({
left: '0px'
});
});
document.getElementById("szam").innerHTML = calc;
calc++;
}
#container {
height: 400px;
width: 400px;
background-color: yellow;
}
#animate {
height: 50px;
width: 50px;
position: absolute;
background-color: rebeccapurple;
}
body {
margin: 0px;
padding: 0px;
}
#szam {
position: absolute;
top: 100px;
left: 170px;
font-size: 72px;
}
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-
2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous">
</script>
<div id="container">
<div id="animate">
<p>Hello</p>
</div>
<h1 id="szam">0</h1>
</div>
Here's a screenshot:
The loop runs relatively quickly, but the animations are queued by default. This means that the code iterates i from 0 to 10 and queues each animation, but displays 10 almost immediately because the loop happens so fast. On the other hand, each animation waits for the previous animation to finish before it starts. So the animation takes much more time to complete than the loop itself.
To demonstrate with the code below, notice that the "Loop is done" message seems to appear as soon as the trigger is clicked.
One solution is to use the complete callback of jQuery's animate to advance the counter when each cycle is complete.
complete
Type: Function()
A function to call once the animation is complete, called once per matched element.
.animate( properties [, duration ] [, easing ] [, complete ] )
var calc = 0;
var elm = document.getElementById("szam");
function advance() {
calc++;
elm.innerHTML = calc;
}
$('#animate').click(function() {
for (var i = 1; i <= 10; i++) {
$(this).animate({
top: '150px'
}).animate({
left: '150px'
}).animate({
top: '0'
}).animate({
left: '0'
}, advance);
}
console.log('Loop is done.');
});
#container {
height: 200px;
width: 200px;
background-color: yellow;
}
#animate {
height: 50px;
width: 50px;
position: absolute;
background-color: rebeccapurple;
cursor: pointer;
color: white;
}
body {
margin: 0px;
padding: 0px;
}
#szam {
position: absolute;
top: 15px;
left: 85px;
font-size: 72px;
}
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-
2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous">
</script>
<div id="container">
<div id="animate">
<p>CLICK</p>
</div>
<h1 id="szam">0</h1>
</div>
I'm trying to use jquery to animate a series of words I'm trying to just get one word to roll down into view then stay for a half second and then disappear out of view by rolling down (like a lotto machine).
The overflow for the div is hidden so the words are out of view at top: -20 and at top: 20 but in view around top 5 or top 0. But each time the setInterval runs it displays the .rw-word at a different location, also the timing seems to be off....
Here's what I have so far:
html :
<div id="login-modal" class="modal">
<section class='rw-wrapper'>
<h3 class='rw-sentance'>LOG IN TO START
<div class="rw-words">
<span class="rw-word">COLLECTING</span>
</div>
</h3>
</section>
css:
.rw-wrapper {
width: 80%;
position: relative;
padding: 10px;
}
.rw-sentance {
overflow: hidden;
height: 21px;
}
.rw-sentance span {
white-space: nowrap;
}
.rw-words {
display: inline;
text-indent: 10px;
font-family: 'Permanent Marker', cursive;
position: relative;
}
.rw-words span {
opacity: 1;
max-width: 40%;
color: #F58835;
font-size: 25px;
letter-spacing: 2px;
position: absolute;
top: -25px;
}
javascript:
$(document).on('click', '#account-login', function (){
wordScroll();
});
setInterval(wordScroll, 2000);
function wordScroll() {
$('.rw-word').delay(200).animate({ top: '0'}, 100,function(){
$('.rw-word').delay(4000).animate({ top: '25'}, 100,function(){
$('.rw-word').css('top','-20px');
});
});
}
Fiddle
I think you should place setInterval(wordScroll, 2000); withing the click event.
$(document).on('click', '#account-login', function (){
//wordScroll();
setInterval(wordScroll, 2000);
});
function wordScroll() {
$('.rw-word').delay(200).animate({ top: '0'}, 100,function(){
$('.rw-word').delay(4000).animate({ top: '25'}, 100,function(){
$('.rw-word').css('top','-20px');
});
});
}
So I have this drop down menu that is supposed to have slightly delayed drop on hover, but it doesn't drop at all.
My HTML:
<div class="container">
<ul class="menu">
<li class="overflow-hidden">one
<div class="submenu">test</div>
</li>
<li class="overflow-hidden">two</li>
<li class="overflow-hidden">three</li>
</ul>
</div>
CSS
.menu {
display: block;
width: 100%;
margin: 0;
padding: 0;
}
.overflow-hidden {
display: inline-block;
width: 33%;
height: 20px;
text-align: center;
overflow: hidden;
cursor: pointer;
}
.submenu {
display: block;
height: 200px;
background-color: #999;
}
.container {
width: 100%;
}
What have I missed?
There are a few things you can do to improve your code and make it work:
Document Ready event. you should initialize your code after the DOM has rendered, otherwise your code may be trying to attach events to things that aren't there yet!
$(document).ready(function(){
menuHover();
$('.submenu').width(menuWidth);
});
Scope. Referring to $(this) inside the timer object will not be referring to what you think! Define the element you want to refer to at the top of your function, and you can then safely use this explicit definition in any functions defined within the same scope, and you won't have to worry about their own 'this' being something different.
function () {
var $listItem = $(this);
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(function () {
$listItem.css('overflow', 'visible');
}, 200);
}
Semantics. Naming your list items overflow-hidden is semantically bad practice (that is a style not a name!) ... especially when the item is in the overflow-visible state!. It would be advisable to probably remove this altogether and target your list items by something like .menu li or giving them their own class eg. menu-item.
var menuWidth = $('.container').width();
var menuHover = function () {
var timer;
$(".menu li").hover(
function () {
var $listItem = $(this);
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(function () {
$listItem.css('overflow', 'visible');
}, 200);
},
function () {
var $listItem = $(this);
clearTimeout(timer);
timer = null;
$listItem.css('overflow', 'hidden');
});};
$(document).ready(function(){
menuHover();
$('.submenu').width(menuWidth);
});
You got two things totally wrong.
First: You're missing the jQuery ready event1.
Second: You didn't think about the scope2. $(this) is not available in setTimeout();
$(function(){ // (1a) jQuery ready start
var menuWidth = $('.container').width();
var menuHover = function(){
var timer;
$(".overflow-hidden").hover(
function(){
if(timer){
clearTimeout(timer);
timer = null;
}
var temporary_element = $(this); // (2a) Store element in temporary variable
timer = setTimeout(function(){
temporary_element.css('overflow', 'visible'); // (2b) Get the stored element from the parent scope
}, 200);
},
function(){
clearTimeout(timer);
timer = null;
$(this).css('overflow', 'hidden');
}
);
};
menuHover();
$('.submenu').width(menuWidth);
}); // (1b) jQuery ready end
CSS3 Transitions
With the use of transition (-webkit,-moz,-ms) you don't even need javascript.
You can use the class or id to control sub elements.
CSS
#menu{
}
#menu>div{
width:33%;
float:left;
height:20px;
background-color:grey;
}
#menu>div>div:nth-child(1){
line-height:20px;
text-align:center;
}
#menu>div>div:nth-child(2){
overflow:hidden;
height:0px;
transition:height 700ms ease 500ms;
/*
the duration is 700milliseconds and the delay is 500milliseconds
*/
background-color:#cc2;
padding:0 16px;
}
#menu>div:hover>div:nth-child(2){
height:20px;
}
HTML
<div id="menu">
<div><div>one</div><div>1</div></div>
<div><div>two</div><div>2</div></div>
<div><div>three</div><div>3</div></div>
</div>
DEMO
http://jsfiddle.net/95wM2/
.menu {
display: block;
width: 100%;
margin: 0;
padding: 0;
background-color: green;
}
.overflow-hidden {
display: inline-block;
width: 33%;
height: 20px;
text-align: center;
position: relative;
cursor: pointer;
}
.submenu {
display: none;
}
.overflow-hidden:hover .submenu {
display: inline-block;
height: 200px;
background-color: #999;
z-index: 1;
width: 100%;
position: absolute;
left: 0;
top: 20px;
}
.container {
width: 100%;
}
you do not need jquery for this, you can use the pseudo class :hover
See this fiddle: http://jsfiddle.net/yA6Lx/14/
.overflow:hover .submenu{
visibility: visible;
opacity: 1;
}