I'm trying to execute an async function twice one after another. But the problem is that they are executing together. But, what I want is that the 2nd execution must be start after the execution of first is completed.
PS, there are many questions on SO, with same title, like this one JavaScript: execute async function one by one. But none of them solve my problem.
Please, check the Fiddle
$(document).ready(function() {
function typeWriter(element, msg, speed, index = 0) {
if (index < msg.length) {
$(element).html($(element).html() + msg.charAt(index++));
setTimeout(function() {
typeWriter(element, msg, speed, index);
}, speed);
}
}
$('.intro').fadeIn(function() {
const greet = new Promise(function(resolve, reject) {
typeWriter('#sayHello', "Hello !!", 200);
resolve();
});
greet.then(function() {
typeWriter('#myName', "I'm learning programming", 200);
});
});
});
.intro {
text-align: center;
display: none;
font-family: 'Amaranth', sans-serif;
}
.cursor {
display: inline-block;
width: 10px;
height: inherit !important;
}
.cursor>span {
display: inline-block;
position: relative;
top: 2px;
width: 3px;
height: 26px;
margin: 0 0 0 0px;
background-color: #000;
animation: blink 1s step-end infinite;
}
#keyframes blink {
from,
to {
opacity: 1;
}
50% {
opacity: 0;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="intro">
<h1 class="title"><span id="sayHello"></span><span class="cursor"><span></span></span>
</h1>
<h1 class="title"><span id="myName"></span><span class="cursor"><span></span></span>
</h1>
</div>
Here, I'm trying to print two lines of text inside two different <h1> tags. For that, I'm calling function typeWriter(...) twice, once for each <h1>. I'm trying to print two lines, one after completion of previous one, i.e., print Hello !! in first call and after it finishes then print I'm Learning Programming in second line, but it is not happening. The problem is they are executing together.
On further searching for the solution, I found this question JavaScript: execute async function one by one, and follow answer given by #DavidHedlund, but It doesn't work for me. Sorry, if I'm making any mistake.
Please take a look of the fiddle that I tried from #DavidHedlund answer. In this case, only Hello !! is printed.
$(document).ready(function() {
function typeWriter(element, msg, speed, index = 0) {
if (index < msg.length) {
$(element).html($(element).html() + msg.charAt(index++));
setTimeout(function() {
typeWriter(element, msg, speed, index);
}, speed);
}
}
$('.intro').fadeIn(function() {
function executeTasks() {
var tasks = Array.prototype.concat.apply([], arguments);
var task = tasks.shift();
task(function() {
if (tasks.length > 0)
executeTasks.apply(this, tasks);
});
}
function createTask(param) {
let element = param[0];
let msg = param[1];
let speed = param[2];
return function(callback) {
setTimeout(function() {
if (typeof callback == 'function') typeWriter(element, msg, speed);
}, 1);
}
}
var t1 = createTask(['#sayHello', "Hello !!", 200]);
var t2 = createTask(['#myName', "I'm Anshuman Gupta", 200]);
executeTasks(t1, t2);
});
});
.intro {
text-align: center;
display: none;
font-family: 'Amaranth', sans-serif;
}
.cursor {
display: inline-block;
width: 10px;
height: inherit !important;
}
.cursor>span {
display: inline-block;
position: relative;
top: 2px;
width: 3px;
height: 26px;
margin: 0 0 0 0px;
background-color: #000;
animation: blink 1s step-end infinite;
}
#keyframes blink {
from,
to {
opacity: 1;
}
50% {
opacity: 0;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="intro">
<h1 class="title"><span id="sayHello"></span><span class="cursor"><span></span></span>
</h1>
<h1 class="title"><span id="myName"></span><span class="cursor"><span></span></span>
</h1>
</div>
Thank you!!
So, whats happening here is, the typeWriter() method is using a setTimeout(), which works on a threaded model (that is setTimeout() will not block the thread, instead will return immediately). That is why, the typeWriter() method returns immediately, and therefore the corresponding promise resolves.
Try this code
$(document).ready(function() {
function typeWriter(element, msg, speed) {
return new Promise (function (resolve, reject) {
var recursive = function (element, msg, speed, index) {
if (index < msg.length) {
$(element).html($(element).html() + msg.charAt(index++));
setTimeout(function() {
recursive(element, msg, speed, index);
}, speed)
} else {
resolve();
}
}
recursive(element, msg, speed, 0)
})
}
$('.intro').fadeIn(function() {
typeWriter('#sayHello', "Hello !!", 200).then(function() {
return typeWriter('#myName', "I'm learning programming", 200);
});
});
})
Alternatively, you could use await if that seems simpler for you.
$(document).ready(function() {
function typeWriter(element, msg, speed, index = 0) {
return new Promise((resolve, reject) => {
if (index < msg.length) {
$(element).html($(element).html() + msg.charAt(index++));
setTimeout(function() {
typeWriter(element, msg, speed, index).then(resolve).catch(reject);
}, speed);
} else {
resolve();
}
});
}
async function typeStuff() {
console.log("Hi");
await typeWriter('#sayHello', "Hello !!", 200);
await typeWriter('#myName', "I'm learning programming", 200);
}
$('.intro').fadeIn(function() {
typeStuff();
});
});
.intro {
text-align: center;
display: none;
font-family: 'Amaranth', sans-serif;
}
.cursor {
display: inline-block;
width: 10px;
height: inherit !important;
}
.cursor>span {
display: inline-block;
position: relative;
top: 2px;
width: 3px;
height: 26px;
margin: 0 0 0 0px;
background-color: #000;
animation: blink 1s step-end infinite;
}
#keyframes blink {
from,
to {
opacity: 1;
}
50% {
opacity: 0;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="intro">
<h1 class="title"><span id="sayHello"></span><span class="cursor"><span></span></span>
</h1>
<h1 class="title"><span id="myName"></span><span class="cursor"><span></span></span>
</h1>
</div>
Explanation: in your code, you called resolve immediately after calling typeWriter. The issue is that typeWriter does not "block," meaning it sets a timeout to execute code in the future and immediately returns. You can avoid this my making typeWriter a promise. Then, you can either use await (which is cleaner but less supported by browsers) or typeWriter(...).then(function() { /* Do more stuff */ }) (they are essentially equivalent).
The problem is you do not wait for the recursion function to finish and then call resolve.
const greet = new Promise(function(resolve, reject) {
typeWriter('#sayHello', "Hello !!", 200);
resolve();
});
greet.then(function() {
typeWriter('#myName', "I'm learning programming", 200);
});
Try this:
const timeout = (speed) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, speed);
});
}
$(document).ready(function() {
async function typeWriter(element, msg, speed, index = 0) {
if (index < msg.length) {
$(element).html($(element).html() + msg.charAt(index++));
await timeout(speed);
await typeWriter(element, msg, speed, index);
}
}
$('.intro').fadeIn(function() {
const greet = new Promise(async function(resolve, reject) {
await typeWriter('#sayHello', "Hello !!", 200);
resolve();
});
greet.then(function() {
typeWriter('#myName', "I'm learning programming", 200);
});
});
});
Related
I am trying to make a link that's anchored to a heading appear after scrolling down 300px on my website, but my code doesn't seem to work. Does anyone know why?
NOTE-
I am using Bootstrap5 on my website.
I have altered my code based on the replies I got but I'm still facing the issue. This is how my code looks now-
Here is my code -
<a href="#header-title-1" id="customID" class="bottom-0 end-0 quick-anchor-top hide"> <i
class="fa-solid fa-arrow-up"></i></a>
.quick-anchor-top {
font-size: 25px;
padding: 15px 25px 15px 25px;
border-radius: 50px;
color: rgb(0, 0, 0);
background-color: rgba(182, 20, 20, 0.800);
transition: all 0.4s ease;
margin: 20px;
position: fixed;
z-index: 1;
}
.quick-anchor-top:hover {
transition-duration: 0.4s;
color: white;
background-color: rgba(0, 0, 0, 0.800);
}
.quick-anchor-top.show {
display: block;
}
.quick-anchor-top.hide {
display: none;
}
const myID = document.getElementById("customID");
// Reset timeout after each call
const debounce = function (func, duration = 250){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, duration);
};
}
// Call only once per duration
function throttle(func, duration = 250) {
let shouldWait = false
return function (...args) {
if (!shouldWait) {
func.apply(this, args)
shouldWait = true
setTimeout(function () {
shouldWait = false
}, duration)
}
}
}
// Handle scroll Event
const scrollHandler = function() {
const { scrollY } = window;
if ( scrollY >= 300) {
myID.classList.add('show');
myID.classList.remove('hide');
} else {
myID.classList.add('hide');
myID.classList.remove('show');
}
};
window.addEventListener("scroll", throttle(() => scrollHandler()) );
The JavaScript code works properly: the show and hide CSS classes names are appearing. The problem is in the CSS. So, to fix it try the following:
.quick-anchor-top {
font-size: 25px;
padding: 15px 25px 15px 25px;
border-radius: 50px;
color: rgb(0, 0, 0);
background-color: rgba(182, 20, 20, 0.800);
transition: all 0.4s ease;
margin: 20px;
position: fixed;
z-index: 1;
display: none;
}
.quick-anchor-top.show {
display: block;
}
.quick-anchor-top.hide {
display: none;
}
.quick-anchor-top:hover {
transition-duration: 0.4s;
color: white;
background-color: rgba(0, 0, 0, 0.800);
}
When page just loaded
You don't need to set
class="bottom-0 end-0 quick-anchor-top hide"
change a tag to
<a href="#header-title-1" id="customID" > <i
class="fa-solid fa-arrow-up"></i></a>
change your if else to
if (y >= 300) {
myID.className = "quick-anchor-top"
} else {
myID.className = ""
}
That is not the correct way to add or remove classes. Also I would recommend using a debounce or throttle depending on how you need to handle events because a scroll event can run several hundred times in a second.
const myID = document.getElementById("customID");
// Reset timeout after each call
const debounce = function (func, duration = 250){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, duration);
};
}
// Call only once per duration
function throttle(func, duration = 250) {
let shouldWait = false
return function (...args) {
if (!shouldWait) {
func.apply(this, args)
shouldWait = true
setTimeout(function () {
shouldWait = false
}, duration)
}
}
}
// Handle scroll Event
const scrollHandler = function() {
const { scrollY } = window;
if ( scrollY >= 300) {
myID.classList.add('show');
myID.classList.remove('hide');
} else {
myID.classList.add('hide');
myID.classList.remove('show');
}
};
window.addEventListener("scroll", throttle(() => scrollHandler()) );
I have a simple animation code, looks like a console input.
Originally from: https://codepen.io/atunnecliffe/pen/BaZyLR
I modified the splash screen intro into just a console input in my website:
Code:
<script>
//console
var textarea = $('.term');
var text = 'ping life';
var i = 0;
runner();
function runner() {
textarea.append(text.charAt(i));
i++;
setTimeout(
function () {
runner();
}, Math.floor(Math.random() * 1000) + 50);
}
</script>
Now the effect that I want is a bit complex, for me at least, as my knowledge about JQuery is limited. I wanted the code to enter ping life, then backspace completely, repeat infinitely. I looked up on how to simulate backspace in JQuery, using escape sequence of (8), but I am not sure how to use the escape sequence, nor implement the function into the existing recursive function, for it to repeat infinitely.
Any help would be wonderful :)
Like this?
Counting like this will give a zigzag like counting pattern. I added buffers for start and end of input, and a fixed timeout for deleting letters.
textarea.text(text.substr(0, i)) selects a substring of your text (treated as an array of letters - selecting everything between index 0 and i)
Easier than appending and deleting letters
var direction = 1;
var i = 0;
var textarea = $('.term');
var text = 'ping life';
// NOTE:
// I added the "#dev:~$ " as css:before elem, easier to write the code
function count() {
i += direction;
direction *= (((i % text.length) == 0) ? -1 : 1);
textarea.text(text.substr(0, i));
clearInterval(time);
// direction is 1 if counting up
if (direction === 1) {
if (i === 0) {
// buffer for start
time = setInterval(count, 1000);
} else {
time = setInterval(count, Math.floor(Math.random() * 1000) + 50);
}
} else {
// direction is -1 if counting down
if (i === text.length) {
time = setInterval(count, 1500);
} else {
// buffer for end
time = setInterval(count, 100);
}
}
}
// inital interval
// setTimeout doesn't work well here
var time = setInterval(count, 1000)
html,
body {
margin: 0 auto;
height: 100%;
}
pre {
padding: 0;
margin: 0;
}
pre::before {
content: "#dev:~$ ";
color: white;
}
.load {
margin: 0 auto;
min-height: 100%;
width: 100%;
background: black;
}
.term {
font-family: monospace;
color: #fff;
opacity: 0.8;
font-size: 2em;
overflow-y: auto;
overflow-x: hidden;
padding-top: 10px;
padding-left: 20px;
}
.term:after {
content: "_";
opacity: 1;
animation: cursor 1s infinite;
}
#keyframes cursor {
0% {
opacity: 0;
}
40% {
opacity: 0;
}
50% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="load">
<pre class="term"></pre>
</div>
I'm making a simple full viewport scroller. You can change sections by triggering wheel event.
To prevent the eventhandler from firing many times in row and skipping pages, I've added a timer, calculating the difference between date.now() stored in variable and date.now() inside the eventHandler. This happens mostly if you spam scrolling, it makes you have to wait about 3 seconds to scroll again instead of 200ms. How to prevent this from happening?
document.ready = (fn) => {
if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading"){
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
document.ready(() => {
const SECTIONS = document.querySelectorAll('.section');
let current;
let onWheelTimeout = 'poop';
let time = Date.now()
// initialize first section as active
_.first(SECTIONS).classList.add('active');
document.addEventListener('wheel', onWheel)
function goSectionUp() {
const current = document.querySelector('.active');
current.classList.remove('active');
if(current.previousElementSibling) {
current.previousElementSibling.classList.add('active');
} else {
_.last(SECTIONS).classList.add('active');
}
}
function goSectionDown() {
const current = document.querySelector('.active');
current.classList.remove('active');
if(current.nextElementSibling) {
current.nextElementSibling.classList.add('active');
} else {
_.first(SECTIONS).classList.add('active');
}
}
function onWheel(e) {
const now = Date.now()
const diff = now - time;
time = now;
if(diff > 200) {
if(e.deltaY < 0) {
onScroll('up')
} else {
onScroll('down')
}
}
}
function onScroll(direction) {
if(direction === 'up') {
goSectionUp()
} else {
goSectionDown()
}
};
});
html {
box-sizing: border-box;
overflow: hidden;
width: 100%; height: 100%;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
padding: 0; margin: 0;
overflow: hidden;
height: 100%; width: 100%;
position: relative;
}
#page {
width: 100%; height: 100%;
transition: all 1s ease;
transform: none !important;
}
.section {
height: 100vh; width: 100%;
opacity: 0;
visibility: hidden;
position: absolute;
top: 0;
left: 0;
transition: all .7s ease-in-out;
}
.section:nth-of-type(1) {
background-color: red;
}
.section:nth-of-type(2) {
background-color: aquamarine;
}
.section:nth-of-type(3) {
background-color: blueviolet;
}
.section:nth-of-type(4) {}
.active {
opacity: 1; visibility: visible;
}
#button {
position: sticky;
top: 0; left: 100px;
z-index: 1000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<div id="page">
<div class="section">one</div>
<div class="section">two</div>
<div class="section">three</div>
<div class="section">four</div>
</div>
It seems like what you want is a debounce function. I'd recommend using this one, by David Walsh:
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
Usage
var myScrollFunction = debounce(function() {
// All the taxing stuff you do
}, 250);
document.addEventListener('wheel', myScrollFunction);
To answer why your code doesn't work as expected: The mouse wheel produces a series of continuous events while it is scrolling, so your time diff is constantly < 200. Here's an example of it working "properly" (though the best answer is still a true debounce function as stated above).
JSBin example
https://jsbin.com/cuqacideto/edit?html,console,output
I have created functions to type and erase text animation effect in JS. But, I'm stuck as to how to call the erase function only after the type function is fully executed? I've tried setTimeout which didn't seem to work. Help appreciated!
var clength = 0;var caption = 'MyName';var x = 1;var y = x/2;
$(document).ready(function() {
setInterval('cursorAnimation()', 600);
ding = $('#caption');
type();
});
function type() {
ding.html(caption.substr(0, clength++));
if (clength < caption.length + 1) {
setTimeout('type()', 180);
} else {
clength = 0;
caption = '';
}
}
function erase() {
ding.html(caption.substr(0, clength--));
if (clength >= 0) {
setTimeout('erase()', 50);
} else {
clength = 0;
caption = '';
}
}
function cursorAnimation() {
$('#cursor').animate({
opacity: 0
}, 'fast', 'swing').animate({
opacity: 1
}, 'fast', 'swing');
}
Well, there are different ways to do this.
Firstly I must say the way you use setTimeout is not the best. By putting the string expression into it you make the JS engine to parse the expression through eval. A slow and simply dangerous thing that must be evaded as much as possible; except it's really needed.
In this case you can simply pass a function as a parameter of setTimeout and it will work as well. So, setTimeout(type, 180).
Now as I understand your code both type() and erase() are recursive functions that either call themselves or finish their execution after reaching the end. "Finish their execution after reaching the end" is exactly the branch of code you need so you just put there a call of erase.
} else {
clength = 0;
caption = '';
erase();
}
Now maybe you want to save the possibility for your type() function to launch without the mandatory call of erase() in the end. In your case I would encapsulate it in a closure, like this:
function type(callback) {
return function(){
ding.html(caption.substr(0, clength++));
if (clength < caption.length + 1) {
setTimeout('type()', 180);
} else {
clength = 0;
caption = '';
if (callback != undefined) callback();
}
}
}
In this case your type function becomes a first class function, a function that generates other functions. When you call it with type(erase) it writes callback as erase and returns a function whose variable scope contains callback so it can call it as it was its own local variable.
If you call type() it creates a function with callback being undefined so nothing happens in this case.
EDIT:
function type(callback) {
return function(){
ding.html(caption.substr(0, clength++));
if (clength < caption.length + 1) {
setTimeout(type(callback), 180);
} else {
if (callback != undefined) callback();
}
}
}
Here's a CSS-only possible solution:
.caption {
display: inline-block;
box-sizing: content-box;
white-space: nowrap;
overflow: hidden;
font-family: monospace;
border-right: 1px solid;
animation-name: typing, cursor;
animation-duration: 5s, 1s;
animation-iteration-count: infinite;
animation-timing-function: steps(23, end), linear;
}
#keyframes typing {
0% {
width: 0;
}
20% {
width: 0;
}
60% {
width: 165px;
}
80% {
width: 165px;
}
100% {
width: 0;
}
}
#keyframes cursor {
from {
border-right: 1px solid;
}
to {
border-right: 1px transparent;
}
}
<span class="caption">My name is Hritik Gupta</span>
originally I'm using jquery fade in and fade out to do blinking for a fixed number of times like below:
function blink(selector, counter) {
$(selector).fadeOut(500, function() {
$(this).fadeIn(500, function() {
if (counter++ < 10) {
blink(this, counter);
}
});
});
}
Now I want to change it to using jquery to change the background color to do blinking effect. But my coding don't seems to work:
function blink(selector, counter) {
setTimeout(function() {
$(selector).css("background-color", "red");
}, 500);
setTimeout(function() {
$(selector).css("background-color", "black");
}, 500);
if (counter++ < 10) {
blink(this, counter);
}
}
It just blink for once. Anything wrong guys?
I try the below but doesn't work too:
function blink(selector, counter) {
setTimeout(function() {
$(selector).css("background-color", "red", function() {
setTimeout(function() {
$(this).css("background-color", "black", function() {
if (counter++ < 10) {
blink(this, counter);
}
});
}, 1000);
});
}, 1000);
}
Any ideas guys?
You are calling blink recursively but there is no callback for when the timeout finishes so it is adding all of the timeout events at the same time instead of one after the other (well not exactly the same time but you get the idea).
You can try it this way:
function blink(selector, counter) {
setTimeout(function() {
if( $(selector).hasClass('black') ) {
$(selector).removeClass('black').addClass('red');
} else {
$(selector).removeClass('red').addClass('black');
}
if( counter++ < 10 ) { // to get the result you want this number may need to be 20 for 10 blinks
blink(selector, counter);
}
}, 500);
}
I would set the classes black and red to utilize the background color.
UPDATE
Your second example fails because jQuery doesn't accept a callback function as an argument for the css method. So doing the following won't log anything to the console:
$('.my-element').css('background', 'red', function() {
console.log('this will not log anything because jquery does not call this callback function');
});
For background color animation you would probably need jquery-ui library or you could use css animation to do this:
function blink(selector) {
$(selector).addClass('aniBg');
}
blink("div");
.aniBg {
width: 200px;
height: 200px;
background-color: red;
border: solid 1px black;
animation: animeBG 2s 5;
-webkit-animation: colorchange 2s 5;
}
#keyframes animeBG {
0% { background: red; }
100% { background: black; }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>color animation</div>
Fiddle.
$(document).ready(function() {
function blink(selector, counter) {
var t = counter * 1000;
setTimeout(function() {
$(selector).css("background-color", "red");
}, t);
setTimeout(function() {
$(selector).css("background-color", "black");
}, t + 500);
if (counter++ < 10) {
blink(selector, counter);
}
}
blink($('div'), 1);
});
div {
width: 50px;
height: 50px;
border: 1px solid;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
</div>
this may work for your need