Friday, July 11, 2014

making content hug the bottom of the browser

Remember how easy it was to make a footer consistently sit at the base of a browser's window ten years ago?

 
 

All you had to do was something like this:

<html>
   <body>
      <table cellpadding="0" cellspacing="0" width="100%" height="100%">
         <tr>
            <td height="20" align="center">
               I'm always at the top.
            </td>
         <tr>
         <tr>
            <td height="100%">
               &nbsp;
            </td>
         <tr>
         <tr>
            <td height="20" align="center">
               I'm always at the bottom.
            </td>
         <tr>
      </table>
   </body>
</html>

 
 

But now we are all too good for tables, so what would a modern solution look like? I tried to build something which could be considered a responsive design (with media queries) that could accommodate many different widths, by which I mean it could be thin on a smart phone touchscreen and wider on a laptop screen, which also had a consistent header and footer. I made something like this:

 
 

I gave the middle meat-of-it-all content a scrollbar that would come and go as needed rather than having the content push down the footer and create a scrollbar at the browser's window (which is what would happen in the old school table example above).

 
 

I made this little prototype by making an ASP.NET MVC Web API application in Visual Studio 2013 and then dressing up the _Layout.cshtml "master page" in the Shared folder within the Views folder like so:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width;initial-scale=1,maximum-
         scale=1,minimum-scale=1,user-scalable=no" />
   <meta name="apple-mobile-web-app-capable" content="yes" />
   <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
   <meta name="format-detection" content="telephone=no" />
   <title>WHATEVER</title>
   @Styles.Render("~/Content/css")
   @Scripts.Render("~/bundles/jquery")
</head>
   <body>
      <div id="header">
      </div>
      <div id="content">
         <div>
            @RenderBody()
         </div>
      </div>
      <div id="footer">
         <ul id="navigation">
            <li id="SignIn">sign in</li>
            <li id="SignOut" class="hide">sign out</li>
            <li id="CreateWallet">create an account</li>
         </ul>
      </div>
      <script language="javascript">
         var masterWidth = 0;
         var masterHeight = 0;
         window.scrollTo(0, 1);
         $(function() {
            setInterval("accountForViewResizing();", 100);
         });
         
         function accountForViewResizing() {
            var windowWidth = document.all ? document.body.clientWidth :
                  window.innerWidth;
            var windowHeight = document.all ? document.body.clientHeight :
                  window.innerHeight;
            if (windowWidth != masterWidth || windowHeight != masterHeight) {
               adjustView(windowWidth, windowHeight);
            }
         }
         
         $("#navigation li").bind('click', function() {
            getView(this.id);
         });
         
         function adjustView(windowWidth, windowHeight) {
            if (!windowWidth) {
               windowWidth = masterWidth;
            } else {
               masterWidth = windowWidth;
            }
            if (!windowHeight) {
               windowHeight = masterHeight;
            } else {
               masterHeight = windowHeight;
            }
            var overflow;
            var innerWidth;
            var calculation;
            if (windowWidth >= 920) {
               innerWidth = 880;
            } else {
               if (windowWidth >= 480) {
                  innerWidth = 440;
               } else {
                  innerWidth = 280;
               }
            }
            calculation = Math.floor((windowWidth - (innerWidth + 40)) / 2);
            if (($('#content').children().eq(0).height() + 82) > windowHeight) {
               overflow = "scroll";
            } else {
               overflow = "initial";
            }
            $('#footer').attr("style", "top:" + (windowHeight - 41) + "px;");
            $('#content').attr("style", "height:" + (windowHeight - 82) + "px; width:" +
                  innerWidth + "px; overflow-y: " + overflow + ";");
            $('#header').attr("style", "background-position:" + calculation + "px 0px;");
            $('#navigation').attr("style", "padding-right:" + calculation + "px;");
         }
      </script>
   </body>
</html>

 
 

Wow, hacky as hell, huh? Let's talk about this in a moment. Please also note my stylesheet which looks like this:

#content {
   height: 0px;
   width: 0px;
   margin: 0 auto;
   background-color: #FFFFFF;
   padding: 0;
}
html,body {
   height: 100%;
   padding: 0px;
   margin: 0;
   background-color: #dddddd;
   font-family: Verdana, Arial, Helvetica, sans-serif;
   overflow: hidden;
}
.field {
   width: 240px;
}
#footer {
   background-color: #ee2e24;
   height: 41px;
   position: absolute;
   top: -41px;
   width: 100%;
}
#footer ul {
   list-style: none;
   margin: 0;
   padding: 8px 0 0 0;
   float: right;
}
#footer ul li {
   display: inline;
   margin: 0;
   padding: 0px 10px 0px 20px;
   line-height: 25px;
   font-size: 13px;
   font-weight: bold;
   color: #FFFFFF;
   cursor: pointer;
}
#header {
   background-color: #ee2e24;
   height: 41px;
   background-image: url('/Content/whatever.gif');
   background-repeat: no-repeat;
}
.hide {
   display: none !important;
}
.label {
   padding-bottom: 2px;
   width: 160px;
   font-size: 18px;
   line-height: 24px;
}
.logo {
   padding-bottom: 2px;
   text-align: center;
   clear: both;
}
.logo img {
   margin: 0 auto;
}
.recipient {
   font-size: 18px;
   line-height: 24px;
   clear: both;
   text-align: right;
}
.spacer {
   height: 10px;
   clear: both;
}
#view {
   width: 240px;
}
.view {
   margin-left: 20px;
}
.view button {
   float: right;
}
.view input {
   width: 230px;
   height: 22px;
   font-size: 18px;
   line-height: 24px;
   float: right;
   border: 1px solid #666666;
}
.view select {
   width: 232px;
   height: 26px;
   font-size: 18px;
   line-height: 24px;
   float: right;
   border: 1px solid #666666;
}
 
@media screen and (min-width: 480px) {
   .field {
      float: right;
      width: 240px;
   }
   .label {
      padding-bottom: 2px;
      width: 160px;
      font-size: 18px;
      line-height: 24px;
      float: left;
      text-align: right;
   }
   .spacer {
      height: 20px;
      clear: both;
   }
   #view {
      width: 400px;
   }
}

 
 

Is using JavaScript to spin a timer which augments the styles of some elements a nasty thing to do? I'm not convinced that it is. I think it might be just fine. The media query stuff above in CSS doesn't work in Internet Explorer 8. (I realize now after the fact that I should have made the default styles friendly to a layout that is one thousand pixels wide for older Internet Explorer usages and then made the media query exception cases accommodate a mobile scenario, not the other way around.) This suggests that you may test to see if a media query will work with Modernizr like so:

var isModern = Modernizr.mq('only all and (max-width: 400px)');

 
 

With Modernizr you may fall over to a different stylesheet by way of conditional logic around something like the isModern variable I give above, but if you do so you are still bringing in some JavaScript logic and there is also the complexity of maintaining seperate stylesheets too. I don't know my jQuery spaghetti mess above is all that outrageous. What else do I want to say? In my endless loop I had to check to see if the screen resolution had changed, if I didn't the scroll bar on the central content would flicker and spaz out. I universally get rid of a scrollbar at the browser itself by putting overflow: hidden on the html and body tags. If I didn't do this then the footer sat at the visual bottom of the browser but one could scroll down and see some blank space below it. This dead space was put there by content not yet hidden from view by the overflow-y setting on the div with the id of "content." Perhaps there is a more elegant way to fix this. I haven't figured it out yet, and now I don't need to.

No comments:

Post a Comment