requestAnimationFrame running before being called by function - javascript

I am attempting to make a simple timer (counting up from 0) with JavaScript and requestAnimationFrame. I want to start the timer from 0 when something is clicked. Currently my code displays the timer when the button is clicked, but it looks to me like requestAnimationFrame is running before the function is even called. If you load the code on a web page and wait a few seconds, then click the button, you will see the timer doesn't begin at 0, it starts at however many seconds it has been since the page first loaded. I'm at a loss and googling has not helped me figure out why/how the timer is starting counting before the function has been called.
My current code:
<div class="time">
Time: <label id="labelTime"></label>
</div>
<button id="button">Click me</button>
<script>
const button = document.getElementById('button');
button.addEventListener('click', clickButton);
function clickButton() {
runTimer();
}
function runTimer() {
let rAF_ID;
let rAFCallback = function(callback) {
let count = callback;
let s = Math.floor((count / 1000) % 60).toString().padStart(2, '0');
let m = Math.floor((count / 60000) % 60);
document.getElementById('labelTime').innerHTML = m + ":" + s;
rAF_ID = requestAnimationFrame(rAFCallback);
}
rAF_ID = requestAnimationFrame(rAFCallback);
}
</script>

The timestamp (DOMHighResTimeStamp) value passed into your rAFCallback function does not start from when the animation was first run, instead it has a "time origin" which varies on the context.
https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp
If the script's global object is a Window, the time origin is determined as follows:
If the current Document is the first one loaded in the Window, the time origin is the time at which the browser context was created.
If during the process of unloading the previous document which was loaded in the window, a confirmation dialog was displayed to let the user confirm whether or not to leave the previous page, the time origin is the time at which the user confirmed that navigating to the new page was acceptable.
If neither of the above determines the time origin, then the time origin is the time at which the navigation responsible for creating the window's current Document took place.
If the script's global object is a WorkerGlobalScope (that is, the script is running as a web worker), the time origin is the moment at which the worker was created.
In all other cases, the time origin is undefined.
So if you want to get a delta-time value from when the animation was started, you'll need to do that yourself, like so:
let timestampAtStart = null;
let lastRequestId = null;
function myAnimateFunction( timestamp ) {
if( !timestampAtStart ) {
timestampAtStart = timestamp;
}
let timeSinceStart = timestamp - timestampAtStart;
console.log( timeSinceStart );
lastRequestId = window.requestAnimationFrame( myAnimateFunction );
}
function startAnimation() {
if( lastRequestId ) window.cancelAnimationFrame( lastRequestId );
timestampAtStart = null;
lastRequestId = window.requestAnimationFrame( myAnimateFunction );
}

Related

Is there a way to fulfill a promise at an exact time?

I have a web page with transitions, On click everything goes to opacity:0 ( 1 second duration ) and then a new page is swapped in and everything goes to opacity:1 ( 1 second duration )
It ends up looking weird if the page doesn't have exactly 1 second to hide and appear. Also if the page doesn't get swapped immediately in between the two it looks award.
This was my first code
$('#main').css('opacity', '0');
setTimeout(function(){
$('#main').load('/views/'+name+'.html').css('opacity', '1')
}, 1000);
however load() sometimes takes too long to grab the view, and since the css is implemented immediately the opacity is already 1 when it swaps in.
so I tried this:
$.get('/views/'+name+'.html', function(page){
setTimout(function(){
$('#main').html(page).css('opacity', '1')
},1000);
})
But now if the $.get() is slow, the page is blank for too long.
Ideally I would like to know how long the promise took to fulfill, and subtract that from the setTimeout time.
I am thinking now that I have to manually create a new date object and check the difference after promise fulfillment.
Is there a better solution?
I forgot I can just use promises.
var pagePromise = $.get('/views/'+name+'.html')
$('main').css('opacity', '0')
setTimeout(function(){
pagePromise.then( function(page){
$('main').html(page).css('opacity', '1') }
)
},1000)
Just get time before and after your request. This may not be exact, but the error margin can easily be ignored for most purposes, yours included.
var time = Date.now();
$.get('/views/' + name + '.html', function(page) {
var elapsed = Date.now() - time;
setTimout(function() {
$('#main').html(page).css('opacity', '1')
}, 1000 - elapsed);
})
You can use Date.now() and calculate the difference.
var start = Date.now();
$.get('/views/' + name + '.html', function(page) {
setTimout(function() {
$('#main').html(page).css('opacity', 1);
}, 1000 - (Date.now() - start));
});
It's a little hard to tell exactly what you're trying to accomplish, but attempting to follow your logic, it appears that you want your new content to show up in one second after you hid the old content except when the content takes longer than that to load in which case, it shows up when it's loaded. You can do that by breaking the process down into a couple steps.
Record the starting time.
Fetch your content with ajax
When the content has been fetched, check how much time has elapsed.
If more than a second has elapsed, then just insert the content and show it.
If less than a second has elapsed, then set a timer for the remaining amount of time and then show the content when that timer fires.
You can implement that logic like this:
var begin = Date.now();
var main = $('#main').css('opacity', '0');
$.get('/views/'+name+'.html').then(function(content) {
main.html(content);
var elapsed = Date.now() - begin;
if (elapsed > 1000) {
// show it now
main.css('opacity', '1');
} else {
setTimeout(function(){
// show it when 1 second has finished
main.css('opacity', '1');
}, 1000 - elapsed);
}
});
Using this sort of notification and time measurement scheme, there is no guessing about load times.
You can use ajax and make your code synchronous.
jQuery.ajax({
url: '/views/'+name+'.html',
success: function (result) {
$('#main').html(page).css('opacity', '1');
},
async: false,
type: "GET"
});

JS variable containing ongoing time value

I'm working on a chatbot script (Hubot - running in terminal) exercise and looking for a method to count the time since the last message was left in the thread. Then after nobody has left a message for X number of minutes (let's say 10,000 milliseconds) I would like to console.log("CRICKETS!..CRICKETS!..")
I'm imagining something like:
//currentTime - startTime = timeSince
//and
// if( timeSince > 10,000)
// {console.log("Crickets!..")
however I'm not sure of how to create the currentTime variable as continuously growing counter
Below is the code I've started which doesn't appear to throw any errors in the , but also doesn't seem to work as I'm running it in the terminal. It just prints the current time twice
module.exports = function(robot) {
return robot.hear(/$/i, function(msg) {
var startTime = (Date.now()) ;
return(startTime);
if (Date.now() - startTime > 1000) {
console.log("CRICKETS..!...")
};
});
};
You'll notice I'm using Date.now() but I'm not attached if there's a better method. Also here is a link to basic hubot scripts in case it is needed for context - https://github.com/github/hubot/blob/master/docs/scripting.md
You can always use setTimeout and cancel it if need be
Pseudo-code:
var myTimeout = setTimeout(function () {
//nobody has left a message for 10 seconds
}, 10000);
if (user has left message)
clearTimeout(myTimeout);
The window.setTimeout function allows you to trigger a callback function after a delay. And you can clear that timeout by calling window.clearTimeout(value_returned_by_setTimeout).
We could define a callback: function crickets(){ console.log('Chirp! Chirp!'); }
Assuming some function newMessage gets called whenever a a new message appears, you could try something like this:
var cricketTimeout = null;
function newMessage(){
//... your code
if (cricketTimeout) clearTimeout(cricketTimeout);
cricketTimeout = setTimeout(crickets, delayInMilliseconds);
}

Javascript page gets laggy after a few hours when calling a function every 10 seconds

I'm refreshing data in my javascript page by calling a function every 10 seconds which requests most recent data from my server.
This is the function that is called every 10 seconds:
function loadEvents() {
//Angular $resource factory
EventsService.getEvents({}, function(response) {
var events = response.events;
var now = new Date().getTime();
$scope.currentevent = null;
for(var i = 0; i<events.length && $scope.currentEvent === null; i++){
var value = events[i];
if(new Date(value.start).getTime()<now && new Date(value.end).getTime()>now){
$scope.currentEvent = value;
}
}
//I use daypilot to show the events, the below code only updates the list of events if
//The events have changed
var currentEvents = JSON.stringify($scope.scheduler.events.list);
var newEvents = JSON.stringify(events);
if(currentEvents!=newEvents){
$scope.scheduler.events.list = events;
$scope.scheduler.update();
}
$timeout(function () {
loadEvents();
}, 10000);
});
}
For some reason, the page gets laggy after about 5 hours. This is specially noticeable on slow devices like my android tablet. This issue doesn't occur after removing the $timeout. This seems like a memory issue? I can solve the issue by just refreshing the page by calling location.reload(); every 5 hours, but this doesn't seem the best solution. I rather want to solve the issue that is causing this behavior.
I also measured how long it takes to execute the function and get the response from server, this never takes longer than 100ms. The events returned by the server is only for 1 day and is just a few kilobytes.

PhantomJS - pause/resume javascript execution

I have a PhantomJS script that I'm trying to use in order to essentially generate a video of a particular website.
var page = require('webpage').create();
page.viewportSize = {
width: 1280,
height: 720
};
page.open('http://my-awesome-site.whatever', function() {
var fps = 30;
var seconds = 10;
var elapsed = 0;
var current = 0;
takeScreenShot();
function takeScreenShot() {
if (elapsed < seconds * 1000) {
page.render('screenshot_' + (current++) + '.png');
elapsed += 1000 / fps;
setTimeout(takeScreenShot, 1000 / fps);
}
else {
phantom.exit();
}
}
});
The above script will attempt to take 30 screenshots per second for 10 seconds, which I then combine into an mp4 using ffmpeg.
The problem is that the page.render() function is not instantaneous, and does not halt the scripts running on the page. So when I'm pointing at a page using jQuery animations, which I believe rely on setTimeout, those timeouts continue to run while each screenshot is processed. As a result, the outputted video appears greatly sped up.
Is there a way through PhantomJS to pause script execution? What I'm hoping for is to do something like:
page.pause();
page.render('screenshot_' + (current++) + '.png');
page.resume();
But sadly, I don't see anything like that in their api docs.
You could set page.settings.javascriptEnabled=false if the page looks don't depend on JS too much.
Be aware that you need to define the settings before the first call as they are evaluated only once eg :
var page = require('webpage').create();
page.settings = {
javascriptEnabled=false
}
See : http://phantomjs.org/api/webpage/property/settings.html
If it doesn't fit your need, you need to dig into the code of the page to find a way to stop the animations (use your browser's console). Once you know what you need to do, just evaluate the command in Phantom.
page.open('http://my-awesome-site.whatever', function() {
var stopScripts = page.evaluate(function() {
// do whatever you need to stop execution
return true;
});
// ... take your screenshots
});

InDesign CS5 Script: How can I open and close a popup window after `n` seconds?

The window is opening fine, but the script will not continue until the popup window is manually closed! This is not desirable, as I would rather the window close itself after n seconds...
So do I have to open the window in a separate thread from the rest of the script? Is that even possible?
Here's my code so far:
function showMessageWindow()
{
var script = getScriptName(); // initialized from the function below
var message = new Window("dialog", script);
message.add("StaticText", undefined, "The script " + script + " is now running, please wait...");
message.show();
var startTime = new Date().getTime(); // in milliseconds
var currentTime = new Date().getTime(); // in milliseconds
var waitTime = 5000; // 1000 milliseconds is 1 second
var delay = function()
{
while( (currentTime - startTime) < waitTime)
{
currentTime = new Date().getTime(); // in milliseconds
}
} // end of closure delay
delay(); // calling the delay closure function here
message.close(); // close the message after the specified delay time
} // end of function showMessageWindow
// called from the function showMessageWindow
function getScriptName()
{
var scriptName = "";
try
{
scriptName = File(app.activeScript).name;
}
catch(e)
{
scriptName = File(e.fileName).name;
}
return scriptName;
} // end of function getScriptName
dialog type windows are modal dialogs preventing any background operations. However even with a non modal window, I am not sure you can get parallel execution of both routines from teh same script. I am pretty sure teh script engine will kindly wait for yt-our delaying routine to end before keeping on moving :\
The only way I could deal with such asynchronous processus was using scriptUI in combination of a swf file and have any timer stuff done in AS3. That way you can have script execution to move on within InDesign and have your loop running in the swf file. I did that once especially to monitor a hot folder.
BTW : you have a mistake in your code => message.add("StaticText", undefined,…
That should be statictext in lowercase ;)

Categories