Submit your widget

iPhone like Sliding Headers effect With jQuery

Created 14 years ago   Views 10012   downloads 1912    Author jqueryfordesigners
iPhone like Sliding Headers effect With jQuery
View DemoDownload
108
Share |

 

One really nice UI effect is when a new heading is just about to replace the existing heading, it pushes the existing heading out of view. It’s very subtle and only really visible if you perform the scroll slowly. Unfortunately, for now, we’re not replicating subtle part of the effect.

Creating the Effect

To create this effect we need to have a fixed position fake header sitting over the content. To do this we’re going have to mess around with the DOM using jQuery. We need to wrap the box with another box of the same height and width and give it position: relative so that our fake header can make use of position: absolute and appear to be fixed.

Now that this is sitting at the very top level, we need to bring the real headers up over the fake header, so that it appears as if they’re pushing the fake out of the way. To achieve this we’ll use a z-index on the real headers that is higher than the fake header. However, this causes it’s own problems.

All the headers now have to be position: absolute to really sit over the fake header, which also means we have to give the element a fixed height and width. This isn’t too much of a problem because we can get this information from the original.

Now that the headers are position absolute, the text that is sitting next to it falls flush against each other, because the newly positioned headers don’t flow in the document. To fix this we need to create a spacer element. In the screencast I mention this might be possible to simplify. You could duplicate the header and insert it after the original header before setting the position: absolute. To be consistent with the screencast, I’ve stuck with creating the spacer.

Finally we need to attach an event handler to the containing box, to say when there is a heading that is exactly aligned with the top of the containing box, to switch in the text from that header in to the fake and move the fake’s z-index to be at least one more than the current header (so it sits topmost).

jQuery

The jQuery job breaks down in to four parts:

  1. Collect the variables we’ll need for the effect
  2. Position and insert the fake header
  3. Tweak the headings to be positioned absolutely and create the spacer
  4. Bind the scroll event

Variables

We need to grab jQueryified versions of the container box and headers. We also need to create a clone of the first heading for the fake header. Finally we initialise a z-index and store the top position of the container. At first it looks like .offset().top would do, but we also need to factor in the margin-top and border-top-width, and this gives us the real top position.

Of course the whole thing is wrapped in the $(document).ready() method to ensure the code only runs once the DOM is ready.

$(document).ready(function () {
  var $container = $('#box');
  var $headers = $container.find('h2');
  var $fakeHeader = $headers.filter(':first').clone();
  var zIndex = 2;
  var containerTop = $container.offset().top + 
    parseInt($container.css('marginTop')) + 
    parseInt($container.css('borderTopWidth'));

Inserting the Fake Header

This is a pretty straight forward process:

  1. Wrap the container in a box, in my case I’ve reused the box class name so that it’s the same width and height, but more importantly: position: relative
  2. Set the CSS on the $fakeHeader variable
  3. Inherit details from the first original header, such as the width and text
$container.wrap('<div class="box" />');
$fakeHeader.css({ 
  zIndex: 1, 
  position: 'absolute', 
  width: $headers.filter(':first').width() 
});
$container.before($fakeHeader.text($headers.filter(':first').text()));

Absolutely Positioning Headings

Since we’re absolutely positioning the headings we’ll need to manually reset the width of the element. We’re also setting a constantly incrementing z-index that the fake header can borrow from to jump above the real heading.

Once the headings are absolutely positioning, they no longer affect the flow of the document, and the adjacent elements now sit flush against each other. Now we need to manually correct this issue just using a spacer element. I’ve created a new empty div element and set the height and width to the outerHeight and outerWidth of the heading. It’s important that we select the outerHeight rather than just height because we need to include the margin around that element.

I’d suggest that if you’re using this technique in a live environment, you can either do it using code (as I have done in this example), or if you’re finding that it doesn’t match up 100%, you can create a class in your CSS that prepares that spacer, then apply the class to the newly inserted div.

$headers.each(function () {
  var $header = $(this);
  var height = $header.outerHeight();
  var width = $header.outerWidth();

  $header.css({
    position: 'absolute',
    width: $header.width(),
    zIndex: zIndex++
  });

  // create the white space
  var $spacer = $header.after('<div />').next();
  $spacer.css({
    height: height,
    width: width
  });
});

Using the Scroll Event to Trigger the Effect

As the user scrolls the overflowing container element, we need to track where our fake header is, and once it passes underneath a real header, the fake header will match the text and use a high z-index.

To achieve this, we bind a scroll event to the container element, and as it is being scrolled, we loop through the headings checking it’s top position.

If the top position is less than the top position of the container (remember we included margin and border width to accurately ascertain this), then we copy that heading’s details across to the fake heading.

The effect that we achieve is that as the fake header passes under the real header, as soon as they’re in the same location visually on the page, the fake header pops over the real header giving the illusion that the heading is now locked in position.

$container.scroll(function () {
  $headers.each(function () {
    var $header = $(this);
    var top = $header.offset().top;
    
    if (top < containerTop) {
      $fakeHeader.text($header.text());
      $fakeHeader.css('zIndex', parseInt($header.css('zIndex'))+1);
    }
  });
});

That’s all we need. As you’ll see with any of these tutorials, we just need to break the task in to smaller tasks and apply the solutions a bit at a time.