My intention is very simple: I have a newSpan() function, that creates a new span and inserts it before the first child of the main element (or appends it if main is empty), and a letterTyper() function, that fills the inserted element with the string stored in a CONTENT constant while generating a sort of backwards typing effect. The idea is to create a new element and then triggering the effect inside it a given number of times with a loop in window.onload.
But when I fill the window.onload anonymous function with
window.onload = function() {
newSpan();
letterTyper();
newSpan();
letterTyper();
}
What I end up with in the DOM is
<main>
<span id="#span1">lorem ipsum</span>
<span id="#span0"></span>
</main>
I also tried to use a DOMContentLoaded event, to no effect. Why is that? What am I doing wrong?
My code:
const CONTENT = 'lorem ipsum';
var main = document.querySelector('main');
var spanId = 0;
var charCount = CONTENT.length;
window.onload = function() {
newSpan();
letterTyper();
newSpan();
letterTyper();
}
function newSpan() {
var newSpan = document.createElement('span');
var isEmpty = main.innerHTML === '';
newSpan.setAttribute('id', '#span' + spanId);
if (isEmpty) {
main.appendChild(newSpan);
} else {
main.insertBefore(newSpan, document.getElementById('#span' + (spanId - 1)));
}
spanId++;
}
function letterTyper() {
var targetSpan = main.firstChild;
targetSpan.textContent = CONTENT.substring(charCount, charCount + 1) + targetSpan.textContent.substring(0, targetSpan.textContent.length);
charCount--;
if ( charCount < 0) {
clearTimeout(timer);
charCount = CONTENT.length;
} else {
timer = setTimeout('letterTyper()', 100);
}
}
setTimeout is asynchronous. When you call letterTyper for the first time, it will return to your window.onload and trigger the next newSpan() and letterTyper().
That means that the letterTyper loop of the first span interferes with the second loop, because the first loop hasn't finished when the second one is started. In other words, the second loop is never created, but all calls of letterTyper will see the #span1 element (the second span element) as the first child of main.
You have to wait until the first loop is finished before you start the second loop. I suggest you take a look at Promises.
I changed a few things in your code so that is now uses promises:
const CONTENT = 'lorem ipsum';
var main = document.querySelector('main');
var spanId = 0;
var charCount = CONTENT.length;
window.onload = function() {
// start the first loop
letterTyper(newSpan()).then(function () {
// the first loop has finished, start the second loop
return letterTyper(newSpan());
}).then(function () {
// the second loop has finished, start the third loop
return letterTyper(newSpan());
});
}
function newSpan() {
var newSpan = document.createElement('span');
var isEmpty = main.children.length === 0;
newSpan.setAttribute('id', '#span' + spanId);
if (isEmpty) {
main.appendChild(newSpan);
} else {
main.insertBefore(newSpan, document.getElementById('#span' + (spanId - 1)));
}
spanId++;
charCount = CONTENT.length; // reset the character count
return newSpan;
}
function letterTyper(span) {
return new Promise(function (resolve, reject) {
// executes one tick (adds one character and calls itself after 100ms)
var tick = function () {
// prepend a new character
span.textContent = CONTENT[charCount -= 1] + span.textContent;
if (charCount > 0) {
// call tick again in 100ms
setTimeout(tick, 100);
} else {
// loop is finished, the "then" part will be called
resolve();
}
};
// start the loop
setTimeout(tick, 100);
});
}
<main></main>
Related
Page 1:
function tempWindow(text)
{
var w = window.open('https://example.com/?data='+text, '_blank');
var t = setInterval(() => {
if(w.closed)
{
clearInterval(t);
return true;
}
}, 100);
}
var list = ['text1', 'text2', 'text3'];
for(var i=0;i<list.length;i++)
{
tempWindow(list[i]);//Each loop iteration must wait for each window to close
}
Temporary window:
(function(){
setTimeout(() => {
window.close();
}, 5000);
})();
The code on page 1 should open three windows and send to each of them the strings text1, text2, text3, stored in the variable data.
These windows must not be opened at the same time. For this, setInterval checks every 100 milliseconds if the previous window has already been closed.
Therefore, the second window can only be opened after closing the first one and so on.
I believe that the tempWindow function could return a promise and that using await a solution would be possible, but I don't know how to do that.
use that code. it will wait until window is close
function tempWindow(text) {
return new Promise(resolve => {
var w = window.open('https://example.com/?data=' + text, '_blank');
var t = setInterval(() => {
if (w.closed) {
clearInterval(t);
resolve(true);
}
}, 100);
})
}
var list = ['text1', 'text2', 'text3'];
for (var i = 0; i < list.length; i++) {
await tempWindow(list[i]);//Each loop iteration must wait for each window to close
}
make sure loop code is inside async function like that
async testFunction(){
var list = ['text1', 'text2', 'text3'];
for (var i = 0; i < list.length; i++) {
await tempWindow(list[i]);//Each loop iteration must wait for each window to close
}
}
call function like this testFunction()
I am building a website and the homepage will basically have 2 div's containing text. I want one of the divs to change every 2 seconds with values I've placed in an array
var skills = ["text1","text2","text3","text4"];
var counter = 0;
var previousSkill = document.getElementById("myGreetingSkills");
var arraylength = skills.length - 1;
function display_skills() {
if(counter === arraylength){
counter = 0;
}
else {
counter++;
}
}
previousSkill.innerHTML = skills[counter];
setTimeout(display_skills, 2000);
innerHTML is evil, use jQuery! (assuming because you have it selected as a tag)
Working fiddle
(function($) {
$(function() {
var skills = ["text1","text2","text3","text4"],
counter = skills.length - 1,
previousSkill = $("#myGreetingSkills"),
arraylength = skills.length - 1;
function display_skills() {
if (counter === arraylength) {
counter = 0;
}
else {
counter++;
}
previousSkill.html(skills[counter]);
}
display_skills();
setInterval(function() {
display_skills();
}, 2000);
});
})(jQuery);
You need to wrap display_skills inside a function in your setTimeout() and use setInterval() instead.
var myInterval = setInterval(function(){display_skills()}, 2000);
And make sure you call previousSkill.innerHTML = skills[counter]; inside your interval'd function - else it will run just once.
You can terminate your interval with window.clearInterval(myInterval);
Use array.join().
The syntax of this function is array.join(string), where3 string is the joining character.
Example:
[1, 2, 3].join(" & ") will return "1 & 2 & 3".
Hope this helps,
Awesomeness01
So I'm new to javascript and I am looking for a way to count how many times a function is executed. The code randomly generates a square or circle and displays from the shape is shown to when you click it (reactionTime). That works fine and dandy.
But I'm looking for a way to keep track of the number of times a shape is clicked and then eventually the cumulative time to calculate average time per click. If it helps, I come from a pretty good C++ background.
To count number of clicks, I was thinking of adding a closure function.
From here: How do I find out how many times a function is called with javascript/jquery?
myFunction = (function(){
var count = 0;
return function(){
count++
alert( "I have been called " + count + " times");
}
})();
And from here: Function count calls
var increment = function() {
var i = 0;
return function() { return i += 1; };
};
var ob = increment();
But I tried a global variable and several variations of closure functions to no avail (look for the comments). I tried putting the closure function in other functions. And I also tried something like:
var increment = makeBox();
I'm wondering if anyone can guide me in the right direction. It would be much appreciated!
var clickedTime; var createdTime; var reactionTime;
var clicked; var avg = 0;
avg = (avg + reactionTime) / clicked;
document.getElementById("clicked").innerHTML = clicked;
document.getElementById("avg").innerHTML = avg;
function getRandomColor() {
....
}
function makeBox() { // This is the long function that makes box
createdTime = Date.now();
var time = Math.random();
time = time * 3000;
///////// var increment = function () {
var i = 0;
//return function() { return i += 1; };
i++;
return i;
///////// };
// clicked++; /////////// global variable returns NaN
// console.log(clicked);
// alert("Clicked: "+clicked);
setTimeout(function() {
if (Math.random() > 0.5) {
document.getElementById("box").style.borderRadius="75px"; }
else {
document.getElementById("box").style.borderRadius="0px"; }
var top = Math.random(); top = top * 300;
var left = Math.random(); left = left * 500;
document.getElementById("box").style.top = top+"px";
document.getElementById("box").style.left = left+"px";
document.getElementById("box").style.backgroundColor = getRandomColor();
document.getElementById("box").style.display = "block";
createdTime = Date.now();
}, time);
}
ob = increment(); //////////////////////// I think this gives me 1 every time
alert("Increment: "+ob); //////////////////
document.getElementById("box").onclick = function() {
clickedTime = Date.now();
reactionTime= (clickedTime - createdTime)/1000;
document.getElementById("time").innerHTML = reactionTime;
this.style.display = "none";
makeBox();
}
makeBox();
You have a few problems but to answer your question:
You're not defining clicked as a number (or any other type) so trying to perform an operation on undefined returns NaN...because well, it's not a number.
Your second attempt var i = 0; won't work because i is re-defined on each function call.
You should be able to use your gobal variable clicked as long as you set it to zero.
Here is an example that shows how a closure can count calls to a function:
function add5(y) {
//A totally normal function
return y + 5;
}
var counter = 0, /*a counter scoped outside of the function counter function*/
trackedAdd5 = (function (func) {
/*This anonymous function is incrementing a counter and then calling the function it is passed*/
return function () {
counter++;
/*The trick is this function returns the output of calling the passed in function (not that it is applying it by passing in the arguments)*/
return func.apply(this, arguments);
}
})(add5); /*calling this tracking function by passing the function to track*/
document.getElementById('run').addEventListener('click', function () {
/*Here we are treating this new trackedAdd5 as a normal function*/
var y = document.getElementById('y');
y.value = trackedAdd5(parseInt(y.value, 10));
/*Except the outer counter variable now represents the number of times this function has been called*/
document.getElementById('counter').value = counter;
});
<label> <code>y = </code>
<input id='y' value='0' />
<button id='run'>add5</button>
</label>
<br/>
<label><code>add5()</code> was called
<input readonly id='counter' value='0' />times</label>
makeBox.click = 0; // define the function's counter outside the function
makeBox.click++; // replace the `i` usage with this inside the function
About ob = increment();: it is used erroneously (redefines ob many times);
var ob = increment(); // define it once
ob(); // increments the counter
// another way to define `increment`:
var increment = (function () {
var i = 0;
return function () {
return i += 1;
};
})();
ob = increment(); // ob becomes 1 initially
ob = increment(); // ob becomes 2, etc.
I'm trying to write a script that will pick a random word from an array called words, and stop the loop after 5 times and replace the html with Amazing. so it always ends on amazing. Can't figure out best practice for something like this. My thinking is there just don't know where to put the script ender or how to properly implement this.
I feel like I need to implement something like this into my script, but can't figure out where. Please help.
if(myLoop > 15) {
console.log(myLoop);
$("h1").html('AMAZING.');
}
else {
}
Here is the Javascript that I'm using to loop and create bring new words in.
$(document).ready(function(){
words = ['respected​', 'essential', 'tactical', 'effortless', 'credible', 'smart', 'lucid', 'engaging', 'focussed', 'effective', 'clear', 'relevant', 'strategic', 'trusted', 'compelling', 'admired', 'inspiring', 'cogent', 'impactful', 'valued']
var timer = 2000,
fadeSpeed = 500;
var count = words.length;
var position, x, myLoop;
$("h1").html(words[rand(count)]);
function rand(count) {
x = position;
position = Math.floor(Math.random() * count);
if (position != x) {
return position;
} else {
rand(count);
}
}
function newWord() {
//clearTimeout(myLoop); //clear timer
// get new random number
position = rand(count);
// change tagline
$("h1").fadeOut(fadeSpeed, function() {
$("h1").slideDown('slow'); $(this).html(words[position]).fadeIn(fadeSpeed);
});
myLoop = setTimeout(function() {newWord()}, timer);
}
myLoop = setTimeout(function() {newWord()}, timer);
});
Here's my codepen
http://codepen.io/alcoven/pen/bNwewb
Here's a solution, which uses a for loop and a closure.
Words are removed from the array using splice. This prevents repeats.
I'm using jQuery delay in place of setTimeout:
var i, word, rnd, words, fadeSpeed, timer;
words = ['respected​', 'essential', 'tactical', 'effortless', 'credible', 'smart', 'lucid', 'engaging', 'focused', 'effective', 'clear', 'relevant', 'strategic', 'trusted', 'compelling', 'admired', 'inspiring', 'cogent', 'impactful', 'valued'];
fadeSpeed = 500;
timer = 2000;
for(i = 0 ; i < 6 ; i ++) {
if(i===5) {
word= 'awesome';
}
else {
rnd= Math.floor(Math.random() * words.length);
word= words[rnd];
words.splice(rnd, 1);
}
(function(word) {
$('h1').fadeOut(fadeSpeed, function() {
$(this).html(word);
})
.slideDown('slow')
.delay(timer)
.fadeIn(fadeSpeed);
}
)(word);
}
h1 {
text-transform: uppercase;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1></h1>
I added an iteration counter to check how many times it has changed.
Added this by other variables:
var iter = 1;
Added this in the newWord function:
iter = iter + 1;
if (iter > 5) {
return;
}
var word;
if (iter == 5) {
word = 'awesome';
}
else {
...
Here's my solution by changing your code:
http://codepen.io/anon/pen/YPGWYd
My end goal is to mitigate as much lag (window freezing/stuttering) as possible, giving the client a responsive window from page load.
My program is a Chrome extension, and part of it needs to search through a reddit submission, including all comments for certain words and then do some stuff with them. After this answer, I converted my code to use setInterval for the recursive search. Unforutnately, this runs concurrently, so even though each branch in the comment tree is delayed from its parent, the overall search overlaps each other, negating any benefit in the delay.
I have a solution, but I don't know how to implement it.
The solution would be to have a callback when a branch runs out that goes to the nearest parent fork. This in effect would traverse the comment tree linearly and would allow the setInterval (or probably setTimeout would be more appropriate) to have a noticeable affect.
The code that would need to be changed is:
function highlightComments(){
var elems = $(".content .usertext-body > .md");
var index = 0;
var total = elems.length;
console.log("comments started");
var intId = setInterval(function(){
highlightField(elems.get(index));
index++;
if(index == total){
clearInterval(intId);
addOnClick();
console.log("comments finished");
}
}, 25);
}
and highlightField is:
function highlightField(node) {
var found = $(node).attr("data-ggdc-found") === "1";
var contents = $.makeArray($(node).contents());
var index = 0;
var total = contents.length;
if (total == 0){
return;
}
var intId = setInterval(function() {
if (contents[index].nodeType === 3) { // Text
if (!found){
//Mods
var content = contents[index].nodeValue.replace(new RegExp(data.mods.regex, "gi"), data.mods.replacement);
//Creators
content = content.replace(new RegExp(data.creators.regex, "gi"), data.creators.replacement);
//Blacklist
for (var key in data.blacklist.regex){
if(data.blacklist.regex.hasOwnProperty(key)){
content = content.replace(new RegExp(data.blacklist.regex[key], "gi"), data.blacklist.replacement[key]);
}
}
if (content !== contents[index].nodeValue) {
$(contents[index]).replaceWith(content);
}
}
} else if (contents[index].nodeType === 1) { // Element
highlightField(contents[index]);
}
index++;
if(index == total){
clearInterval(intId);
}
}, 25);
}