Patching Holes in CSS with jQuery: A Practical Example

Patch on bag

Today we look at a real-world problem that CSS can’t easily solve, and examine the jQuery fix.

In my front-end development work, I often use JavaScript—with jQuery, the “make JavaScript easier” library—as a tool of last resort: a way to impose styling on a site when CSS isn’t up to the task, or would be too complicated to implement.

A big piece of knowing when to turn to jQuery is understanding the kinds of problems that CSS can’t solve. Today I’ll give you an example from WPShout itself, explain why CSS isn’t up to the problem, and explore the jQuery fix in detail.

If you’d like a gentle introduction to jQuery itself, here’s a quick conceptual overview and a good beginner tutorial. Our real-life example isn’t too much more complicated than the tutorial, so come back soon!

The Problem: A Spillover Nav Menu

One of the more unique pieces of WPShout’s layout is its side nav menu. We like it a lot, but it’s also the source of the styling complexity behind today’s example.

Our Nav Menu is Absolutely Positioned, Which Could Cause Spillover

In addition to its generally funny shape, our nav menu actually sits partly on top of the main content container, where the article text goes:

WPShout nav menu

Click to enlarge

To manage this and a few other layout complexities, our nav menu needs to be absolutely positioned. This CSS attribute, position: absolute;, takes an element out of the main layout flow of an HTML page. The element can still place itself in relationship to other DOM elements, but those other elements ignore it entirely in their own layout decisions.

Because elements are ignoring each other, you can get some really problematic results. In particular, what happens if our nav menu is taller than the article next to it? It gets ugly, with elements improperly overlapping, which is what I mean by “spillover”:

Absolute positioning spillover

Click to enlarge

We Need a Min-Height, but Which Min-Height to Choose?

What we want, even for a very short article, is that the footer only shows up below the nav menu:

Sticky footer

Click to enlarge

However, there’s no way in CSS to say “Only show up below this absolutely positioned element,” because part of absolute positioning is that other elements ignore the absolutely positioned element in their own layout decisions.

So what we need to do is set a minimum height for the container of the main article, which in this case is a div of class main-content:

Min-height with absolutely positioned element

Click to enlarge

We need an ongoing survey of the menu’s height, and to set our main container’s minimum height equal to that. CSS can’t do that, but jQuery can.

What does this min-height need to be? It needs to be “Whatever the nav menu’s actual height is”—which we don’t know. We might add things to that nav menu or remove them, and of course different browsers, browser widths, and devices will render the menu’s height significantly differently. Not only that, because browser width changes layout details, a user who resizes her browser might see multiple different nav menu heights in the same page load!

What we need is an ongoing survey of the menu’s height, and to set .main-content equal to that. CSS can’t do that, but jQuery can.

The Fix: page-min-height.js

To solve this problem, I wrote a jQuery script, page-min-height.js, into WPShout’s theme. I enqueued it from the theme’s functions.php; I won’t show that process here, so if you need to know about enqueueing scripts and styles, check out our article on the subject.

Here’s page-min-height.js itself:

( function( $ ) {
	// 1. Define variables
	var navMenu = '.primary-navigation';
	var pageContent = '.main-content';
	var gap = parseInt( $( 'html' ).css( 'font-size' ), 10 ) * 2;

	// 2. Define function to give min-height
	function setPageMin() {
		var height = $( navMenu ).height();
		$( pageContent ).css( 'min-height', height + gap );
	}

	$( window ).load( function() {
		// 3. Run function on first window load
		setPageMin();

		// 4. Run function every 120 MS when window resized
		$( window ).resize( function() {
			setTimeout( function() {
	      			setPageMin();
			}, 120);
		});
	});
})(jQuery);

I’ll explain this code in four parts, corresponding to the four sections above.

1. Defining Variables

In this section, we define some variables whose values we’ll be using throughout the rest of the script:

  1. navMenu is the class name of the navigation menu, here .primary-navigation.
  2. navMenu is the class name of the main content container, here .main-content.
  3. gap looks confusing, but it’s an integer equal to “two times the main font size, in pixels, of the HTML document.” I use this number to create a two-line “gap” below the nav menu, so that it can never actually touch the footer.

As a note, ( function( $ )})(jQuery); on the first and last lines of the file is jQuery boilerplate, necessary in WordPress, so that we can use “$” to mean “jQuery” throughout the file. See here for more information.

2. Defining setPageMin()

This section defines a function, setPageMin(), that does two things:

  1. Find the current height, in pixels, of the object specified by navMenu—which is .primary-navigation, the nav menu itself.
  2. Set pageContent—the .main-content element—to have a min-height equal to navMenu‘s height plus the gap we previously defined.

3. Running setPageMin() on Window Load

$( window ).load( function() { }); sets up a function that will run the instant all DOM objects have fully displayed themselves in the browser. This is the point at which the nav menu has a height that we can reliably measure, so the first thing we do at this point is to run setPageMin().

4. Running setPageMin() on Window Resize

We’re not done yet, though. What if a user resizes her browser window? This could easily make the nav menu taller or shorter, causing it to overlap—or fall far short of—the boundary we established with our first call to setPageMin().

So we need to listen for a user resizing her window, and run a function when she does so. This is what $( window ).resize( function() { }); does.

What’s inside that function? Well, the easiest thing would just be a call to setPageMin(), as follows:.

$( window ).resize( function() {
	setPageMin();
});

However, JavaScript’s very jittery. The code above would run setPageMin(); every single millisecond in which the user’s resizing her browser. That’s overkill, and it would likely make the page behave choppily.

Instead, we’re just going to run setPageMin(); once every 120 milliseconds. That’s about eight times a second—plenty quick for humans, and much less intensive. That’s what the following code does:

setTimeout( function() {
	setPageMin();
}, 120);

If you’d like more information on setTimeout, I’d encourage you to read up! It’s a wonderful function with an amazing number of uses.

In some, this section runs setPageMin(); about eight times a second whenever a user’s resizing her browser window—meaning that the min-height we set for our main content never gets out of sync with the actual measured height of the nav menu. Cool!

Thanks, jQuery!

This week, I’ve gone into detail on a real-world limitation of CSS, and a practical jQuery solution. I hope you’re now more comfortable with jQuery, and I hope you’ve got some new ideas about using jQuery to patch holes in your own projects. Thanks for reading!

Image credit: rmkoske


3 Responses

Comments

  • OzzyLovesBabyShampoo says:

    What is spillover? I can’t visualize the problem you fixed here.

    • fredclaymeyer says:

      Thank you for asking! By “spillover” I mean two elements overlapping in a very ugly way – specifically how the nav menu just spills into the footer in the image below. It’s definitely my phrase rather than a standard industry term. 🙂

      • OzzyLovesBabyShampoo says:

        Oh, cool, I definitely have to read this article, then. I have had that happen before and it blows.