jQuery Scrolling Menu

Jan 30 2012

When implementing the scrolling menu for Chrome and Firefox, I prototyped the menu using fiddlesalad’s LESS CSS editor. Then, I copied the compiled CSS and started coding with a straight forward procedural approach.

function displayMenu() {
    var selected = [$('#menu ul:first li.selected'), $('#menu ul:eq(1) li.selected')];
    if (selected[0].length) $('#menu div:first').scrollTo(selected[0]);
    if (selected[1].length) $('#menu div:eq(1)').scrollTo(selected[1]);
    $('#menu ul:first').parallax({
        takeoverFactor: .2,
        mouseport: $('#menu ul:first').parent(),
        xparallax: false
    });
    $('#menu ul:eq(1)').parallax({
        takeoverFactor: .2,
        mouseport: $('#menu ul:eq(1)').parent(),
        xparallax: false
    });
    $('#menu ul:first li').click(function () {
        window.location.assign(ajax_url + 'calendar/' + $(this).text() + '/');
    });
    $('#menu ul:eq(1) li').click(function () {
        var course = $(this).text().split(' ');
        window.location.assign(ajax_url + 'calendar/' + course[0] + '/' + course[1] + '/');
    });
}

$('#menu ul:first li.last').livequery(displayMenu);

Later, I noticed that the performance can be improved by caching jQuery results. Also, many lines of code were repetitive. Before moving onto the more challenging part of the menu design, scrolling to the current location, I made it object oriented using plain JavaScript.

function Menu(selector) {
    this.selector = selector; // #menu ul:first
    this.$list = $(this.selector);
    this.$container = this.$list.parent();
    this.display = bindMethod(this, function () {
        var selected = this.$list.find('.selected').prev(),
            prevCount = 0;
        var prev = selected.prev();
        while (selected.length && prevCount++ < 3)
            selected = selected.prev();
        // only course pages have enough height, otherwise the courses at the top cannot be reached
        if (selected.length && window.location.pathname.split('/').length == 5)
            this.$container.scrollTo(selected);
        this.$list.parallax({
            takeoverFactor: .2,
            mouseport: this.$container,
            xparallax: false
        });
    });
    this.$list.find('.last').livequery(this.display);
    this.scrollTop = bindMethod(this, function () {
        if (this.activated)
            this.$container.scrollTo(this.$list.find('li:first'));
    });
    this.$container.mouseenter(bindMethod(this, function () {
        this.activated = true;
    }))
    this.width = function () {
        return this.$container.width();
    }
}

Writing classes in JavaScript brings unexpected quirks, such as this being set to an HTML Element. However, using an additional helper, bindMethod, this problem is avoided. I used the scrollTop method to scroll to the top of the menu:

var deptMenu = new Menu('#menu ul:first'), courseMenu = new Menu('#menu ul:eq(1)');
var $deptTop = $('#menu').prev().find('div:first'), $courseTop = $('#menu').prev().find('div:eq(1)');
$deptTop.width(deptMenu.width());
$courseTop.width(courseMenu.width());
$deptTop.hover(deptMenu.scrollTop);
$courseTop.hover(courseMenu.scrollTop);

 

No responses yet