Search

Coda Popup Bubbles

Posted on 3rd March 2008 — Coda is one of the new web development tools for the Mac - and it’s popular amongst designers and developers I know. Panic (the developers of Coda) are also known for their sharp design.

In particular, Jorge Mesa writes to ask how to re-create their ‘puff’ popup bubble shown when you mouse over the download image.

In essence the effect is just a simple combination of effect, but there’s a few nuances to be wary of.

Coda Bubble

How to Solve the Problem

To create the puff popup bubble effect, we need the following:

  1. Markup that assumes that JavaScript is disabled. It would be fair to say that the popup would be hidden from the CSS.
  2. The hidden popup, correctly styled for when we make it appear.
  3. jQuery to animate the puff effect on mouseover and mouseout.

The biggest trick to be wary of is: when you move the mouse over the popup, this triggers a mouseout on the image used to trigger the popup being shown. I’ll explain (carefully) how to make sure the effect doesn’t fail in this situation.

I’ve provided a screencast to walk through how create this functionality. Details on how and what I used can be found below.

Watch the coda bubble screencast (alternative flash version)

(QuickTime version is approx. 23Mb, flash version is streaming)

View the demo and source code used in the screencast

HTML Markup

For the purpose of reusability, I’ve wrapped my ‘target’ and ‘popup’ in a div. The target is the element which the user must mouseover to show the popup.

<div class="bubbleInfo">
  <img class="trigger" src="http://mysite.com/path/to/image.png" />
  <div class="popup">
    <!-- your information content -->
  </div>
</div>

CSS

There’s very little to the minimum required CSS. Of course, how you markup your bubble will change this, and the screencast uses the version from the Coda web site, so there’s a considerable amount of CSS to style the bubble.

The minimum I recommend for the example is:

.bubbleInfo {
    position: relative;
}

.popup {
    position: absolute;
    display: none; /* keeps the popup hidden if no JS available */
}

This way we can absolutely position the popup against the trigger.

jQuery

To create the effect, we need to run the following animation on the popup element:

Mouse Over

  1. On mouseover: reset the position of the popup (required because we’re floating upwards as we puff away).
  2. Animate the popup’s opacity from 0 to 1 and move it’s CSS top position by negative 10px (to move upwards).
  3. If the mouseover is fired again, and we’re still animating - ignore.
  4. If the mouseover is fired again, and the popup is already visible - ignore.

Mouse Out

  1. Set a timer to trigger the popup hide function (this prevents accidentally moving out of the ‘active’ area).
  2. If a timer is set (to hide), reset the timer (thus only allowing one hide function to fire).
  3. Once timed out, animiate the popup’s opacity from 1 to 0 and move it’s CSS top position by negative 10px (to float upwards away).
  4. Set the appropriate flags to indicate the popup is now hidden.

The ‘Trick’

There was one piece of tricky logic that initially I couldn’t work out. Each time I moved the mouse over the popup, it would fire a mouseout on the trigger element - which would hide the popup. This is an undesirable bug.

There may be a another way around this, and from what I can tell, the Coda site developers didn’t solve it this way - but here’s the solution:

You need to clear the timer set in the mouseout (point 1 above) in the mouseover. This completely solves the problem.

Complete Source Code

Here’s the complete source code for the effect, including comments throughout the code to explain what each block is doing.

$(function () {
  $('.bubbleInfo').each(function () {
    // options
    var distance = 10;
    var time = 250;
    var hideDelay = 500;

    var hideDelayTimer = null;

    // tracker
    var beingShown = false;
    var shown = false;
    
    var trigger = $('.trigger', this);
    var popup = $('.popup', this).css('opacity', 0);

    // set the mouseover and mouseout on both element
    $([trigger.get(0), popup.get(0)]).mouseover(function () {
      // stops the hide event if we move from the trigger to the popup element
      if (hideDelayTimer) clearTimeout(hideDelayTimer);

      // don't trigger the animation again if we're being shown, or already visible
      if (beingShown || shown) {
        return;
      } else {
        beingShown = true;

        // reset position of popup box
        popup.css({
          top: -100,
          left: -33,
          display: 'block' // brings the popup back in to view
        })

        // (we're using chaining on the popup) now animate it's opacity and position
        .animate({
          top: '-=' + distance + 'px',
          opacity: 1
        }, time, 'swing', function() {
          // once the animation is complete, set the tracker variables
          beingShown = false;
          shown = true;
        });
      }
    }).mouseout(function () {
      // reset the timer if we get fired again - avoids double animations
      if (hideDelayTimer) clearTimeout(hideDelayTimer);
      
      // store the timer so that it can be cleared in the mouseover if required
      hideDelayTimer = setTimeout(function () {
        hideDelayTimer = null;
        popup.animate({
          top: '-=' + distance + 'px',
          opacity: 0
        }, time, 'swing', function () {
          // once the animate is complete, set the tracker variables
          shown = false;
          // hide the popup entirely after the effect (opacity alone doesn't do the job)
          popup.css('display', 'none');
        });
      }, hideDelay);
    });
  });
});

Taking it Further

This effect could be perfected by changing the initial reset (popup.css()) code to read from the trigger element and approximate it’s position. In my example, I’ve hardcoded it because I only have one on the page - but you may want to use this effect several times across your page.

Related screencasts

Demo

If you find this demo doesn't work as expected, it's possibly due to the demo running from within an iframe. Try running the demo in it's own window.

Source Code

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <title>Coda Bubble Example</title>
    <style type="text/css" media="screen">
    <!--
        * {
            margin: 0;
            padding: 0;
        }
        
        body {
            padding: 10px;
        }
        
        h1 {
            margin: 14px 0;
            font-family: 'Trebuchet MS', Helvetica;
        }
        
        p {
            margin: 14px 0;
            font-family: 'Trebuchet MS', Helvetica;
        }
        
        .bubbleInfo {
            position: relative;
            top: 150px;
            left: 100px;
            width: 500px;
        }
        .trigger {
            position: absolute;
        }
     
        /* Bubble pop-up */

        .popup {
        	position: absolute;
        	display: none;
        	z-index: 50;
        	border-collapse: collapse;
        }

        .popup td.corner {
        	height: 15px;
        	width: 19px;
        }

        .popup td#topleft { background-image: url(http://static.jqueryfordesigners.com/demo/images/coda/bubble-1.png); }
        .popup td.top { background-image: url(http://static.jqueryfordesigners.com/demo/images/coda/bubble-2.png); }
        .popup td#topright { background-image: url(http://static.jqueryfordesigners.com/demo/images/coda/bubble-3.png); }
        .popup td.left { background-image: url(http://static.jqueryfordesigners.com/demo/images/coda/bubble-4.png); }
        .popup td.right { background-image: url(http://static.jqueryfordesigners.com/demo/images/coda/bubble-5.png); }
        .popup td#bottomleft { background-image: url(http://static.jqueryfordesigners.com/demo/images/coda/bubble-6.png); }
        .popup td.bottom { background-image: url(http://static.jqueryfordesigners.com/demo/images/coda/bubble-7.png); text-align: center;}
        .popup td.bottom img { display: block; margin: 0 auto; }
        .popup td#bottomright { background-image: url(http://static.jqueryfordesigners.com/demo/images/coda/bubble-8.png); }

        .popup table.popup-contents {
        	font-size: 12px;
        	line-height: 1.2em;
        	background-color: #fff;
        	color: #666;
        	font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", sans-serif;
        	}

        table.popup-contents th {
        	text-align: right;
        	text-transform: lowercase;
        	}

        table.popup-contents td {
        	text-align: left;
        	}

        tr#release-notes th {
        	text-align: left;
        	text-indent: -9999px;
        	background: url(http://jqueryfordesigners.com/demo/images/coda/starburst.gif) no-repeat top right;
        	height: 17px;
        	}

        tr#release-notes td a {
        	color: #333;
        }
        
    -->
    </style>

    <script src="jquery.js" type="text/javascript"></script>

    <script type="text/javascript">
    <!--

    $(function () {
        $('.bubbleInfo').each(function () {
            var distance = 10;
            var time = 250;
            var hideDelay = 500;

            var hideDelayTimer = null;

            var beingShown = false;
            var shown = false;
            var trigger = $('.trigger', this);
            var info = $('.popup', this).css('opacity', 0);


            $([trigger.get(0), info.get(0)]).mouseover(function () {
                if (hideDelayTimer) clearTimeout(hideDelayTimer);
                if (beingShown || shown) {
                    // don't trigger the animation again
                    return;
                } else {
                    // reset position of info box
                    beingShown = true;

                    info.css({
                        top: -90,
                        left: -33,
                        display: 'block'
                    }).animate({
                        top: '-=' + distance + 'px',
                        opacity: 1
                    }, time, 'swing', function() {
                        beingShown = false;
                        shown = true;
                    });
                }

                return false;
            }).mouseout(function () {
                if (hideDelayTimer) clearTimeout(hideDelayTimer);
                hideDelayTimer = setTimeout(function () {
                    hideDelayTimer = null;
                    info.animate({
                        top: '-=' + distance + 'px',
                        opacity: 0
                    }, time, 'swing', function () {
                        shown = false;
                        info.css('display', 'none');
                    });

                }, hideDelay);

                return false;
            });
        });
    });
    
    //-->
    </script>
</head>
<body id="page">
    <h1>Coda Bubble Example</h1>
    <p>This shows a demonstration of the 'puff' popup bubble effect as seen over the download link on the <a href="http://www.panic.com/coda/">Coda web site</a>.</p>
    <p>Roll the mouse over and out from the download image to see the popup fade in and out of view, while gently gliding upwards.</p>
    <p><a href="http://jqueryfordesigners.com/coda-popup-bubbles">Read the article, and see the screencast this demonstration relates to</a></p>
    <p><small>Note that the transparency doesn't work in IE - this is the same on the Coda web site where the images were sourced.</small></p>

    
    <div class="bubbleInfo">
        <div>
            <img class="trigger" src="http://jqueryfordesigners.com/demo/images/coda/nav-download.png" id="download" />
        </div>
        <table id="dpop" class="popup">
        	<tbody><tr>
        		<td id="topleft" class="corner"></td>
        		<td class="top"></td>
        		<td id="topright" class="corner"></td>
        	</tr>

        	<tr>
        		<td class="left"></td>
        		<td><table class="popup-contents">
        			<tbody><tr>
        				<th>File:</th>
        				<td>coda 1.1.zip</td>
        			</tr>
        			<tr>
        				<th>Date:</th>
        				<td>11/30/07</td>
        			</tr>
        			<tr>
        				<th>Size:</th>
        				<td>17 MB</td>
        			</tr>
        			<tr>
        				<th>Req:</th>
        				<td>Mac OS X 10.4+</td>
        			</tr>						
        			<tr id="release-notes">
        				<th>Read the release notes:</th>
        				<td><a title="Read the release notes" href="./releasenotes.html">release notes</a></td>
        			</tr>
        		</tbody></table>

        		</td>
        		<td class="right"></td>    
        	</tr>

        	<tr>
        		<td class="corner" id="bottomleft"></td>
        		<td class="bottom"><img width="30" height="29" alt="popup tail" src="http://static.jqueryfordesigners.com/demo/images/coda/bubble-tail2.png"/></td>
        		<td id="bottomright" class="corner"></td>
        	</tr>
        </tbody></table>
    </div>
</body>
</html>

Comments

  1. Tim On 7th May 2008 at 08:05

    Cool, but the transparency falls over in IE7 too! You get big black outline around the popup.

  2. Adam On 13th May 2008 at 13:05

    Not working in ie6 :/

  3. Remy On 13th May 2008 at 21:05

    @Tim + @Adam - sorry about that. In the former instance, the images are taken directly from Coda’s web site. When you make your own I sure you’ll use alpha transparencies on the images.

  4. Choseth On 15th May 2008 at 07:05

    Hi, very interessant plugin, what a work !

    I’m a newbie in jQuery and I’m trying to use the Bubble script for multiple bubbles in the same page.

    But like Carla, it cames out a mess. Could it be possible to find a extended version of this tutorial which helps me to manage multiple bubbles?

  5. Real Estate Postcards Guy On 17th May 2008 at 15:05

    This it what I’m talking about…great tutorial I was wondering how Coda did it.

  6. Manish Bansal On 19th May 2008 at 06:05

    Hi I found this tip UI awesome, so I have tried to make this tip dynamic for end users. but i have strut in between, Download zip from here http://www.2shared.com/file/3307138/2577de6b/Coda_tip.html

    guys is there any possibility of dynamiszing those code which i have uploaded.

    Regards, Manish Bansal

  7. D. Carreira On 31st May 2008 at 17:05

    Hi, I’ve used this tutorial to create a contact pop-up, if you want to take a look, here is the link (the website is in portuguese) E-Serviços.net, the effect is on mouseover the text in bottom link (”Contacto”)

  8. Ryan On 4th June 2008 at 07:06

    Here’s an interesting bug I found, transparency will never work on this example as transparent pngs and animation cause a black border to appear where the shadow effect should be. Once the animation is complete the transparency should work but due to your use of the setInterval function it never stops animating perse and therefore never gets to render the transparency correctly in IE7. Anyone know of a fix?

  9. Don On 5th June 2008 at 18:06

    Again thanks man very useful tutorial, very helpful and easy to follow

  10. Craig On 6th June 2008 at 12:06

    REMY: can you post a snippet on how to use your plugin? I’ve loaded it in, I have a class=bubble and inside that trigger and popup… nada… the previous code works…

    Thanks

  11. Remy On 6th June 2008 at 12:06

    @Craig - try this:

    
    $(document).ready(function () {
      $('div.bubble').bubble({
        'trigger' : '.trigger', // selector for the trigger element
        'popup' : '.popup', // selector for the actual bubbe (within div.bubble)
        'distance' : 10, // distance in px it will travel
        'hideDelay' : 500, // time before hiding
        'effectTime' : 250 // total time for effect
      });
    });
    
  12. Nick On 10th June 2008 at 23:06

    FYI - a quick dirty way to create a “showDelay”, wrap the mouseover stuff inside a setTimeout function too:

    hideDelayTimer = setTimeout(function () {
        // reset position of info box
        beingShown = true;
        info.css({
            top: -15,
            left: 150,
            display: 'block'
        }).animate({
            top: '-=' + distance + 'px',
            opacity: 1
        }, time, 'swing', function() {
            beingShown = false;
            shown = true;
        });
    }, hideDelay);

    I was using multiple bubbles on a page and this prevents massive bubbleage when you quickly mouseover all of them.

  13. Michael Robinson On 11th June 2008 at 04:06

    Thanks for this tutorial, I can’t wait to try it out!

    Mike

  14. antipix On 11th June 2008 at 15:06

    Is it just me or your code with jquery 1.2.6 version causes javascript error ?

  15. antipix On 13th June 2008 at 21:06

    it was just me ^^

  16. Rob C On 21st June 2008 at 20:06

    This is some real snazzy stuff.

    Thanks for the tutorial Remy. I’ll be hanging around here more often now that I’ve found this site =)

    R.

  17. Sue On 30th June 2008 at 19:06

    This is a great tutorial! Thanks!!

    I have a small problem though. I’m using the same trigger on 3 different pages and the placement is slightly different. Is there any way to position the popup bubble through CSS instead of jQuery, but still use the same jQuery code? I just need a unique ID for each bubble and I’m having problems…

  18. LiZharD On 7th July 2008 at 13:07

    Great tut! I’m trying to mashup this tutorial with this: http://jqueryfordesigners.com/image-cross-fade-transition/

    but with no luck. Bubble works fine but no image fade transition. Any ideas?

  19. marcus On 10th July 2008 at 20:07

    @Nick: doesn’t work for me. now nothing is showin up at all.

    besides that: great stuff! :) thanks.

  20. marcus On 10th July 2008 at 21:07

    got it :) my timer was set too low to recognize the delay ;)

    thanks again for this.

  21. dar On 16th July 2008 at 20:07

    Hello from argentina, first of all great tutorial! im having a little problem, the bubble works fine, but, in the same html, i have images using lightbox, i investigated and find out that prototype and jQuery, uses same shortcut “$” :( i been trying to solve it al afternoon, but i cant, cause pages who mention this problem only talk about thickbox, and the jquery of it its different from this. hope you can help me!

  22. charly On 17th July 2008 at 20:07

    hi! great stuff here!! its working for me! the only thing that i cant do is making transparent my png background (in IE), i made a new image and didnt work any ideas? Thanks

    (i posted the same msg in another post by error) please delete it! Thanks again

  23. k3k On 18th July 2008 at 08:07

    Great tutorial. I started to use jQuery about 1 year ago and I can’t stop :)

    Greatings from Italy.

  24. Free Article Directory On 19th July 2008 at 04:07

    Thanks for the info. Really helped!

  25. Download On 23rd July 2008 at 16:07

    thanks,design is so complicated issue that i usually created mess thing.I ll try to implement it on my site :D

Comments are now closed.