Implementing the marquee tag using jQuery

Wednesday, Feb 9, 2011 6 minute read Tags: jquery doing-it-wrong web javascript
Hey, thanks for the interest in this post, but just letting you know that it is over 3 years old, so the content in here may not be accurate.

It’s time for another foray into the good old days of HTML, and we’re going to look at how to build the <marquee> tag, which has also gone for quite some time.

Again we’re going to use jQuery to help us out, so let’s see what we’re building:

(function($) {
    $.fn.textWidth = function(){
         var calc = '<span style="display:none">' + $(this).text() + '</span>';
         $('body').append(calc);
         var width = $('body').find('span:last').width();
         $('body').find('span:last').remove();
        return width;
    };
    
    $.fn.marquee = function() {
        var that = $(this),
            textWidth = that.textWidth(),
            offset = that.width(),
            width = offset,
            css = {
                'text-indent' : that.css('text-indent'),
                'overflow' : that.css('overflow'),
                'white-space' : that.css('white-space')
            },
            marqueeCss = {
                'text-indent' : width,
                'overflow' : 'hidden',
                'white-space' : 'nowrap'
            };
        
        function go() {
            if(width == (textWidth*-1)) {
                width = offset;
            }
            that.css('text-indent', width + 'px');
            width--;
            setTimeout(go, 1e1);
        };
        that.css(marqueeCss);
        width--;
        go();
    };
})(jQuery);

We then use it like this:

$('h1').marquee();

As you can probably see this is a bit more involved than when we implemented the <blink> tag.

Breaking it down

From what you can see here we’ve actually got 2 plugins that I’m creating, the first one is going called textWidth, the other being the actual marquee.

Note: I’ve actually used some code I found on the web for the textWidth plugin, which you can find here.

Text Width

The first issue we have to overcome is working out just how wide the piece of text we’re going to be moving is, otherwise we don’t really know what we’re going to be moving.

The piece of code we’re using for it is quite simple, all it does is creates a hidden tag that will contain only the text, and then get the size of that element. It’s not 100% fool-proof, I’m not taking into account padding/ margin/ border on the span tag, but it’ll generally do the job.

Implementing Marquee

So now that we’re able to work out the width of the text we can start implementing the full marquee plugin, first thing we need to do is setup a few variables:

  • that will be a jQuery instance of the DOM elements we’ve selected
  • textWidth is well, the text width
  • offset is the full width of the element we want
  • css will contain the original values of the elements we’ve about to change for the marquee to work
  • marqueeCss is a set of CSS values we need to change

As you can see we are changing some CSS values, what we are setting is:

  • text-indent, we need to set this to the full width of the element, this means that the text wont start until it’s off the screen
  • overflow, so the text doesn’t show up when we push the indent out we will set the overflow to hidden
  • white-space, this is an interesting one, there’s probably a better way to do this, but what it does is prevents the content from breaking to a new line when the width isn’t enough for the content to reside within. This combined with the overflow will mean that the content stays on the one line and isn’t shown until we want

Again we’re going to use the recursive setTimeout pattern, which I talked about here, but before we get started we want to update the CSS for the element and then do our first move, decreasing the width by 1px for when we first call go.

Let’s have another look at the go method:

function go() {
    if(width == (textWidth*-1)) {
        width = offset;
    }
    that.css('text-indent', width + 'px');
    width--;
    setTimeout(go, 1e1);
};

This is why we need the textWidth, as we move once we’ve moved the text off the screen we need to then move it back to the right hand side and it starts all over again.

Woo it’s so pretty.

Time for sex appeal

Why don’t we add the ability to set the number of times to scroll, that’s an easy one to add:

$.fn.marquee = function(args) {
    var that = $(this),
        textWidth = that.textWidth(),
        offset = that.width(),
        width = offset,
        css = {
            'text-indent' : that.css('text-indent'),
            'overflow' : that.css('overflow'),
            'white-space' : that.css('white-space')
        },
        marqueeCss = {
            'text-indent' : width,
            'overflow' : 'hidden',
            'white-space' : 'nowrap'
        },
        args = $.extend(true, { count: -1 }, args),
        i = 0;
    
    function go() {
        if(width == (textWidth*-1)) {
            i++;
            if(i == args.count) {
                that.css(css);
                return;
            }
            width = offset;
        }
        that.css('text-indent', width + 'px');
        width--;
        setTimeout(go, 1e1);
    };
    that.css(marqueeCss);
    width--;
    go();
};

Really all we’ve done here is allow an argument to be passed in, and each time we hit the left edge we increment the count and check if we’ve done enough passes. When we have we’ll set it back to the original state.

Now you can run this if you want only two passes:

$('h1').marquee({ count: 2 });

Next up, we’ll add some speed, and that’s just a matter of adding speed to the arguments, and default it to 1e1 so that we have our standard. I wont bore you with the code (it’ll be visible in the next parts anyway), but with this you can just run:

$('h1').marquee({ speed: 5 });

Now it goes twice as fast!

Bringing sexy backwards

So the next thing that’d be cool, let’s go from left to right, rather than right to left.

First off we’ll add a new argument property, meaning we can do args like this:

{ 
    leftToRight: true 
}

Here’s the updated plugin:

$.fn.marquee = function(args) {
    var that = $(this);
    var textWidth = that.textWidth(),
        offset = that.width(),
        width = offset,
        css = {
            'text-indent' : that.css('text-indent'),
            'overflow' : that.css('overflow'),
            'white-space' : that.css('white-space')
        },
        marqueeCss = {
            'text-indent' : width,
            'overflow' : 'hidden',
            'white-space' : 'nowrap'
        },
        args = $.extend(true, { count: -1, speed: 1e1, leftToRight: false }, args),
        i = 0,
        stop = textWidth*-1;
    
    function go() {
        if(width == stop) {
            i++;
            if(i == args.count) {
                that.css(css);
                return;
            }
            if(args.leftToRight) {
                width = textWidth*-1;
            } else {
                width = offset;
            }
        }
        that.css('text-indent', width + 'px');
        if(args.leftToRight) {
            width++;
        } else {
            width--;
        }
        setTimeout(go, args.speed);
    };
    if(args.leftToRight) {
        width = textWidth*-1;
        width++;
        stop = offset;
    } else {
        width--;            
    }
    that.css(marqueeCss);
    go();
};

Now what we need to do is change the start position, if we’re going left-to-right we’ll set the initial indent to be the negative width of the text. I’ve also done some refactoring which will have a preset value for the position we need to stop and reset from. By default this will be the negative width of the text when we’re going right to left. When we’re going left to right though we want it to be the full width of the content.

Also the width that we’re tracking either gets increased or decreased, depending what direction we’re going.

So we can go backwards like this:

$('h1').marquee({ leftToRight: true });

Weeeeeeeeee!

Finishing it off with $.Deferred

We looked at $.Deferred() as part of the <blink> tag implementation, so I wont cover it in great depth here, really all we have to do is quite simply, we create our $.Deferred() at the start of the plugin, return the promise at the end, and run resolve when the count is up.

There’s also a reject call to make sure that we can fail if the selector didn’t work.

Conclusion

This brings us to the conclusion of our fun into jQuery again and bringing back a good ol’ friend in the form of marquee.

I’ve got a gist if you want the code and a jsfiddle if you want to play around with it.

Go go gadget 1998 :D.