I’ve had a hard time debugging a news ticker – which I wrote from scratch using JavaScript.
It works fine on most browsers apart from IE9 (and some mobile browsers – Opera Mobile) where it is moving very slowly.
Using Developer Tools > Profiler enabled me to find the root cause of the problem.
It’s a call to offsetLeft to determine whether to rotate the ticker i.e. 1st element becomes the last element.
function NeedsRotating() {
var ul = GetList();
if (!ul) {
return false;
}
var li = GetListItem(ul, 1);
if (!li) {
return false;
}
if (li.offsetLeft > ul.offsetLeft) {
return false;
}
return true;
}
function MoveLeft(px) {
var ul = GetList();
if (!ul) {
return false;
}
var li = GetListItem(ul, 0);
if (!li) {
return false;
}
var m = li.style.marginLeft;
var n = 0;
if (m.length != 0) {
n = parseInt(m);
}
n -= px;
li.style.marginLeft = n + "px";
li.style.zoom = "1";
return true;
}
It seems to be taking over 300ms to return the value, whereas the ticker is suppose to be moving left 1 pixel every 10ms.
Is there a known fix for this?
Thanks
DOM operations
I agree with @samccone that if
GetList()andGetListItem()are performing DOM operations each time, you should try to save references to the elements retrieved by those calls as much as possible and reduce the DOM operations.You’ll just be storing a reference to the DOM element in a variable. Since it’s a reference, it is the real value. It is the same exact object. E.g.:
That stores a reference to the DOM object. You can read
offsetLeftfrom that object anytime, without performing another DOM operation (likegetElementsByTagName) to retrieve the object.On the other hand, the following would just store the value and would not stay in sync:
offsetLeft
If
offsetLeftreally is a bottleneck, is it possible you could rework this to just read it a lot less? In this case, each time you rotate out the first item could you readoffsetLeftonce for the new first item, then just decrement that value in each call toMoveLeft()until it reaches0(or whatever)? E.g.If you want to get even more aggressive about avoiding
offsetLeft, maybe you could do something where you read the width of each list item once, and theoffsetLeftof the first item once, then just use those values to determine when to rotate, without ever callingoffsetLeftagain.Global Variables
You don’t need to use global variables, and in fact you should avoid it — it’s bad design. There are at least a couple of good approaches you could take without using global variables:
You can wrap your whole program in a closure:
There
elementsis scoped to the anonymous function that contains it. It’s not a global variable and won’t be visible outside the anonymous function. It will be visible to any code, including functions (such asNeedsRotating()in this case), within the anonymous function, as long as you don’t declare a variable of the same name in your inner functions.You can encapsulate everything in an object:
Here I’ve wrapped everything in an anonymous function again so that even
tickeris not a global variable.Response to Comments
First of all, regarding
setTimeout, you’re better off doing this:rather than:
In JS, functions are first-class objects, so you can pass the actual function object as an argument to
setTimeout, instead of passing a string, which is like usingeval.You may want to consider
setIntervalinstead ofsetTimeout.That’s actually not the case. The closure is formed when the function is defined. So calling the function via
setTimeoutdoes not interfere with the function’s access to the closed variables. Here is a simple demo snippet:setTimeoutwill, however, interfere with the binding ofthisin your methods, which will be an issue if you encapsulate everything in an object. The following will not work:Inside
ticker.whatever,thiswould not refer toticker. However, here you can use an anonymous function to form a closure to solve the problem:The key things are:
Access
offsetLeftonce each time you rotate the list.If you store the list items in a variable, you can access their
offsetLeftproperty without having to repeatedly perform DOM operations likegetElementsByTagName()to get the list objects.The variable in #2 can either be an object property, if you wrap everything up in an object, or just a variable that is accessible to your functions via their closure scope. I’d probably wrap this up in an object.
I updated the “DOM operations” section to clarify that if you store the reference to the DOM object, it will be the exact same object. You don’t want to store
offsetLeftdirectly, as that would just be storing the value and it wouldn’t stay in sync.However you decide to store them (e.g. object property or variable), you should probably retrieve all of the
liobjects once and store them in an array-like structure. E.g.Each time you rotate, indicate the current item somehow, e.g.:
Or if you want you could store the
liobjects in an array and do this for each rotation: