Monthly Archives: December 2007

Pushing a shopping cart full of tea with CSS and JavaScript

While brainstorming for a recent project, I toyed with the idea of having a shopping cart that follows the user on a scrolling page. The idea was partly inspired by Derek Allard’s entry, “Conditionally Sticky Sidebar“.

The idea would be that the cart would only start following the scrolling page if it had a product in it; an empty cart would not. I ended up not using the feature for a variety fo reasons, but I thought I could still share the logic behind the effect.

For those who like to skip ahead: view the final example or download a zip with all the html, CSS, JavaScript, and supporting images.

First, a storefront

To start off, here’s example 1 – a very basic storefront. In this example, our storefront sells tea. The shopping cart, pictured with some items inside, is on the right. The shopping cart is 200 pixels wide, and I’ve reserved the right-side 200 pixels all the way down the page to serve as the shopping cart ‘aisle’.

HTML

<div id="shoppingCartAisle">
  <div id="shoppingCart">
    <h3>Shopping Cart</h3>
    <ul>
      <li>product 1</>
      <li>product 2</>
      <li>product 3</>
    </ul>
  </div>
</div>

CSS

...
#shoppingCartAisle {
  width:200px;
  float:left;
}
#shoppingCartAisle #shoppingCart {
  width:200px;
  position:absolute;
  top:0;
  background:transparent url(images/bottom.gif) no-repeat bottom left;
}
#shoppingCartAisle #shoppingCart h3 	{
  background:transparent url(images/top.gif) no-repeat top left;
  margin-top:0;
  padding:4px;
 }
...

Notice that I have a ‘shoppingCartAisle’, which is the container for the ‘shoppingCart’ div. #shoppingCartAisle is set to position relative, and #shoppingCart is absolutely positioned to stick to the top of #shoppingCartAisle. (I’m using absolute positioning because I’m setting up what is to come.)

Using position:fixed

To illustrate the cart running up and down the page, I’ve created example 2, which has #shoppingCart using position:fixed; instead of position:absolute;. As the user scrolls, the shoppingCart follows the browser. Unfortunately, it overlaps the header area as well. I’d rather my cart stay in the aisle.

Deciding how to do this

This is where Derek Allard’s article proved to be of great reference. I varied from his approach in two ways:

  1. My cart does not hug the side of the browser, but instead stays within the right column of a fixed-width layout.
  2. I planned for the distance from the top of the browser to the cart to vary based on promotional messages or whatnot that may/may not line the top of the page, so it was important for the cart to be positioned absolute to the aisle, not the browser window.

It’s helpful for me to state what I want to happen in English before writing the JavaScript. When the browser scrolls, determine if it has scrolled down past where the ‘shoppingCartAisle’ begins. If so, change the ‘shoppingCart’ to position:fixed so that it follows the user down the page. If the browser scrolls back up to where ‘shoppingCartAisle’ starts, then switch ‘shoppingCart’ back to position:absolute so that it doesn’t go into the header space of the page.

The fun part – JavaScript

To determine where the shoppingCartAisle begins, I have a function called establishTopPosition(), which is the first of two functions you see below. The second function, pushMyCart(), does what the above paragraph describes in English. For the parts that look weird, just know that Derek Allard figured that stuff out for me.

<script type="text/javascript">
function establishTopPosition() {
  var shoppingCartAisle = document.getElementById('shoppingCartAisle');
  var y = 0;
  while (shoppingCartAisle!=null) {
    y += shoppingCartAisle.offsetTop
    shoppingCartAisle = shoppingCartAisle.offsetParent;
  }
    return y;
}
function pushMyCart() {
  var shoppingCart = document.getElementById('shoppingCart');
  var topPos = establishTopPosition();
  if( window.XMLHttpRequest ) { // IE 6 hates position fixed
    if (document.documentElement.scrollTop > topPos  || self.pageYOffset > topPos) {
      shoppingCart.style.position = 'fixed';
    } else {
      shoppingCart.style.position = 'absolute';
    }
  }
}
</script>

And I want JavaScript to pushMyCart() when the page is scrolling, so add this to the onscroll event of the body:

<body onscroll="pushMyCart();">

See example 3 where JavaScript is pushing my cart.

Let’s enhance it some more

I only want a rolling shopping cart if there are products in the cart. If it’s empty, I just want it to stay put.

To do this, I’m going to add a class to the shopping cart called ‘rollingCart’ when a product is added. Then I will tweak my JavaScript to only roll the cart if it has a class of ‘rollingCart’.

I also need a way to clear out the items from my cart, so I’ve provided a link, ‘Empty Cart’, than when clicked, removes the class of ‘rollingCart’ from the shopping cart.

Once again, I’m relying on the great work of others to accomplish this, namely Robert Nyman, who wrote the very helpful functions getElementsByClassName, addClassName, and removeClassName, and also one Simon Willison, who wrote the addLoadEvent function. (Yes, I know there are libraries that can do this, but this assumes there are none being used.)

Here are the functions I wrote to accomplish my new task. The first one sets up the ‘add to cart’ links so that once clicked, they give a ‘rollingCart’ class to the cart, then tell the browser to push my cart. The second function sets up the ‘clear cart’ link, so that once clicked, it removes the ‘rollingCart’ class, then tells the browser to parkMyCart(). parkMyCart() attaches the cart to the top of the shoppingCartAisle element again.

function addToCartLinks() {
  var links = getElementsByClassName('addToCart','a',document);
  var cart = document.getElementById('shoppingCart');
  for (var i=0; i<links.length; i++) {
    links[i].onclick = function() {
      addClassName(cart,'rollingCart');
      pushMyCart();
      return false;
    }
  }
}
 
function clearCart() {
  var dumpit = document.getElementById('clearCart');
  var cart = document.getElementById('shoppingCart');
  dumpit.onclick = function() {
    removeClassName(cart,'rollingCart');
    parkMyCart();
    return false;
  }
}
 
function parkMyCart() {
  var cart = document.getElementById('shoppingCart');
  cart.style.position = 'absolute';
}

See it in action with example 4 – remember to ‘add to cart’ and ‘clear cart’ to see the effect. Feel free to download the zip as well.

Note that I kept these functions simple for the sake of demonstration – no products are added or removed from the cart when links are clicked; just the class name changes. Also, this doesn’t work in IE6, and the JavaScript allows IE6 to ‘gracefully degrade’ by always having a positioned absolute cart.

I hope you find the post helpful. I am no JavaScript genius, so if you see anything that could make this better, please leave a comment.

Why tea?

This post is so themed to raise awareness for Ron Paul in honor of this weekend’s fundraiser event, which purposefully coincides with the anniversary of the Boston Tea Party. If you don’t know who Ron Paul is, I encourage you to google him or search YouTube for supporter-made videos. Or start with these: one, two, three. Thanks.


This post was ported from an old host and CMS, so many comments were lost. Below are the comments that I found were most helpful regarding this post that I salvaged. Some links or attributions may not be working correctly.


Jennifer C. said:
At first I thought it wasn’t working, but then I actually read the article and noted that it works only after adding an item to the cart, clicking the add links. I too am using Firefox 2.0.0.11 on XP Pro.
Try adding an item, then scroll down. Then, clear cart – poof, the cart will revert to the top of the screen.
Now, you could get really wild and crazy and add some mootools transforms for a visual frenzy!