JavaScript Scroll

This page contains code that you can use to create a JavaScript scroll. This is a JavaScript marquee that displays scrolling text. The text scrolls in any direction you need; horizontally (from right to left, left to right), or vertically (down to up, or up to down). The JavaScript scroll was originally created by Netscape and is distributed using the Mozilla Public License. It should work on most browsers.

This JavaScript scrolling script comes in two parts: The first part is an external .js file, which contains the scrolling text code. The second part is the customizable part, where you enter your own parameters.

Here are some samples, just to get you started.

Instructions

First of all, download xbMarquee.js.

If you prefer not to download the file, you can copy/paste the contents into a file (see bottom of this article).

Once you've done that, you can use any of the following codes to display your JavaScript scroller. These codes simply provide the parameters for the xbMarquee.js file. These parameters determine things such as the scroll direction, the text to scroll, the speed of the marquee etc. You only need to reference xbMarquee.js once on any page that uses the code (regardless of how many marquees are on that page).

Right to Left

This marquee scrolls from right to left.

Example:

CodeResult

Left to Right

This text scrolls from left to right.

Example:

CodeResult

Faster

This text scrolls from right to left, but much faster than the previous examples.

Example:

CodeResult

Scrolling Up

This text scrolls upwards. The height of the marquee has changed too - from 19px to 100%.

Example:

CodeResult

Scrolling Down

This text scrolls downwards.

Example:

CodeResult

Contents of xbMarquee.js

Below are the full contents of xbMarquee.js. You will need this for the above marquee examples to work.

Copy to Clipboard
/*
 * xbMarquee.js
 * $Revision: 1.2 $ $Date: 2003/02/07 16:04:20 $
 */

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Netscape code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2002
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s): Doron Rosenberg <doron@netscape.com>
 *                 Bob Clary <bclary@netscape.com>
 *
 * ***** END LICENSE BLOCK ***** */

document.writeln('<style type="text/css">');
document.writeln('	div.marqueecenter1 { text-align: center; }');
document.writeln('	div.marqueecenter2 { margin-left: auto; margin-right: auto; }');
document.writeln('	div.marqueeleft1 { text-align: left; }');
document.writeln('	div.marqueeleft2 { margin-left: 0; margin-right: auto; }');
document.writeln('	div.marqueeright1 { text-align: right; }');
document.writeln('	div.marqueeright2 { margin-left: auto; margin-right: 0; }');
document.writeln('</style>');

function xbMarquee(id, height, width, scrollAmount, scrollDelay, direction, behavior, html)
{
  this.id            = id;
  this.scrollAmount  = scrollAmount ? scrollAmount : 6;
  this.scrollDelay   = scrollDelay ? scrollDelay : 85;
  this.direction     = direction ? direction.toLowerCase() : 'left';  
  this.behavior      = behavior ? behavior.toLowerCase() : 'scroll';  
//  this.name          = 'xbMarquee_' + (++xbMarquee._name);
  this.name          = id;
  this.runId         = null;
  this.html          = html;
  this.isHorizontal = ('up,down'.indexOf(this.direction) == -1);

  if (typeof(height) == 'number')
  {
    this.height = height;
    this.heightUnit = 'px';
  }
  else if (typeof(height) == 'string')
  {
    this.height = parseInt('0' + height, 10);
    this.heightUnit = height.toLowerCase().replace(/^[0-9]+/, '');
  }
  else
  {
    this.height = 100;
    this.heightUnit = 'px';
  }

  if (typeof(width) == 'number')
  {
    this.width = width;
    this.widthUnit = 'px';
  }
  else if (typeof(width) == 'string')
  {
    this.width = parseInt('0' + width, 10);
    this.widthUnit = width.toLowerCase().replace(/^[0-9]+/, '');
  }
  else
  {
    this.width = 100;
    this.widthUnit = 'px';
  }

  // xbMarquee UI events
  this.onmouseover   = null;
  this.onmouseout    = null;
  this.onclick       = null;
  // xbMarquee state events
  this.onstart       = null;
  this.onbounce      = null;

  var markup = '';

  if (document.layers)
  {
    markup = '<ilayer id="' + this.id + 'container" name="' + this.id + 'container" ' +
             'height="' + height + '" ' +
             'width="' + width + '"  ' +
             'clip="' + width + ', ' + height + '" ' +
             '>' + 
             '<\/ilayer>';            
    
  }
  else if (document.body && typeof(document.body.innerHTML) != 'string')
  {
    markup = '<div id="' + this.id + 'container" name="' + this.id + 'container" ' +
             'style="position: relative; overflow: scroll; ' + 
             'height: ' + this.height + this.heightUnit + '; ' +
             'width: ' + this.width + this.widthUnit + '; ' +
             'clip: rect(0px, ' + this.width + this.widthUnit + ', ' + this.height + this.heightUnit + ', 0px); ' +
             '">' + 
             '<div id="' + this.id + '" style="position:relative;' + 
             (this.isHorizontal ? 'width:0px;' : '') + // if we scroll horizontally, make the text container as small as possible
             '">' +
             (this.isHorizontal ? '<nobr>' : '') +
             this.html +
             (this.isHorizontal ? '<\/nobr>' : '') +
             '<\/div>' +
             '<\/div>';             
    
  }
  else 
  {
    markup = '<div id="' + this.id + 'container" name="' + 
             this.id + 'container" ' +
             'style="position: relative; overflow: hidden; overflowY: visible; ' + 
             'height: ' + this.height + this.heightUnit + '; ' +
             'width: ' + this.width + this.widthUnit + '; ' +
             'clip: rect(0px, ' + this.width + this.widthUnit + ', ' + this.height + this.heightUnit + ', 0px); ' +
	             '">' + 
             '<\/div>';             
   
  }
  document.write(markup);  

  window[this.name] = this;

}

// Class Properties/Methods

xbMarquee._name = -1;

xbMarquee._getInnerSize = function(elm, propName)
{
  var val = 0;

  if (document.layers)
  {
    // navigator 4
    val = elm.document[propName];    
  }
  else if (elm.style && typeof(elm.style[propName]) == 'number')
  {
    // opera
    // bug in Opera 6 width/offsetWidth. Use clientWidth
    if (propName == 'width' && typeof(elm.clientWidth) == 'number')
      val = elm.clientWidth;
    else
      val =  elm.style[propName];
  }
  else
  {
    //mozilla and IE
    switch (propName)
    {
    case 'height':
       if (typeof(elm.offsetHeight) == 'number')
         val =  elm.offsetHeight;
       break;
    case 'width':
       if (typeof(elm.offsetWidth) == 'number')
       {
         val = elm.offsetWidth;                  
       }
       break;
    }
  }

  return val;

};

xbMarquee.getElm = function(id)
{
  var elm = null;
  if (document.getElementById)
  {
    elm = document.getElementById(id);
  }
  else
  {
    elm = document.all[id];
  }
  return elm;
}

xbMarquee.dispatchUIEvent = function (event, marqueeName, eventName)
{
  var marquee = window[marqueeName];
  var eventAttr = 'on' + eventName;
  if (!marquee)
  {
    return false;
  }

  if (!event && window.event)
  {
    event = window.event;
  }

  switch (eventName)
  {
  case 'mouseover':
  case 'mouseout':
  case 'click':
    if (marquee[eventAttr])
      return marquee['on' + eventName](event);
  }

  return false;
};

xbMarquee.createDispatchEventAttr = function (marqueeName, eventName)
{
  return 'on' + eventName + '="xbMarquee.dispatchUIEvent(event, \'' + marqueeName + '\', \'' + eventName + '\')" ';
};

// Instance properties/methods

xbMarquee.prototype.start = function ()
{
  var markup = '';

  this.stop();

  if (!this.dirsign)
  {
    if (!document.layers)
    {
      this.containerDiv = xbMarquee.getElm(this.id + 'container')

      if (typeof(this.containerDiv.innerHTML) != 'string')
      {
        return;
      }

      // adjust the container size before inner div is filled in
      // so IE will not hork the size of percentage units 
      var parentNode    = null;
      if (this.containerDiv.parentNode)
        parentNode = this.containerDiv.parentNode;
      else if (this.containerDiv.parentElement)
        parentNode = this.containerDiv.parentElement;

      if (parentNode && 
          typeof(parentNode.offsetHeight) == 'number' && 
          typeof(parentNode.offsetWidth) == 'number')
      {
        if (this.heightUnit == '%')
        {
          this.containerDiv.style.height = 
          parentNode.offsetHeight * (this.height/100) + 'px';
        }

        if (this.widthUnit == '%')
        {
          this.containerDiv.style.width = 
          parentNode.offsetWidth * (this.width/100) + 'px';
        }
      }

      markup += '<div id="' + this.id + '" name="' + this.id + '" ' +
        'style="position:relative; visibility: hidden;' +
        //(this.isHorizontal ? 'width:0px;' : '') + // if we scroll horizontally, make the text container as small as possible
        '" ' +
        xbMarquee.createDispatchEventAttr(this.name, 'mouseover') +
        xbMarquee.createDispatchEventAttr(this.name, 'mouseout') +
        xbMarquee.createDispatchEventAttr(this.name, 'click') +
        '>' +
        (this.isHorizontal ? '<nobr>' : '') +
        this.html +
        (this.isHorizontal ? '<\/nobr>' : '') +
        '<\/div>';

      this.containerDiv.innerHTML = markup;
      this.div                    = xbMarquee.getElm(this.id);
      this.styleObj     = this.div.style;      

    }
    else /* if (document.layers) */
    {
      this.containerDiv = document.layers[this.id + 'container'];
      markup = 
        '<layer id="' + this.id + '" name="' + this.id + '" top="0" left="0" ' +
        xbMarquee.createDispatchEventAttr(this.name, 'mouseover') +
        xbMarquee.createDispatchEventAttr(this.name, 'mouseout') +
        xbMarquee.createDispatchEventAttr(this.name, 'click') +
        '>' +
        (this.isHorizontal ? '<nobr>' : '') + 
        this.html +
        (this.isHorizontal ? '<\/nobr>' : '') +
        '<\/layer>';

      this.containerDiv.document.write(markup);
      this.containerDiv.document.close();
      this.div          = this.containerDiv.document.layers[this.id];
      this.styleObj     = this.div;
    }

    if (this.isHorizontal && this.height < xbMarquee._getInnerSize(this.div, 'height') )
    {
      
      this.height = xbMarquee._getInnerSize(this.div, 'height')
      this.containerDiv.style.height = this.height + this.heightUnit;
      this.containerDiv.style.clip = 'rect(0px, ' + this.width + this.widthUnit + ', ' + this.height + this.heightUnit + ', 0px)';
    }


    // Start must not run until the page load event has fired
    // due to Internet Explorer not setting the height and width of 
    // the dynamically written content until then
    switch (this.direction)
    {
    case 'down':
      this.dirsign = 1;
      this.startAt = -xbMarquee._getInnerSize(this.div, 'height');
      this._setTop(this.startAt);

      if (this.heightUnit == '%')
        this.stopAt = this.height * xbMarquee._getInnerSize(this.containerDiv, 'height') / 100;
      else
        this.stopAt  = this.height;

      break;

    case 'up':
      this.dirsign = -1;

      if (this.heightUnit == '%')
        this.startAt = this.height * xbMarquee._getInnerSize(this.containerDiv, 'height') / 100;
      else     
        this.startAt = this.height;

      this._setTop(this.startAt);
      this.stopAt  = -xbMarquee._getInnerSize(this.div, 'height');      

      break;

    case 'right':
      this.dirsign = 1;
      this.startAt = -xbMarquee._getInnerSize(this.div, 'width');
      this._setLeft(this.startAt);

      if (this.widthUnit == '%')
        this.stopAt = this.width * xbMarquee._getInnerSize(this.containerDiv, 'width') / 100;
      else    
        this.stopAt  = this.width;

      break;

    case 'left':
    default:
      this.dirsign = -1;

	if (this.widthUnit == '%')
		this.startAt = this.width * xbMarquee._getInnerSize(this.containerDiv, 'width') / 100;
	else  
		this.startAt = this.width        

	this._setLeft(this.startAt);
      
	
	// this.stopAt  = -xbMarquee._getInnerSize(this.div,'width')*2;
	
	// this method does not work very well with FireFox.  offsetWidth property used in this function returns the absolute width of the div container
	// instead of the new offsetWidth when innerHTML is added or when the div becomes wider. To overcome this a new span element is added to 
	// the document body to measure the new offsetwidth and then it is removed.
	
	var temp_span = document.createElement('span');     

	temp_span.id = 'span_' + this.div.id;
	temp_span.innerHTML = this.html;

	document.body.appendChild(temp_span);                

	this.stopAt = - temp_span.firstChild.firstChild.offsetWidth;	

	document.body.removeChild(temp_span);            
      	
      break;
    }
    this.newPosition          = this.startAt;
    this.styleObj.visibility = 'visible'; 
  }

  this.newPosition += this.dirsign * this.scrollAmount;

  if ( (this.dirsign == 1  && this.newPosition > this.stopAt) ||
       (this.dirsign == -1 && this.newPosition < this.stopAt) )
  {
    if (this.behavior == 'alternate')
    {
      if (this.onbounce)
      {
        // fire bounce when alternate changes directions
        this.onbounce();
      }
      this.dirsign = -this.dirsign;
      var temp     = this.stopAt;
      this.stopAt  = this.startAt;
      this.startAt = temp;
    }
    else
    {
      // fire start when position is a start
      if (this.onstart)
      {
        this.onstart();
      }
      this.newPosition = this.startAt;
    }
  }
  
  switch(this.direction)
  {
    case 'up': 
    case 'down':
      this._setTop(this.newPosition);
      break;

    case 'left': 
    case 'right':
    default:
      this._setLeft(this.newPosition);
      
      break;
  }
  this.runId = setTimeout(this.name + '.start()', this.scrollDelay);
};

xbMarquee.prototype.stop = function ()
{
  if (this.runId)
    clearTimeout(this.runId);
    
  this.runId = null;
};

xbMarquee.prototype.setInnerHTML = function (html)
{
  if (typeof(this.div.innerHTML) != 'string')
  {
    return;
  }

  var running = false;
  if (this.runId)
  {
    running = true;
    this.stop();
  }
  this.html = html;
  this.dirsign = null;
  if (running)
  {
    this.start();
  }
};

// fixes standards mode in gecko
// since units are required

if (document.layers)
{
  xbMarquee.prototype._setLeft = function (left)
  {
    this.styleObj.left = left;    
  };

  xbMarquee.prototype._setTop = function (top)
  {
    this.styleObj.top = top;    
  };
}
else
{
  xbMarquee.prototype._setLeft = function (left)
  {
    this.styleObj.left = left + 'px';    
  };

  xbMarquee.prototype._setTop = function (top)
  {
    this.styleObj.top = top + 'px';    
  };
}

Enjoy this page?