Friday, August 19, 2011

Best Floating Menu


Absolute Floating Menu

Many web pages don't fit on most users' screens. The visitors have to scroll to read the page contents. Such scrolling however hides the navigation menus (or a shopping cart contents) usually located at the top of the page.

The javascript shown here allows to create dynamic menus which move along with scrolling. Such floating menu will be always visible on screen. The effect is achieved my moving an absolutely-positioned or relatively-positioned DIV box containing the menu markup.

The floating DIV will visibly move towards the specified viewing area spot, slowing as it moves closer to it and finishing the move with a visible snap. Such animation will definitely draw user attention to the floating menu.

Also available:

Features include:

  • Free-floating DIV box with position:absolute (see demo on this page)
  • Floating DIV box confined within specific area (demo):
  • box with position:relative confined within a container area that doesn't have any other children nodes.
  • box with position:absolute confined within a container area having position:relative.
  • Complex containers:
  • Experimental support for floating DIV confined within IFRAME container.
  • Experimental support for floating DIV confined within DIV container with scrollbar.

Floating DIV demonstration

Look for the floating box somewhere on the page.

Click the buttons to move the floating menu to different corners:

Using the javascript

To use the script perform the following steps:

  • Download version 1.7 of the script code and put it on your server as a separate file and reference it in HTML HEAD:
  1. <script type="text/javascript" src="specify script file URL here">  
  2. </script>  
  • Create a DIV element with id of 'floatdiv' which contains the menu markup and add script start code.
  • If you want your DIV box to move around the window freely, put it within BODY tag.
  • If you want to confine it to client area of specific container, place it within this container. Make sure that floating DIV box or container has position:relative.
For example:
  1. <div id="floatdiv" style="  
  2.     position:absolute;  
  3.     width:200px;height:50px;top:10px;right:10px;  
  4.     padding:16px;background:#FFFFFF;  
  5.     border:2px solid #2266AA;  
  6.     z-index:100">  
  7. This is a floating javascript menu.  
  8. </div>  
  9.   
  10. <script type="text/javascript">  
  11.     floatingMenu.add('floatdiv',  
  12.         {  
  13.             // Represents distance from left or right browser window  
  14.             // border depending upon property used. Only one should be  
  15.             // specified.  
  16.             // targetLeft: 0,  
  17.             targetRight: 10,  
  18.   
  19.             // Represents distance from top or bottom browser window  
  20.             // border depending upon property used. Only one should be  
  21.             // specified.  
  22.             targetTop: 10,  
  23.             // targetBottom: 0,  
  24.   
  25.             // Uncomment one of those if you need centering on  
  26.             // X- or Y- axis.  
  27.             // centerX: true,  
  28.             // centerY: true,  
  29.   
  30.             // Remove this one if you don't want snap effect  
  31.             snap: true  
  32.         });  
  33. </script>  
  • Change the targetTop and targetRight variables. They represent the desired distance from the top and right window borders respectively.
  • Alternatively, you can use targetBottom instead of targetTop or targetLeft instead oftargetRight.
  • For centering use centerX: true or centerY: true. Values of target for respective axis will be ignored.
  • To remove initial movement set DIV's position properties (top or bottomleft or right) to the same values as target* variables.
  • Make sure your page has DOCTYPE set. It doesn't matter if it is HTML 4.01 or XHTML, but the script will fail to work if Internet Explorer is in quirks mode.
  • Be sure to verify page design with the Javascript turned off.

See Also

An alternative:

  • Fixed & Sticky Menu will pin the menu to the spot if the browser supports it, moving it if not.

Browser compatibility

The javascript snippet above was tested on the following user agents:

MozillaFirefox 3.0.xOk.
MicrosoftInternet Explorer 8.0Ok.
KHTMLGoogle Chrome 7.0.517.44Ok.
Safari 5.0.2Ok.
No Javascript or
Javascript turned off
AnyMenu will be absolutely positioned on initial style leftand top values and will not move.

JS File Source

/* Script by: www.jtricks.com
 * Version: 1.7 (20110408)
 * Latest version: www.jtricks.com/javascript/navigation/floating.html
 */
var floatingMenu =
{
    hasInner: typeof(window.innerWidth) == 'number',
    hasElement: typeof(document.documentElement) == 'object'
        && typeof(document.documentElement.clientWidth) == 'number'
};

var floatingArray =
[
];

floatingMenu.add = function(obj, options)
{
    var name;
    var menu;

    if (typeof(obj) === "string")
        name = obj;
    else
        menu = obj;
        

    if (options == undefined)
    {
        floatingArray.push( 
            {
                id: name,
                menu: menu,

                targetLeft: 0,
                targetTop: 0,

                distance: .07,
                snap: true
            });
    }
    else
    {
        floatingArray.push( 
            {
                id: name,
                menu: menu,

                targetLeft: options.targetLeft,
                targetRight: options.targetRight,
                targetTop: options.targetTop,
                targetBottom: options.targetBottom,

                centerX: options.centerX,
                centerY: options.centerY,

                prohibitXMovement: options.prohibitXMovement,
                prohibitYMovement: options.prohibitYMovement,

                distance: options.distance != undefined ? options.distance : .07,
                snap: options.snap,
                ignoreParentDimensions: options.ignoreParentDimensions,

                scrollContainer: options.scrollContainer,
                scrollContainerId: options.scrollContainerId
            });
    }
};

floatingMenu.findSingle = function(item)
{
    if (item.id)
        item.menu = document.getElementById(item.id);

    if (item.scrollContainerId)
        item.scrollContainer = document.getElementById(item.scrollContainerId);
};

floatingMenu.move = function (item)
{
    if (!item.prohibitXMovement)
    {
        item.menu.style.left = item.nextX + 'px';
        item.menu.style.right = '';
    }

    if (!item.prohibitYMovement)
    {
        item.menu.style.top = item.nextY + 'px';
        item.menu.style.bottom = '';
    }
};

floatingMenu.scrollLeft = function(item)
{
    // If floating within scrollable container use it's scrollLeft
    if (item.scrollContainer)
        return item.scrollContainer.scrollLeft;

    var w = window;

    // Find top window scroll parameters if we're IFRAMEd
    while (w != w.parent)
        w = w.parent;

    return this.hasInner
        ? w.pageXOffset  
        : this.hasElement  
          ? w.document.documentElement.scrollLeft  
          : w.document.body.scrollLeft;
};

floatingMenu.scrollTop = function(item)
{
    // If floating within scrollable container use it's scrollTop
    if (item.scrollContainer)
        return item.scrollContainer.scrollTop;

    var w = window;

    // Find top window scroll parameters if we're IFRAMEd
    while (w != w.parent)
        w = w.parent;

    return this.hasInner
        ? w.pageYOffset
        : this.hasElement
          ? w.document.documentElement.scrollTop
          : w.document.body.scrollTop;
};

floatingMenu.windowWidth = function()
{
    return this.hasElement
        ? document.documentElement.clientWidth
        : document.body.clientWidth;
};

floatingMenu.windowHeight = function()
{
    if (floatingMenu.hasElement && floatingMenu.hasInner)
    {
        // Handle Opera 8 problems
        return document.documentElement.clientHeight > window.innerHeight
            ? window.innerHeight
            : document.documentElement.clientHeight
    }
    else
    {
        return floatingMenu.hasElement
            ? document.documentElement.clientHeight
            : document.body.clientHeight;
    }
};

floatingMenu.documentHeight = function()
{
    var innerHeight = this.hasInner
        ? window.innerHeight
        : 0;

    var body = document.body,
        html = document.documentElement;

    return Math.max(
        body.scrollHeight,
        body.offsetHeight, 
        html.clientHeight,
        html.scrollHeight,
        html.offsetHeight,
        innerHeight);
};

floatingMenu.documentWidth = function()
{
    var innerWidth = this.hasInner
        ? window.innerWidth
        : 0;

    var body = document.body,
        html = document.documentElement;

    return Math.max(
        body.scrollWidth,
        body.offsetWidth, 
        html.clientWidth,
        html.scrollWidth,
        html.offsetWidth,
        innerWidth);
};

floatingMenu.calculateCornerX = function(item)
{
    var offsetWidth = item.menu.offsetWidth;

    if (item.centerX)
        return this.scrollLeft(item) + (this.windowWidth() - offsetWidth)/2;

    var result = this.scrollLeft(item) - item.parentLeft;
    if (item.targetLeft == undefined)
    {
        result += this.windowWidth() - item.targetRight - offsetWidth;
    }
    else
    {
        result += item.targetLeft;
    }
        
    if (document.body != item.menu.parentNode
        && result + offsetWidth >= item.confinedWidthReserve)
    {
        result = item.confinedWidthReserve - offsetWidth;
    }

    if (result < 0)
        result = 0;

    return result;
};

floatingMenu.calculateCornerY = function(item)
{
    var offsetHeight = item.menu.offsetHeight;

    if (item.centerY)
        return this.scrollTop(item) + (this.windowHeight() - offsetHeight)/2;

    var result = this.scrollTop(item) - item.parentTop;
    if (item.targetTop === undefined)
    {
        result += this.windowHeight() - item.targetBottom - offsetHeight;
    }
    else
    {
        result += item.targetTop;
    }

    if (document.body != item.menu.parentNode
        && result + offsetHeight >= item.confinedHeightReserve)
    {
        result = item.confinedHeightReserve - offsetHeight;
    }

    if (result < 0)
        result = 0;
        
    return result;
};

floatingMenu.computeParent = function(item)
{
    if (item.ignoreParentDimensions)
    {
        item.confinedHeightReserve = this.documentHeight();
        item.confinedWidthReserver = this.documentWidth();
        item.parentLeft = 0;  
        item.parentTop = 0;  
        return;
    }

    var parentNode = item.menu.parentNode;
    var parentOffsets = this.offsets(parentNode, item);
    item.parentLeft = parentOffsets.left;
    item.parentTop = parentOffsets.top;

    item.confinedWidthReserve = parentNode.clientWidth;

    // We could have absolutely-positioned DIV wrapped
    // inside relatively-positioned. Then parent might not
    // have any height. Try to find parent that has
    // and try to find whats left of its height for us.
    var obj = parentNode;
    var objOffsets = this.offsets(obj, item);
    while (obj.clientHeight + objOffsets.top
           < item.menu.offsetHeight + parentOffsets.top)
    {
        obj = obj.parentNode;
        objOffsets = this.offsets(obj, item);
    }

    item.confinedHeightReserve = obj.clientHeight
        - (parentOffsets.top - objOffsets.top);
};

floatingMenu.offsets = function(obj, item)
{
    var result =
    {
        left: 0,
        top: 0
    };

    if (obj === item.scrollContainer)
        return;

    while (obj.offsetParent && obj.offsetParent != item.scrollContainer)
    {  
        result.left += obj.offsetLeft;  
        result.top += obj.offsetTop;  
        obj = obj.offsetParent;
    }  

    if (window == window.parent)
        return result;

    // we're IFRAMEd
    var iframes = window.parent.document.body.getElementsByTagName("IFRAME");
    for (var i = 0; i < iframes.length; i++)
    {
        if (iframes[i].contentWindow != window)
           continue;

        obj = iframes[i];
        while (obj.offsetParent)  
        {  
            result.left += obj.offsetLeft;  
            result.top += obj.offsetTop;  
            obj = obj.offsetParent;
        }  
    }

    return result;
};

floatingMenu.doFloatSingle = function(item)
{
    this.findSingle(item);

    var stepX, stepY;

    this.computeParent(item);

    var cornerX = this.calculateCornerX(item);

    var stepX = (cornerX - item.nextX) * item.distance;
    if (Math.abs(stepX) < .5 && item.snap
        || Math.abs(cornerX - item.nextX) == 1)
    {
        stepX = cornerX - item.nextX;
    }

    var cornerY = this.calculateCornerY(item);

    var stepY = (cornerY - item.nextY) * item.distance;
    if (Math.abs(stepY) < .5 && item.snap
        || Math.abs(cornerY - item.nextY) == 1)
    {
        stepY = cornerY - item.nextY;
    }

    if (Math.abs(stepX) > 0 ||
        Math.abs(stepY) > 0)
    {
        item.nextX += stepX;
        item.nextY += stepY;
        this.move(item);
    }
};

floatingMenu.fixTargets = function()
{
};

floatingMenu.fixTarget = function(item)
{
};

floatingMenu.doFloat = function()
{
    this.fixTargets();
    for (var i=0; i < floatingArray.length; i++)
    {
        this.fixTarget(floatingArray[i]);
        this.doFloatSingle(floatingArray[i]);
    }
    setTimeout('floatingMenu.doFloat()', 20);
};

floatingMenu.insertEvent = function(element, event, handler)
{
    // W3C
    if (element.addEventListener != undefined)
    {
        element.addEventListener(event, handler, false);
        return;
    }

    var listener = 'on' + event;

    // MS
    if (element.attachEvent != undefined)
    {
        element.attachEvent(listener, handler);
        return;
    }

    // Fallback
    var oldHandler = element[listener];
    element[listener] = function (e)
        {
            e = (e) ? e : window.event;
            var result = handler(e);
            return (oldHandler != undefined) 
                && (oldHandler(e) == true)
                && (result == true);
        };
};

floatingMenu.init = function()
{
    floatingMenu.fixTargets();

    for (var i=0; i < floatingArray.length; i++)
    {
        floatingMenu.initSingleMenu(floatingArray[i]);
    }

    setTimeout('floatingMenu.doFloat()', 100);
};

// Some browsers init scrollbars only after
// full document load.
floatingMenu.initSingleMenu = function(item)
{
    this.findSingle(item);
    this.computeParent(item);
    this.fixTarget(item);
    item.nextX = this.calculateCornerX(item);
    item.nextY = this.calculateCornerY(item);
    this.move(item);
};

floatingMenu.insertEvent(window, 'load', floatingMenu.init);


// Register ourselves as jQuery plugin if jQuery is present
if (typeof(jQuery) !== 'undefined')
{
    (function ($)
    {
        $.fn.addFloating = function(options)
        {
            return this.each(function()
            {
                floatingMenu.add(this, options);
            });
        };
    }) (jQuery);
}

0 comments:

Post a Comment