The following code is intended to display a 0, then a few seconds later change this to 1, then back to 0, and so on, ad infinitum.
The problem is this (obviously) freezes the webpage itself. I could use an animated gif but want to expand this further and code becomes the better choice.
How might I do this?
<div id="bit" style="width: 100px; height: 100px; margin: auto; border: 1px solid #000; text-align: center;"></div>
<script>
var bitValue = 0;
for (;;) {
setTimeout(showBit(), 2000);
}
function showBit() {
document.getElementById("bit").innerHTML = bitValue;
bitValue = Math.abs(bitValue - 1);
}
</script>
A few things went wrong:
setTimeout(showBit(), 1000);
must be:
setTimeout(showBit, 100);
as you want to pass a function and not execute it immeadiately. Another thing is that you cannot just do
for(;;) { /*...*/ }
as that blocks the browser forever. You would have to do it asynchronously:
const delay = ms => new Promise(res => setTimeout(res, ms));
(async function() {
while(true) {
await delay(1000);
showBit();
}
})();
Or a bit simpler with a pseudorecursive timeout:
(function next() {
showBit();
setTimeout(next, 1000);
})();
Or if you dont want to do that manually, just use setInterval:
setInterval(showBit, 1000);
Like #CertainPerformance said, setInterval should be used instead. Here are a few good examples of how it can be used - https://www.w3schools.com/jsref/met_win_setinterval.asp
Use **setInterval()** with **Math.round** and **Math.random**?
var time = setInterval(f, 2000);
function f() {
document.getElementById("bit").innerHTML = Math.round(Math.random());
}
Use if
var time = setInterval(function() {
if (document.getElementById("bit").innerHTML == 0) {
document.getElementById("bit").innerHTML = 1;
}
else {
document.getElementById("bit").innerHTML = 0;
}
}, 2000);
Related
It is possible to stop an insertBefore, inside an addEventListener, to add a smooth css, so that the movement produced by the insertion of the div is not abrupt for the user?
I have read many questions, i have tried using settimeout in various ways, without success:
const gallery = document.getElementById('gallery');
const frames = gallery.querySelectorAll('.frame');
for (var i = 0; i < frames.length; ++i) {
frames[i].addEventListener('click', function() {
if (this.className == "frame b") {
//setTimeout( function(){
gallery.insertBefore(this, this.previousElementSibling);
//}, 1000 );
} else {
//setTimeout( function(){
gallery.insertBefore(this, this.previousElementSibling);
//}, 1000 );
};
});
};
.frame {
width: 200px;
height: 100px;
font: bold 400% sans-serif;
color: white;
float: left;
}
.frame.a {
background-color: brown;
}
.frame.b {
background-color: purple;
}
<div id="gallery">
<div class="frame a">A</div>
<div class="frame b">B</div>
</div>
this refers to a different context inside your setTimeout callback; it doesn't refer to the element for which the event is dispatched anymore.
There are a few ways you could do this, here are 3:
Use an arrow function, where this doesn't get bound to a new context:
setTimeout( () => {
gallery.insertBefore(this , this.previousElementSibling);
}, 1000 );
Store a reference to this for use inside the callback:
const _self = this;
setTimeout( function(){
gallery.insertBefore(_self , _self.previousElementSibling);
}, 1000 );
Manually bind the current context to the callback function:
setTimeout( function(){
gallery.insertBefore(this, this.previousElementSibling);
}.bind(this), 1000 );
Another angle I prefer for this is use this wait fn, tidy & concise.
wait fn src
// at the top of the file, or 1000 here and leave out 1000 in the call
const wait = (delay = 300) => new Promise((resolve) => setTimeout(resolve, delay));
// inside
frames[i].addEventListener('click', async () => {
await wait(1000)
if (this.className == "frame b") {
gallery.insertBefore(this, this.previousElementSibling);
} else {
gallery.insertBefore(this, this.previousElementSibling);
};
});
Only trouble is sometimes forgetting the await keyword, hopefully you have an IDE that tells You when you don't need it, like prettier... Won't tell You when you've forgotten though since there are other valid ways to use it. Outer for loop is separate of this question since it's just assigning refs for later.
There is also now
import {setTimeout} from "timers/promises";
await setTimeout(1000);
From Node 16, you don't need the wait fn.
when coding, I came upon this very weird problem. My code is below:
js
document.getElementById("id").style.width = "0%"
var a = 0;
setTimeout(function () {
if (a <= 60) {
document.getElementById("id").style.width = a + "%";
a++;
}
}, 100);
I want it to run 60 times, each time changing the div's width by 1%
However, when I run the code, the if block makes it finish instantly as if the timeout didn't work. Can anyone tell me what's wrong? Thanks in advance!
setTimeout function executes only once after that specified time in milli seconds.
If you want to keep on executing the block, you have to make use of setInterval instead of setTimeout.
setInterval keeps on calling the function since the interval is cleared.
Dont forget to call clearInterval once target achieved.
var a = 0;
const interval = setInterval(function () {
if (a <= 60) {
console.log("Im being called");
document.getElementById("id").style.width = a + "%";
a++;
} else {
// Target achieved, clearing interval
console.log("Im stoping execution");
clearInterval(interval)
}
}, 100);
#id {
height: 300px;
background: orange;
}
<div id="id"></div>
Why doesn't the background change right as I copy? I added a console.log() and as you can see, the console.log() works, but the background won't change. What is the problem here?
To test, click on the snippet and then press CMD + C
(Windows:CTRL + C)
window.addEventListener('copy', function(e) {
e.preventDefault();
//This should change!
document.getElementById("object").style.backgroundColor = 'white';
console.log("Started!");
tryCopyAsync(e).then(() =>
document.getElementById("object").style.backgroundColor = 'gray'
);
});
async function tryCopyAsync(e){
if(navigator.clipboard){
await e.clipboardData.setData('text/plain',getText());
}
}
function getText(){
var html = '';
var row = '<div></div>';
for (i=0; i<100000; i++) {
html += row;
}
return html;
}
#object{
width:100%;
height:100vh;
background:gray;
}
body{
padding:0;
margin:0;
overflow:hidden;
}
<div id='object'></div>
First, your sleepFor method is completely blocking the event-loop synchronously, even if it is called from an async function:
window.addEventListener('copy', function(e) {
e.preventDefault();
//This should change!
document.getElementById("object").style.backgroundColor = 'white';
console.log("Started!");
tryCopyAsync(e).then(() =>
document.getElementById("object").style.backgroundColor = 'gray'
);
console.log('sync');
});
async function tryCopyAsync(e){
if(navigator.clipboard){
await e.clipboardData.setData('text/plain',getText());
}
}
function sleepFor(sleepDuration){
var now = new Date().getTime();
while(new Date().getTime() < now + sleepDuration){}
}
function getText(){
console.log('blocking');
var html = '';
var row = '<div></div>';
for (i=0; i<10; i++) {
html += row;
sleepFor(300);
}
console.log('stopped blocking');
return html;
}
onclick = e => document.execCommand('copy');
#object{
width:100%;
height:100vh;
background:gray;
}
body{
padding:0;
margin:0;
overflow:hidden;
}
click to trigger the function
<div id='object'></div>
But even if it were called in a microtask, that wouldn't change a thing, because microtasks also do block the event-loop. (Read that linked answer, it explains how the rendering is tied to the event-loop).
If what you want is to have your code let the browser do its repaints, you need to let the browser actually loop the event-loop, and the only ways to do this are:
split your getText logic and make it wait for the next event-loop iteration by posting a task (e.g through setTimeout)
use a dedicated Worker to produce the data returned by getText.
However beware you were not using the async Clipboard API, but simply overriding the default value of the copy event, which can not be done asynchronously. So going this way you will actually need to really use the Clipboard API.
Here is an example using a MessageChannel to post a task since current stable Chrome still has a 1ms minimum delay for setTimeout:
window.addEventListener('copy', function(e) {
e.preventDefault();
//This should change!
document.getElementById("object").style.backgroundColor = 'white';
console.log("Started!");
tryCopyAsync(e).then(() =>
document.getElementById("object").style.backgroundColor = 'gray'
);
});
async function tryCopyAsync(e) {
if (navigator.clipboard) { // you were not using the Clipboard API here
navigator.clipboard.writeText(await getText());
}
}
async function getText() {
var html = '';
var row = '<div></div>';
for (i = 0; i < 1000000; i++) {
if (i % 1000 === 0) { // proceed by batches of 1000
await waitNextFrame();
}
html += row;
}
return html;
}
function waitNextFrame() {
return new Promise(postTask);
}
function postTask(task) {
const channel = postTask.channel ||= new MessageChannel();
channel.port1.addEventListener("message", () => task(), {
once: true
});
channel.port2.postMessage("");
channel.port1.start();
}
onclick = (evt) => document.execCommand("copy");
#object {
width: 100%;
height: 100vh;
background: gray;
}
body {
padding: 0;
margin: 0;
overflow: hidden;
}
<div id='object'></div>
Well, firstly, the argument for "then" should be a function.
.then(()=>{})
From running your code, it looks like this
document.getElementById("object").style.backgroundColor = 'gray';
is getting called immediately after setting the background color white, so you aren't able to notice it turning white, even though it is (just very very briefly).
Try setting some logging in your tryCopyAsync function to see why it is finishing too quickly.
I have created an effect using javascript to show top categories in my project.
As you can see in the image above, it works perfectly fine. But after running for sometime; If the user leaves this page and switched to another tab and come back to this after sometime, then it starts to act weird.
Below is the code which I'm using to make this effect.
var curCat = 0;
var cats = [
"<a href='/search/?cat=1'>Animals</a>",
"<a href='/search/?cat=2'>Graffiti</a>",
"<a href='/search/?cat=3'>Figures</a>",
"<a href='/search/?cat=6'>Landscape</a>",
"<a href='/search/?cat=7'>Portrait</a>",
"<a href='/search/?cat=9'>Other</a>"
];
function catSlider() {
$(catDisplay).html(cats[curCat]);
$(catDisplay).fadeIn();
setInterval(function() {
$(catDisplay).fadeOut();
setTimeout(function() {
if (++curCat >= cats.length) {
curCat = 0;
}
$(catDisplay).html(cats[curCat]);
$(catDisplay).fadeIn();
}, 400);
}, 3000);
}
$(document).ready(function() {
catSlider();
});
What is causing this problem? What am I missing?
Timers get throttled back when the tab doesn't have focus (and many other odd games, such as being accelerated when focus returns), so your setInterval and your setTimeout may get out of sync.
Instead, just use setTimeout, where each action (fade out and fade in) triggers the next:
function catSlider() {
$(catDisplay).html(cats[curCat]);
$(catDisplay).fadeIn();
function fadeOut() {
$(catDisplay).fadeOut();
setTimeout(fadeIn, 400);
}
function fadeIn() {
if (++curCat >= cats.length) {
curCat = 0;
}
$(catDisplay).html(cats[curCat]);
$(catDisplay).fadeIn();
setTimeout(fadeOut, 3000);
}
setTimeout(fadeOut, 3000);
}
And/or you might consider the callbacks that fadeOut and fadeIn can trigger, in particular on the fadeOut:
function catSlider() {
$(catDisplay).html(cats[curCat]);
$(catDisplay).fadeIn();
function fadeOut() {
$(catDisplay).fadeOut(fadeIn); // ***
}
function fadeIn() {
if (++curCat >= cats.length) {
curCat = 0;
}
$(catDisplay).html(cats[curCat]);
$(catDisplay).fadeIn();
setTimeout(fadeOut, 3000);
}
setTimeout(fadeOut, 3000);
}
Side note: If you like, you can replace
if (++curCat >= cats.length) {
curCat = 0;
}
with
curCat = (curCat + 1) % cats.length;
The 3000ms interval might run faster than a 400ms timeout as the browser tries to get the interval in sync after you get back to the tab. To resolve this, you could use a delayed loop, e.g.:
const timer = ms => new Promise(res => setTimeout(res, ms));
(async function() {
while(true) {
hideCurrent();
await timer(400);
showNext();
await timer(2600);
}
})()
I want to create an marquee that start at first, but every 10 seconds, the marquee will stop for 5 seconds before the marquee start again.
How can I do that?
I only manage to create a timer that stop the marquee after 5 seconds :
<script>
function startLoop() {
setInterval( "doSomething()", 5000 ); }
function doSomething() {
document.getElementById('myMarquee').stop(); }
</script>
HTML
<body onload="startLoop();">
<marquee direction="right" id="myMarquee">This is a marquee.</marquee>
</body>
A few days ago I needed something similar to your problem. I soon figured out that marquee is not a standard element, so you can't use it in cross-browser solutions.
I have extracted the animation part, based on jQuery, I used in my solution, you can see the effect in this jsFiddle
HTML
<div id="container">
<div id="mytext">
this is a simple text to test custom marquee
</div>
</div>
CSS
#container
{
display: inline-block;
background-color: #cccccc;
width: 100px;
height: 100px;
overflow: hidden;
}
#mytext
{
display: inline-block;
position: relative;
white-space: nowrap;
}
JavaScript
$(function() {
var txt = $("#mytext");
txt.bind('scroll', function () {
var el = $(this);
// Scroll state machine
var scrollState = el.data("scrollState") || 0;
el.data("scrollState", (scrollState + 1) % 4);
switch (scrollState) {
case 0: // initial wait
el.css({ left: 0 });
el.show();
window.setTimeout(function () {
el.trigger("scroll");
}, 5000);
break;
case 1: // start scroll
var delta = el.parent().width() - el.width();
if (delta < 0) {
el.animate({ left: delta }, 2000, "linear", function () {
el.trigger("scroll");
});
}
break;
case 2: // delay before fade out
window.setTimeout(function () {
el.trigger("scroll");
}, 2000);
break;
case 3: // fade out
el.fadeOut("slow", function () {
el.trigger("scroll");
});
break;
}
}).trigger("scroll");
});
It doesn't do exaclty the same as your requirement, but if you read the code and make some changes to the state-machine, you will get it working :)
If you want to keep using the marquee, this should work (In browsers that support the marquee):
<script>
function stopRunning() {
document.getElementById('myMarquee').stop();
setInterval("startRunning()", 5000);
}
function startRunning() {
document.getElementById('myMarquee').start();
setInterval("stopRunning()", 10000);
}
//You don't really need a function to start the loop, you can just call startRunning();
startRunning();
</script>
This will make the marquee start running, stop after 10 seconds, start again after 5, and repeat.
try this:
var myMarquee = document.getElementById('myMarquee');
run();
function run() {
setTimeout(function() {
myMarquee.stop();
setTimeout(function(){
myMarquee.start();
run();
},5000);
},10000);
}
see it run at http://jsfiddle.net/gjwYh/
To implement every 10 seconds, the marquee will stop for 5 seconds before the marquee start again You need some logic like. I have used step variable to control the progress. Hope its clear
<script>
var step = 1; // marquee is run on load time
function startLoop()
{
setInterval("doSomething()", 5000 );
}
function doSomething()
{
if (step == 0) {
// 5 seconds are passed since marquee stopped
document.getElementById('myMarquee').start();
step = 1;
}
else
{
if (step == 1) {
// 5 seconds are passed since marquee started
step = 2; // Just delay of another 5 seconds before stop
}
else
{
if (step == 2) {
// 10 seconds are passed since marquee started
document.getElementById('myMarquee').stop();
step = 0;
}
}
}
}
</script>
Check Out its Working Here on Jsfiddle. I have also added a timer in a div which will give you ease in checking the behavior of stop and start against time
if you want to apply same in angular then you do like this
import { Component, OnInit , ElementRef, ViewChild} from '#angular/core';
write this under class
#ViewChild('myname', {static: false}) myname:ElementRef;
to start the loop write this under ngOnInit
setTimeout(()=>{ //<<<--- using ()=> syntax
this.startRunning()
console.log("aaa")
}, 1000);
place this code outside of ngOnInit
startRunning() {
setInterval(() =>{
this.myname.nativeElement.stop();
setTimeout(() =>{
this.myname.nativeElement.start();
console.log("start")
},2000)
console.log("stop")
}, 4000);
}