Good day ! I'm learning javascript and have a problem with the timers. I just wanted to check if the onload event is triggered once the whole page is written including the text modified via javascipt. For that purpose I wanted to slow down the writing of the text by inducing a 200 ms delay between characters.
The test I use is the following:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Onload test</title>
<script>
function load() {
alert("The page is considered loaded !");
}
function writeSlowly(text, timer) {
var L= text.length;
var st = "";
function write (seq) {
document.getElementById("st").innerHTML = seq;
};
for (var i = 0; i < L; i += 1) {
st += text[i];
setTimeout(write(st), timer);
};
};
</script>
</head>
<body>
<p id="st"></p>
<script>
writeSlowly("This pages takes a while to load", 200);
window.onload = load;
</script>
</body>
</html>
The page loads as if there were no delay at all. Actually I expected the text (32 characters long) to take about 32 x 200 ms =~ 7 seconds. When debugging (with Firebug - I use Firefox 30) the program steps through the lines but the timer has no effect. The page displays almost instantaneously.
You are creating separate timers for each letter, all start at time 0 and all are executing at time 200ms.
Further, the function for setTimeout needs to be a callback (the function will be called back into when the timer expires). You are passing it a null. write() does not return anything much less a function.
So you are actually writing each letter every time you hit the loop, resulting in no delay
To achieve what you are trying I would do something along the lines of...
var str;
var index = 0;
function writeSlowly(text, timer) {
str = text;
setInterval(writeNext, timer);
};
function writeNext()
{
if(index < str.length - 1)
document.getElementById("st").innerHTML = str.substring(0, ++index);
else
document.getElementById("st").innerHTML = str;
}
I made a few modifications and made it work, you can try it out at this link.
One issue is that you were calling the write function, not setting it as a callback. Another issue is that the string you wanted to write was getting filled up completely before you wrote it. Finally, the timer was being set at 200ms from the current time for all writes, instead of introducing a delay of 200ms for each character written.
The updated Javascript is below.
function writeSlowly(text, timer) {
var L= text.length;
var st = "";
var delay = 0;
for (var i = 0; i < L; i += 1) {
st += text[i];
delay += timer
setTimeout(writer(st), delay);
};
}
function writer(toWrite) {
return function() {
document.getElementById("st").innerHTML = toWrite;
}
}
Edit:
I updated the JSFiddle.
When the text is done scrolling, it it will trigger the done() function and run whatever code you'd like to run at that point.
Following the answers given, their analysis (see comments below the original question) my preferred answer is the following - which like the one by Adam - takes almost exactly 5760 msec for a 2880 characters string with a 2 msec delay per character. The central part is below and the full answer on JS Fiddle.
function writeSlowly(text, timer) {
var L= text.length;
var delay = 0;
for (var i = 0; i < L; i += 1) {
setTimeout(writer, delay += timer);
};
function writer() {
if (!writer.seq) writer.seq = 0; // Create a function property that increments on each call.
document.getElementById("slowpara").innerHTML = text.substring(0, ++writer.seq);
}
}
I thank StackOverflow, the community, in particular Cheruvian and Adam for their generous help.
Related
Trying to have a counter on a web page that does not restart on each different page view within the same user session. Currently using this code (thanks to Praveen Kumar Purushothaman) but this counter resets every time a different page is viewed.
setTimeout(start, 0);
var i = 0;
var num = document.getElementById("number");
function start() {
increase();
setInterval(increase, 1000);
}
function increase() {
if (i < 100000) {
i += 10.41;
num.innerText = i.toFixed(2);
}
}
<span id="number"></span>
My suggestion is storing the variable into session storage. I have added more details in the comments:
setTimeout(start, 0);
// You're saving your current value here.
// Let's use localStorage. Set the i value if it doesn't exist for the first time.
if (!localStorage.getItem("i")) {
localStorage.setItem("i", 0);
}
var num = document.getElementById("number");
function start() {
increase();
setInterval(increase, 1000);
}
function increase() {
var i = localStorage.getItem("i");
if (i < 100000) {
i += 10.41;
// When you're making any changes, make changes to the localStorage too.
localStorage.setItem("i", i);
num.innerText = i.toFixed(2);
}
}
So, I'm making an audio player and I've got a bit of code to get the audio duration of the selected file, for the timer. It was working really well with Session, but then, as I might want more than one player per page, I decided to switch to ReactiveVar or ReactiveDict and I don't think I quite grasped how they work, because my code broke. Can you help me? What am I doing wrong?
This is the code as it was with Session.
Template.audioplayer.onRendered(
function() {
audio = $("audio").get(0);
}
);
Template.audioplayer.helpers({
audioduration: function() {
if (!Session.get("audioduration")) {
audioLenght = Meteor.setInterval(function() {
var totaltime = parseInt(audio.duration, 10);
var mins = Math.floor(audio.duration / 60, 10);
var secs = totaltime - mins * 60;
var gimmethetime = mins + ':' + (secs > 9 ? secs : '0' + secs);
Session.set("audioduration", gimmethetime);
return Session.get("audioduration");
}, 500);
} else {
Meteor.clearInterval(audioLenght);
return Session.get("audioduration");
}
}
});
This is my latest attempt at the same result with ReactiveVar. It came out with "TypeError: Cannot read property 'get' of undefined".
Template.audioplayer.onCreated(
function() {
audio = $("audio").get(0);
this.audioduration = new ReactiveVar();
}
);
Template.audioplayer.helpers({
audioduration: function() {
if (!Template.audioduration.get()) {
audioLenght = Meteor.setInterval(function() {
var totaltime = parseInt(audio.duration, 10);
var mins = Math.floor(audio.duration / 60, 10);
var secs = totaltime - mins * 60;
var gimmethetime = mins + ':' + (secs > 9 ? secs : '0' + secs);
Template.instance().audioduration.set(gimmethetime);
return gimmethetime;
}, 500);
} else {
Meteor.clearInterval(audioLenght);
return Template.instance().audioduration.get();
}
}
});
Thanks in advance!
I would add some information before answering your question. I have seen a lot of resources over the Internet using the Session variable to store temporary data and this stunned me a little. You must understand that the Session lifecycle start when you load the page and ends when you reload the page or close your browser. Let's say you store all your temporary data inside the session, if I take time to got through all the application then my session will be polute with data from all these pages. You are not faulty I just wanted to add some information on this. Anyone correct me if I'm wrong.
Now comes the place to answer.
In your helper, you have a test. In this test you try to access the reactiveVar with Template.audioduration. But as you have made for all the other calls it should be Template.instance().audioduration.get()
Template.audioplayer.onCreated(
function() {
audio = $("audio").get(0);
this.audioduration = new ReactiveVar();
}
);
Template.audioplayer.helpers({
audioduration: function() {
if (!Template.instance().audioduration.get()) {
audioLenght = Meteor.setInterval(function() {
var totaltime = parseInt(audio.duration, 10);
var mins = Math.floor(audio.duration / 60, 10);
var secs = totaltime - mins * 60;
var gimmethetime = mins + ':' + (secs > 9 ? secs : '0' + secs);
Template.instance().audioduration.set(gimmethetime);
return gimmethetime;
}, 500);
} else {
Meteor.clearInterval(audioLenght);
return Template.instance().audioduration.get();
}
}
});
Like this it should work...
EDIT :
You had a second error, I put it here for later reference and explanation.
"Exception in setInterval callback: TypeError: Cannot read property 'audioduration' of null". In order to correct it, I told you to create a variable in the helper scope with Template.instance().
This is a typical JS error. In JS, all the diffulty is to understand what is the scope of a function. In this case, it is possible to access the Template variable in the helper but it cannot be accessed inside the callback. If you reference the template instance inside the context of the helper then it can be accessed from the callback. I don't know much about how are linked the different contexts in this particular case. But it is quite usual that the this object cannot be accessed by callbacks, we are forced to user var that = this;. `See this post.
I'm using Adobe Flash Professional CS6 to create the game. I'll post the code under. Be noticed that there are two symbol I've created using Flash that are not made by code. These symbols are the Crosshair symbol, and the Hitbox symbol. Basically, the objective of the game is to click the Hitbox symbol. My issue is that I am experiencing what seems to be bottlenecking issues. When I click the Hitbox symbol a lot of times with a fast timer the score doesn't register. I am pressuming that this comes from the (maybe) ineffective movement algorithm. But I can't really seem to find room for improvement. Some help would be appreciated.
Be noticed, I had to change the timer from Timer(1) to Timer(30). This made the bottlenecking issue a little bit better, but made the game less fluent.
Aah, and the reason as to why I am using the directionCheckerY and directionCheckerX variables is that I will later in the development add random movement. A random timer will change these to either 0 and 1, creating random movement.
import flash.events.MouseEvent;
import flash.events.TimerEvent;
// Variables
var directionCheckerX:int=0;
var directionCheckerY:int=0;
var pointChecker:int=0;
// Croshair
var crosshair:Crosshair = new Crosshair();
addChild(crosshair);
Mouse.hide();
function moveCrossEvent (evt: MouseEvent) {
crosshair.x = mouseX;
crosshair.y = mouseY;
evt.updateAfterEvent();
}
// Hitbox
var hitbox:Hitbox = new Hitbox();
addChild(hitbox);
hitbox.x=50;
hitbox.y=50;
// Timer
var myTimer:Timer = new Timer(30);
myTimer.addEventListener(TimerEvent.TIMER, timerEvent);
myTimer.start();
function timerEvent(evt:TimerEvent) {
// Border code (Keeps the Hitbox away from out of bounds)
if (hitbox.x <= 0) {
directionCheckerX = 1;
} else if (hitbox.x >= 550) {
directionCheckerX = 0;
}
if (directionCheckerX == 0) {
hitbox.x-=2;
} else {
hitbox.x+=2;
}
if (hitbox.y <= 0) {
directionCheckerY = 1;
} else if (hitbox.y >= 400) {
directionCheckerY = 0;
}
if (directionCheckerY == 0) {
hitbox.y-=2;
} else {
hitbox.y+=2;
}
}
// EventListeners
stage.addEventListener(MouseEvent.MOUSE_MOVE, moveCrossEvent);
hitbox.addEventListener(MouseEvent.CLICK, hitboxEvent);
stage.addEventListener(MouseEvent.CLICK, stageEvent);
function hitboxEvent (evt:MouseEvent) {
pointChecker+=1;
outputTxt.text = String(pointChecker);
evt.stopImmediatePropagation();
//evt.updateAfterEvent();
}
function stageEvent(evt:MouseEvent) {
pointChecker-=1;
outputTxt.text = String(pointChecker);
}
To be clear, I'm not a game developer.
Actually, sometimes there is no big difference between a Timer with 1 millisecond interval and another one with 30 milliseconds interval because it's depending on the SWF file's framerate or the runtime environment ... but here, what about using an Event.ENTER_FRAME event instead of a Timer ? because as Adobe said here about Timers versus ENTER_FRAME events :
Choose either timers or ENTER_FRAME events, depending on whether content is animated.
Timers are preferred over Event.ENTER_FRAME events for non-animated content that executes for a long time.
and in your case the content is animated (even if your game is still basic).
Then you can use a var to set the speed of your hitbox which you can update at any time :
var speed:int = 2;
function timerEvent(evt:TimerEvent): void
{
// ...
if (directionCheckerX == 0) {
hitbox.x -= speed;
} else {
hitbox.x += speed;
}
// ...
}
Hope that can help.
I came across a situation where having single handler helps simplify the project. Does this have any impact on performance? Especially when the mouse move event called, having too many conditions have any impact on performance?
var myHandler = function(e){
if (e.type == 'mousemove'){
} else if (e.type == 'mouseover'){
} else if (e.type == 'mouseout'){
} else if (e.type == 'mousedown'){
} else if (e.type == 'mouseup'){
}
};
$('#Element').bind('mouseover mouseout mousedown mouseup mousemove', myHandler);
If you do need to handle all those events and you order the if-else statements by the frequency that the events are fired (as you already have) there is insignificant performance penalty, i.e. at most 4 short string comparisons. The following code tries to benchmark the performance of 10,000,000 string comparisons of fixed size:
$(function (){
Function.prototype.benchmark = function(name, times, args){
var iteration;
var start = new Date();
for (iteration=0; iteration<times; iteration++) {
var result = this.apply(this, args);
}
var end = new Date();
alert(name + " : " + (end-start));
}
function test(args){
return args[0] == "thisistest";
}
function overhead(args){
}
test.benchmark("string comparison", 10000000,["thisistesT"]);
//run same without the string comparison
overhead.benchmark("overhead", 10000000,["thisistesT"]);
});
Since the browser is not the only application on my PC the results vary between the executions, however, I very rarely got results of under 100ms in Chrome (remember that this is for 10,000,000 iterations).
Anyway, while your performance will not suffer from binding multiple events to a single function I really doubt that this will simplify your project. Having many if-else statements is usually considered a bad practice and a design flaw.
If you do this so that you can share state between the handlers by having them under the common scope of a function, you are better off having something like:
$(function (){
var elementSelector = "#Element";
var i = 0; //shared
var events = {
mouseover : function(){alert("mouseOver " + elementSelector + i++);},
mouseout : function(){alert("mouseOut " + elementSelector + i++);}
// define the rest of the event handlers...
};
var $target = $(elementSelector);
for(var k in events){
$target.bind(k, events[k]);
}
});
One more note regarding performance (plus code readability), switch is significantly faster than else ifs.
var myHandler = function(e){
switch(e.type){
case 'mousemove':
break;
case 'mouseover':
break;
case 'mouseout':
break;
case 'mousedown':
break;
case 'mouseup':
break;
}
};
For more detials you can tajke a look at Performance of if-else, switch or map based conditioning
I want to dynamically display some text from an array one after another in a dynamic textfield.
var wordList:Array = new Array('one','two','three');
for (var i = 0; i < wordList.length; i++) {
this.text_mc.txt.text = wordlist[i];
// Pause for 3 seconds and then move next
}
its the "Pause for 3 seconds and then move next" part I cant figure out.
Thanks.
track what index of the array you are currently showing, and use the setInterval function to call your 'showNext' function every 3000 milliseconds
ie)
var wordList:Array = new Array('one','two','three');
var curIndex = 0;
renderText(curIndex);
setInterval( showNextText, 3000 );
function showNextText()
{
curIndex++;
renderText(curIndex);
}
function renderText( index:Number )
{
this.text_mc.txt.text = wordlist[index];
}