Posted on July 23, 2008 15:08 by swilliams

I gave a presentation last night on the topic of improving web design for developers. On the topic of CSS I gave a very simple demonstration on using a non-table based layout. It went well over all, but I didn't have a ton of time to get it perfect and resulted with this.

It works, but wasn't the best I could do (the right column noticeably doesn't extend to the bottom of the content). Christian asked a great question about this, but my brain froze and I wasn't able to come up with a quality answer. Of course this one detail plagued me through the night.

Well, the answer is very simple. Wrap the two columns in a container element (another <div> in this case). Set the overflow property to auto or hidden, adjust the width, accounting for padding, and use the same background color as the sidebar. And presto, you get what I meant to do.

If you'd like to see the markup and style, just view the source of the page.



Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted on July 18, 2008 15:04 by swilliams

Confession time: I love Panic software. They put tons of effort into the UI of their products, and it shows. Even their website shows astonishing attention to detail, and web design isn't even a service that they offer. What gets me the most are the sliding panels on the page for Coda. I peaked at the source code for it and saw:

// Not for redistribution

Hrmph. And then I thought that I could probably reverse engineer what they were doing without copying and pasting their code. Just to be clear, I did not copy a byte of their code; everything here was written entirely by me (except in one case where labeled). To do some of the lifting, I used the Prototype JavaScript library, though you could just as easily do it with JQuery, YUI, or no library at all.

First, the Markup

This is the simplest part. The tabs are just a horizontally positioned list with the bullets removed. And the panels are divs floated sequentially. The idea is that there is a "window" div that displays one of the divs behind it. When a link is clicked, the floated divs slide in the proper direction.

scroller_image

Here is the markup to make it happen:

<div id="main">
    <ul>
        <li><a href="#" rel="scroll">Panel One</a></li>
        <li><a href="#" rel="scroll">Panel Two</a></li>
        <li><a href="#" rel="scroll">Panel Three</a></li>
        <li><a href="#" rel="scroll">Panel Four</a></li>
    </ul>
    <div id="scroll_container">
        <div id="scroller">
            <div>
                <h1>Panel 1</h1>
            </div>
            <div>
                <h1>Panel 2</h1>
            </div>
            <div>
                <h1>Panel 3</h1>
            </div>
            <div>
                <h1>More</h1>
            </div>
        </div>
    </div>
</div>

The only other thing worth mentioning is the rel="scroll" attribute on the anchor tags. This is just used to identify that these links cause a scrolling action to occur, and are used by the JavaScript to easily grab them.

The Style

Styling the HTML isn't too tricky, though there were a couple of small gotchas. My first thought was to set the position of scroller to relative and then use the JavaScript to adjust its left property. This would have worked, but then IE displays all of the scroll panels even if scroller's overflow is set to hidden.

After a bit of snooping I found that I could get the same behavior by adjusting the scrollLeft property on scroller. This manipulates the [invisible] scrollbar.

Scroller's width is large to accomodate all the panels, but could be set to anything greater than or equal to the aggregate width of the panels.

div#main
{
    width: 600px;
    margin: 0 auto;
    height: 800px;
    overflow: hidden;
}
div#main > ul
{
    margin: 0;
    padding: 0;
    width: 100%;
}
div#main > ul li
{
    font-size: 1.4em;
    margin-right: 20px;
    display: inline;
    list-style-type: none;
}
div#scroll_container
{
    width: 600px;
    overflow: hidden;
    height: 740px;
    background: #eee;
}
div#scroller
{
    width: 5000px;
}
div#scroller > div
{
    width: 600px;
    height: 740px;
    float: left;
}

JavaScript - where the magic happens

I took an iterative approach for the JavaScript. First, the most basic way to get "sliding" functionality. The initial design didn't even slide at all, it just moved the panels without an animation.

var scroller = function() {

    var scroll = function(index) {
        var totalOffset = index * scroller.scrollPanelWidth;
        var scrollee = $(scroller.scrollPanel);
        scrollee.scrollLeft = totalOffset;
    }

    var getScrollLinks = function() {
        var links = $$('a[rel=scroll]');
        links.each(function(elem, index) {
            elem.observe('click', function() {
                scroll(index);
            });
        });
    };
    
    return { 
        scrollPanel: '',
        scrollPanelWidth: 0,
    
        init: function(panel, panelWidth) {
            scroller.scrollPanel = panel;
            scroller.scrollPanelWidth = panelWidth || 600;
            getScrollLinks();
        }
    };
}();

This does exactly that. Some of the conventions I'm using here may seem a little weird, but it allows the scroller object to have private fields. To learn more about it, see here.

You can see this in action here.

Functional, but not fancy. I was going to use Script.aculo.us to do the animation, but it does it using the position: relative method described a few paragraphs ago, making IE render incorrectly. Which is kind of a bummer.

So, we'll have to animate this by ourselves. I arbitrarily decided that 500 milliseconds was an adequate time for the animation to take, in 25 frames (for simplicity's sake, pick a number that divides the duration evenly). To accommodate for these values, a couple of properties were added to the scroller object and the scroll() function now does the animating:

    var currentPosition = 0;   
    
    var scroll = function(index) {
        var totalOffset = index * scroller.scrollPanelWidth;
        var desiredOffset = totalOffset - currentPosition;
        var scrollee = $(scroller.scrollPanel);
        var tick = scroller.scrollTime / scroller.ticks;
        var timeIndex = 0.0;
        var inter;
	    
        function animate() {
            timeIndex += tick;
            var delta = desiredOffset / scroller.ticks;
            if (timeIndex > scroller.scrollTime) {
                clearInterval(inter);
                currentPosition = scrollee.scrollLeft;
                return;
            } 
            scrollee.scrollLeft += delta;
        }
        
        inter = setInterval(function() { animate(); }, tick);
    }

See it in action here.

With each tick of the timer the scroller div is moved a little bit in the proper direction. Since the duration remains constant, this has the neat effect of scrolling divs quicker the further apart they are.

At this point we could pat ourselves on the back and call it a day, but there is still a little bit more we could do. If you'll notice in Panic's design, the animation starts slowly, speeds up, then eases into the final position. Kind of like a train leaving the station, and then arriving at the next stop. Script.aculo.us's Effect.Move has the same property. It is these little things that really amp up the aesthetics of an app.

comparison How did they do it? Math! A sine curve has the function of what we're looking for. Our current example has a linear progression. Meaning each tick moves the scroller the same amount. With a sine curve, we get the behavior that Panic and Script.aculo.us have.

Since I haven't actually calculated the sine of anything since school, I decided to just steal borrow someone's code. As mentioned, Script.aculo.us has a sinoidal function. But grabbing a 100k library for a 3 line function is just silly, so I copied it to my own file directly.

var sinoidal = function(val) {
   return (-Math.cos(val * Math.PI) / 2) + 0.5;
}

sinoidal returns a value between 0 and 1, so we just need to multiply it by our desiredOffset to get our delta and tweak how the scrollLeft moves.

var delta = sinoidal(timeIndex / scroller.scrollTime) * desiredOffset;
/* ... */
scrollee.scrollLeft = currentPosition + delta;

And voila, smooth and fancy animation.

Lastly, I created a simple locking mechanism to keep the scroller from getting confused if a link is clicked before the animation is finished. There is still probably work left to be done, but that goes beyond the scope of what I wanted to discuss. So go forth and make great apps!

See the final example in motion.



Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5