Grow your CSS skills. Land your dream job.

The Making of the Interactive Treehouse Ad

Published by Chris Coyier

It's likely obvious to anyone visiting this site: Treehouse is our primary sponsor. I like that. Not only because they offer structured learning that compliments the learning material I have here, but also because having a single main sponsor means we can spend more time integrating ads into the site in a fun way.

In talking to the folks over at Treehouse, they told me they had some new jQuery based content worth promoting. I thought I'd create a little interactive area to kind of wet people's whistle on what jQuery can do, and get them excited to learn more about it.

Let's go through how it was built.

The Idea

  1. Have a "canvas" (not <canvas>, just an area where things happen)
  2. Have a series of five bits of simple jQuery code
  3. Click a button, code runs, something happens in canvas
  4. You can see the code you are about to execute
  5. It should be visually clear the code you see is what is running and making the changes
  6. The whole time, the canvas area is a clickable ad for Treehouse (users aren't required to interact with the jQuery stuff, it's still just an ad)
  7. You can start over with the interactive steps when completed

Here's what we end up with. Demo on CodePen:

The HTML

Relevant bits commented:

<div class="ad">
   
  <!-- Our jQuery code will run on elements in here -->
  <div class="canvas" id="canvas">
    <a id="treehouse-ad" href="http://teamtreehouse.com" target="_blank">Team Treehouse!</a>
    <img src="http://cdn.css-tricks.com/wp-content/themes/CSS-Tricks-10/images/mike-standard.png" alt="Mike">
  </div>
  
  <div id="code-area-wrap">

    <h3>jQuery 
      <!-- We'll increment the "step", so we need an element to target -->
      <span id="step-counter">1/5</span>
    </h3>

    <!-- Code is visually displayed in here -->
    <pre class="code-area" id="code-area">var treehouse = 
$('#treehouse-ad')
  .addClass('button');</pre>

  </div>
  
  <div class="run-code-wrap">

    <!-- This will be pressed to run the code -->
    <a href="#" class="button" id="run-code">Run Code</a>

  </div>
  
</div>

The CSS

Not much interesting here, so let's not dwell on it. Essentially I made the ad re-use styles already in use on CSS-Tricks, so the ad feels at home. The block of code uses the same code styling as anywhere else on the site. The module has a bar on top like all other modules. The button is the global button style. That kind of thing.

I put a dotted border around the "canvas" as I thought that gives it a bit of an editable/target kind of feel.

The JavaScript

Now we need to make this thing work. Which means...

  1. When button is clicked...
  2. Execute the code
  3. Increment the step
  4. If it's the last step, put stuff in place to start over

The Structure

The JavaScript we write is essentially a "module". It's all related to each other. We should give it some structure to make that obvious. The best way I know how is to make an object that contains it all.

var TreehouseAd = {

  // Everything related to this ad goes in here.

};

That usually ends up something like this:

var TreehouseAd = {

  importantVariableOne: "foo",
  importantVariableTwo: "bar",

  init: function() {
    // The one function that will get called to kick things off

    this.bindUIActions();
  },

  bindUIActions: function() {
    // bind events here
    // have them call appropriately named functions
    
    $("button").on("click", function(event) {
       TreehouseAd.doSomething(event, this);
    });
  },

  doSomething: function(event, target) {
    // do something
  }

};

TreehouseAd.init();

Using structure like this makes for more readable code, more testable code, and just all around feels better than a bunch of disparate functions and variables (spagetti).

The "Steps"

We'll have five steps. As in, the different bits of jQuery that execute and do different things to our ad canvas.

  1. Add a "button" class to the link
  2. Move it up a little bit (demonstrating animation)
  3. Change the text of the button
  4. Add a new element
  5. Add class to the entire ad, giving it a finished feel

We'll also need to do two things with each bit of code:

  1. Display it
  2. Execute it

We'd rather not maintain the code in two separate places, so let's just not. We'll store the five different code bits in an array (giving it some structure, rather than five separate variables). Each item in the array is a string of code. We can grab that string and display it, or grab that string and eval() it.

I know. eval() is evil. We could avoid it by maintaining the code sample in different places. Or is there a better idea?
var TreehouseAd = {

   ...

   codeText: [

      "code for step 1",
      "code for step 2",
      ...
   ],

   ...

}

That ends up like:

codeText: [

  "treehouse = \n\
  $('#treehouse-ad')\n\
    .addClass('button');",
  
  "treehouse.animate({\n\
  top: '-40px'\n\
});",
  
  "treehouse.text(\n\
  'Learn jQuery at Treehouse!'\n\
);",
  
  "$('<div />', {\n\
  id: 'tagline',\n\
  text: "Hi, I'm Mike."\n\
}).insertAfter(treehouse);",
  
  "$('#canvas')\n\
 .addClass('all-done');\n\n\
console.log('Thanks for playing!')" 
  
],

Notice all the slashes and 'n's at the end of the lines? A string that ends in "\" in JavaScript just means "this string to be continued on next line." It doesn't end up in the string itself. "\n" means "new line," in our case, we need that so the code is formatted correctly when we place it inside the <pre> element. It won't effect the eval().

The Animation

To make it visually clear what is going on when you press the "Run Code" button, I thought having the code kind of slide up and fade into the "canvas" would be a cool idea. The steps then become:

  1. Make a clone/duplicate code area directly on top of existing one
  2. Animate position upwards while fading out
  3. Remove clone when done
  4. Wait to change code text until animation has completed

I think of this action as one "chunk" of functionality. Meaning it deserves it's own function as part of our object.

var TreehouseAd = {

  ...

  codeArea: $("#code-area"),
  delay: 400,

  animateCode: function() {

   this.codeArea
    .clone()
    .addClass("clone")
    .insertAfter(this.codeArea)
    .animate({
      top: "-60px",
      opacity: 0
    }, TreehouseAd.delay, function() {
      $(".clone").remove(); 
    });

  },

  ...

};

The Guts

The final code ends up like this. This has the logic in it for running the code and resetting and all that.

var TreehouseAd = {
  
  step: 0,
  delay: 400,
  codeArea: $("#code-area"),
  counter: $("#step-counter"),

  init: function() {
    this.bindUIActions();
  },
  
  codeText: [
  
    "treehouse = \n\
    $('#treehouse-ad')\n\
      .addClass('button');",
    
    "treehouse.animate({\n\
    top: '-40px'\n\
  });",
    
    "treehouse.text(\n\
    'Learn jQuery at Treehouse!'\n\
  );",
    
    "$('<div />', {\n\
    id: 'tagline',\n\
    text: "Hi, I'm Mike."\n\
  }).insertAfter(treehouse);",
    
    "$('#canvas')\n\
   .addClass('all-done');\n\n\
  console.log('Thanks for playing!')" 
    
  ],
  
  bindUIActions: function() {
    
    $("#run-code").on("click", function(e) {
      e.preventDefault();
      TreehouseAd.animateCode();
      TreehouseAd.runCode();
    });
                      
  },
                      
  animateCode: function() {
      
   this.codeArea
    .clone()
    .addClass("clone")
    .insertAfter(this.codeArea)
    .animate({
      top: "-60px",
      opacity: 0
    }, TreehouseAd.delay, function() {
      $(".clone").remove(); 
    });
      
  },
    
  runCode: function() {
    
    setTimeout(function() {
  
      if (TreehouseAd.step < 5) {
         
        eval(TreehouseAd.codeText[TreehouseAd.step]);
        
        TreehouseAd.step++;
        
        TreehouseAd.counter.text((TreehouseAd.step+1) + "/5");
        TreehouseAd.codeArea.text(TreehouseAd.codeText[TreehouseAd.step]);
                
      }
      
      if (TreehouseAd.step == 6) {
          
        // reset canvas
        treehouse
          .text("Team Treehouse!")
          .removeClass()
          .removeAttr("style");
        
        $("#tagline").remove();
        $("#canvas").removeClass("all-done");
        $("#run-code").text("Run Code");
        
        TreehouseAd.step = 0;
        TreehouseAd.codeArea.text(TreehouseAd.codeText[TreehouseAd.step]);
        TreehouseAd.counter.text((TreehouseAd.step+1) + "/5");
        
      }
      
      if (TreehouseAd.step == 5) {
        
        $("#run-code").text("Start Over");
        TreehouseAd.codeArea.text("");
        TreehouseAd.counter.text("Done!");
        
        TreehouseAd.step++;
        
      }
    
    }, TreehouseAd.delay);
    
  }
  
}; 

TreehouseAd.init();

The End

Again, the demo on CodePen:

You can also see a modified version of it live on this site! We've changed it up a few times already for funzies. Probably will keep doing so. Big thanks to Treehouse for letting me do fun stuff like this. Check them out if you are a fan of CSS-Tricks.

Comments

  1. Permalink to comment#

    That’s brilliant! I can see people copying same idea for their websites for interaction with visitors and to keep them longer on the website. And, yes I am going to try it on my site too.

  2. I really love this ad. A lot of my job involves trying to make our advertisements more interesting and evaluating statistics about our advertisements. Your ad is interactive, it’s catchy, it’s very well targeted at the proper demographic… It’s pretty much perfect. Good job, as always. :)

  3. Permalink to comment#

    I too have been structuring a lot of my javascript like this lately. Some things I hadn’t considered in there as well though. One thing that I have been fighting with lately is putting jquery in my functions/modules.

    Obviously it could have no merit if I just know jquery will be there but I still for some reason have been fighting with relying on jQuery to be there or not.

  4. Matt
    Permalink to comment#

    I’d be interested to see the statistics for this ad. What percent go through all five steps and click the button, which go through all five but don’t click, do some stop at step 3, etc. Would be an interesting study since this form of advertisement is very unique.

  5. Permalink to comment#

    Chris: Why do you need to declare e.preventDefault() in this block of code? Just curious… Thanks!

    bindUIActions: function() {
    
      $("#run-code").on("click", function(e) {
        e.preventDefault();
        TreehouseAd.animateCode();
        TreehouseAd.runCode();
      });
    
    }
    
  6. Not to be a downer on this “interactive” ad but it took me forever to even figure out what part of the ad was “Interactive” AFTER being told it was. This is due to the fact that I saw the green “treehouse” box and thought that was the ad and everything else was unrelated content. It took me a long time to read the small paragraph below the code that said it had something to do with Treehouse.

    I think this is because of the dotted border around the main Treehouse ad area. It separates it, and since I’m not used to seeing interactive ads it requires a big jump for me to associate the content inside the box with the content below.

    While it may be cool interaction it fails on a basic level because it takes so long to figure out that it’s somehow related to Treehouse. I’d be curious to see if the number of people interacting with the content is worth making it an interactive ad?

    • Thanks for the thoughts. All good stuff to consider in the future.

      I feel like “Run Code” is a pretty strong verb-y button and curious folks might just click to see what it does.

      It’s also kind of good that you did see the Treehouse area and just glossed over the stuff below it. It needs to be an ad first and anything else second.

  7. Justin Piatti
    Permalink to comment#

    Awesome as always Chris!! Thanks!

    @Chris – “Run Code” can / will also scare some, already overly-cautious, users out there…. lol

    @Louis – THANK YOU for bringing up the “settings” object…. I now have realized that I have some reduncancies to clean up as well…. hahaha…

Leave a Comment

Current day month ye@r *

*May or may not contain any actual "CSS" or "Tricks".