views:

428

answers:

2

Hi, I'm attempting to use a mixture of jQuery and CSS to make the sand in an hourglass appear to flow as you scroll down the page. I basically have two 'sand' images, and use jQuery to change the height of their containers as the user scrolls.

<!-- Load jQuery -->
  <script src="jquery-1.3.2.min.js" type="text/javascript" charset="utf-8"></script>

  <!-- Handle Hourglass Scrolling -->
  <script type="text/javascript" charset="utf-8">
   $(document).ready(function(){

    // Set value of topStart and bottomStart to equal to y-position of the equivilent divs
    var topStart = parseInt($('#top-sand').css('top'));
    var bottomStart = parseInt($('#bottom-sand').css('top'));

    // Calculate the maximum height of the sand relative to the window size
    var sandHeight = $(window).height()*0.22;

    // setValues sets positions based on window size
    function setValues() {
     topStart = parseInt($('#top-sand').css('top'));
     bottomStart = parseInt($('#bottom-sand').css('top'));
     sandHeight = $(window).height()*0.22;
     var hourglassWidth = $('#hourglass #outline img').css('width');
     var leftMargin = $(window).width()*0.5+ 320;

     $('#top-sand').height(22+"%");
     $('#top-sand img').height(sandHeight)
     $('#bottom-sand img').height(sandHeight)
     $('#hourglass').css({left:leftMargin+"px"})
     $('#trace').text(hourglassWidth)


     // contentMarginLeft = $('#hourglass #outline').width();
     //  $('#content').text(contentMarginLeft);
     // css({"margin-left": contentMarginLeft + "px"});
    };

    setValues();

    // This listens for a window scroll event and then handles the height and position of the sand in the Hourglass
       $(window).scroll(function () {

     // Calculating the position of the scrollbar
     var doc = $("body"), 
         scrollPosition = $("body").scrollTop(),
         pageSize = $("body").height(),
      windowSize = $(window).height(),
      fullScroll = pageSize - windowSize;
         percentageScrolled = (scrollPosition / fullScroll);

     // Calculating the Y-positions of the two sand piles
     var topPosition = topStart+(22*percentageScrolled);
     var bottomPosition = bottomStart-(22*percentageScrolled);

     // Updating the sand piles
          $('#top-sand').height(22-(22*percentageScrolled)+"%")
           .css({top: topPosition+"%"});
      $('#bottom-sand').height(22*percentageScrolled+"%")
          .css({top:bottomPosition+"%"});

       });

    // This listens for a window resize event and then reconfigures the layout
    $(window).bind('resize', function() {
     // Reconfigure layout
    });

     });
  </script>

It's been working fine in Safari (although every so often there's a glitch where the height of the top image changes, but it's y position doesn't), but it hasn't been working in Firefox at all. You can see the site here http://www.chris-armstrong.com/ticktalk

I'm pretty new to jQuery, so apologies if it's a complete noob error.

Thanks in advance.

A: 

Some thoughts to ponder:

  1. Prefer jQuery's positioning methods to accessing CSS values directly. For example, use .offset(), .position(), etc. instead of .css("top"). jQuery's methods are more portable across browsers, and will return numbers, not strings. See http://docs.jquery.com/CSS.

    Case in point: topStart = parseInt($('#top-sand').css('top')) above, and many others.

  2. If you must obtain a positioning metric via a CSS attribute, make sure to

    (1) always convert it to a number, e.g. with parseInt(). JavaScript's strings silently convert -- or not -- to numbers in obscure ways. Always be explicit about what you want to treat as a number.

    (2) account for the case where there are units in the CSS value, e.g. as in '100px'.

    Example: var hourglassWidth = $('#hourglass #outline img').css('width') above.

  3. Watch out for fractional values. In $('#hourglass').css({left:leftMargin+"px"}) above, what happens if leftMargin is not an integer (has a decimal point?)

  4. Your best bet might be to single-step with firebug, and watch the positioning variables as they change from line to line, and compare with your expectations.

Oren Trutner
+4  A: 

OK, I got it working! You are going to have to look at my source to see what I did because I changed so much, but the basis of your problem was from the fact that the "scrollable" element is different on different platforms/browsers.

Sometimes it is the html element, other times it is the body element. I used a few lines of code from the scrollTo Plugin to take care of that. I used CSS to position the hourglass. And I used colored div blocks to give the appearance that the sand was moving. You no longer need to handle browser sizing events (which would drive you crazy in IE, trust me :)

Here is the working demo.

I tested in Safari and FF on the Mac, but it should work correctly in IE7+.

Here is the jQuery code (not including the snippet from scrollTo):

<script type="text/javascript">
  $(function(){
    var low      = 28,
        high     = 50,
        max      = 86.5,
        range    = high - low, 
        $mtop    = $("#mask-top"),
        $mbottom = $("#sandy-bottom")
        scroller = $()._scrollable(); // Uses part of the scrollTo plugin

    $(window).scroll(function(e){
      var scrollTop  = $(scroller).scrollTop(),
          bodyHeight = document.body.scrollHeight,
          itemHeight = $('#outline').outerHeight();

      var percentScrolled = scrollTop / (bodyHeight - itemHeight);
      percentScrolled = Math.floor(percentScrolled * 100) / 100;

      var newHeight = (range * percentScrolled);
      newHeight =  Math.floor(newHeight * 10) / 10;

      $mtop.height( (newHeight + low) + "%" );
      $mbottom.css( 'top', (max - newHeight) + "%" );
    })
  })
</script>

Here is the updated HTML which I moved to be a child of body:

<div id="hourglass">
    <img id="outline" src="img/hourglass-outline.png" width="634" height"1080" alt="Hourglass Outline" />
    <!-- A new image I created based on your two-->
    <img id="sandy" src="hourglass-insides.png" width="634" height="1080" />
    <div class="mask" id="mask-top" ></div>
    <img id="sandy-bottom" src="hourglass-insides.png" width="634" height="1080" />
</div>

And for those who care to look, here is the CSS I used to position the hourglass:

<style type="text/css" media="screen">
  #hourglass {
    left: 0; top: 0; bottom: 0; right: 0;
  }
  #outline, #sandy, #sandy-bottom {
    position: fixed;
    top: 0;
    height: 100%;
    left: 50% !important;
    margin: 0;
    margin-left: 215px;
    max-height: 100%;
    min-height: 100%;
    width: auto;
  }

  #sandy {
    top: -32%;
  }

  #sandy-bottom {
    top: 86.5%;
  }

  .mask {
    background: #262B28;
    position: fixed;
    width: 100%;
    left: 0;
    right: 0;
  }
  #mask-top {
    top: 0;
    height: 28%;
  }
</style>
Doug Neiner
Whoops! Just realized I changed your design... fixing now.
Doug Neiner
OK, its fixed. Now it actually looks like your original animation :) Please let me know if you have any questions.
Doug Neiner
Wow, thanks... now to try and work out what all to change. one thing though, when I put it up fullscreen (I'm on 1920 x 1200 resolution), the sand is out of place. Any idea why that would be?Thanks again
Chris Armstrong
@Chris, I had a fixed pixel offset instead of a percentage. I updated my demo to reflect the change, and I am updating my answer here as well.
Doug Neiner
@Doug, Thanks, I'm just trying to implement your changes now, but I'm going wrong somewhere... http://www.chris-armstrong.com/ticktalk2I've added a few more functions for map handling etc, could these be interfering with your code?
Chris Armstrong
@Chris, you just need to download this image: http://pixelgraphics.s3.amazonaws.com/stackoverflow/2045742/hourglass-insides.png and upload and update the `img` element to reflect the correct path. It should be working otherwise.
Doug Neiner