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. Michael On 5th October 2008 at 14:10

    At the end i did use the shadedborders script of Steffen Rusitschka (http://www.ruzee.com/blog/shadedborder/) for rendering the bubble frame and shadow.

    So you do not need any images and png fixes, except for the bubble tail (which could eventually be solved with a small canvas :).

    see it working at http://www.moossen.net/

    this works really great for all browsers, except for IE, at least in my case the bubble background is transparent, so i still have to use the images table for IE :(

  2. ManforCharity On 10th October 2008 at 05:10

    This is website is really awesome…. Keep it up!!!

  3. daniel On 10th October 2008 at 20:10

    I am very new to this and trying to figure all this out. I got this working no problem. I got my bubble styled and positioned exactly how i want it. BUT… I tried to combine this with the coda slider. When i add all the code and java for that the bubble stops working. In this you reference one form of jquery and the slider another. But when i looked at them they looked identical. I am guessing there is some sort of duelling / competing class issue. I ran into the same problem when i was using a mootools slider. But that didnt work because jquery and moo don’t play well together. Any ideas how to make the slider and bubble play nice?

  4. JamesB On 24th October 2008 at 11:10

    I’ve combined this and the technique from webdesignerwall together to use on a site. I needed something very simple and easy to implement, so it doesn’t use any transparent png’s etc. It just works. If anyone’s interested in grabbing the code you can get it at: http://www.smartredfox.com/2008/10/very-simple-jquery-tooltips/

  5. Sidnei Dasilva On 25th October 2008 at 01:10

    Is there a way to load an external file inside of the tooltip?

  6. Josh On 26th October 2008 at 14:10

    This is a stellar tutorial!

    I implemented it but have an issue. When I’m using this with your other script, the slider, this bubble won’t go on top of it. The bubble just gets cut off where the slider begins. I’ve tried setting z-index of both elements (and their sub-elements) but haven’t been successful in resolving it. Any ideas?

    If you’d like to see the working example, drop me an email and I’d be glad to share.

    Thanks!

  7. Casey Reid On 28th October 2008 at 03:10

    I’m in the same boat as Josh above. I want this to work with the slider but the bubble gets cut off where the slider begins.

    Any thoughts?

  8. Steiner On 2nd November 2008 at 20:11

    For anyone having the nasty effect in IE via alpha that I’ve been having. a simple fix!

    Before: .animate({ top: ‘-=’ + distance + ‘px’, opacity: 1 }, time, ’swing’, function() { // once the animation is complete, set the tracker variables beingShown = false; shown = true; });

    After: .animate({ top: ‘-=’ + distance + ‘px’ }, time, ’swing’, function() { $(info).fadeIn(’fast’); beingShown = false; shown = true; });

    And change “var info” to reflect var info = $(’.popup’, this).css(’display’, ‘none’);

    “$(info)” is required too grab the CURRENT item, otherwise it will make ALL boxes on the page visible.

  9. backpack-smith On 15th November 2008 at 19:11

    This’s a nice pop-up effect. Thank for the sharing. Appreciated.

  10. Cody On 18th November 2008 at 22:11

    Simple Question… How do you make the bubble appear downward instead of upward?

  11. Xerses On 22nd November 2008 at 16:11

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

    I have an other problem with this script. I’m using it in a calendar where each date can have an event. When I put the mouse over the trigger of a date, the popup appears but the others triggers around appears above.

    How can I fix that ?

    Thanks a lot for this script.

  12. Scott On 25th November 2008 at 22:11

    I’m having trouble in IE7 with a transparent PNG showing a black background and border around the animated bubble. How do I get IE7 to behave?

  13. kate On 1st December 2008 at 22:12

    i’m having a hard time getting the pop ups in IE. is there something i’m doing wrong here? i don’t know enough about jquery or js to be an effective problem solver and i’ve puttered around with this for quite some time :)

    i’m trying to use it at: http://www.goldenhelix.com/IN_PROGRESS/publications.html

    muchos gracias!

  14. Sanaell On 2nd December 2008 at 05:12

    for fix the IE transparency it’s very simple .popup ID or Class { filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sProperties); } after all your class and id definition

    like me .popup td#topleft,.popup td#topright,.popup td#bottomleft,.popup td#bottomright, .popup td.bottom img, .popup td.bottom, .popup td.left, .popup td.right, .popup td.top { filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sProperties); } that work perfectly

  15. Sanaell On 2nd December 2008 at 05:12

    i forget something so - to fix the IE transparency :::: in css definition where you have a png background by example (or style=”") filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sProperties);

    to prevent problem with font and “opacity” put a background color on the TEXT content

  16. Sean Fraser On 6th December 2008 at 07:12

    I have maligned your code in the past and thank you very much for that. I’ve read various eBooks, forums and mailing lists but nothing expresses how to manipulate your “JSON” syntax. And, since I don’t even measure up to be a newbie, how would I rewrite the popup.css so that “top” can be written for three different IDs (with different heights) which require three different settings?

    
    popup.css({
      display: 'block',
      top: -65, // This is the dimension which need different pixel settings
      left: -32
    })
    
    

    I’m attempting to use Bubble for a navigation effect: Nav Hover.

    And, my 2c on IE/PNGs - why just send IE6 GIFs via Conditional Commented style sheet?

    Thank for all the inspiration.

    Sean

  17. Çeçenistan On 27th December 2008 at 17:12

    Great tutorial, thanks a lot! You’ve just got yourself another subscriber :)

  18. Çeçenistan On 27th December 2008 at 17:12

    Great tutorial! The timeout, cleartimeout stuff has always confused me a bit, and this info will come in handy if I ever need to make a series of drop downs for navigation!

  19. Kabala On 27th December 2008 at 17:12

    It’s a double edged sword for me because I’m trying to demonstrate how to implement a particular technique (as such need to replicate the effect as close as possible) whilst yet promoting good practises such as being unique and creative.

  20. Kabala On 27th December 2008 at 21:12

    I am very new to this and trying to figure all this out. I got this working no problem. I got my bubble styled and positioned exactly how i want it. BUT… I tried to combine this with the coda slider. When i add all the code and java for that the bubble stops working. In this you reference one form of jquery and the slider another. But when i looked at them they looked identical. I am guessing there is some sort of duelling / competing class issue. I ran into the same problem when i was using a mootools slider. But that didnt work because jquery and moo don’t play well together. Any ideas how to make the slider and bubble play nice?

  21. ravi On 29th December 2008 at 04:12

    Any one done it for multiple images.Can you please post demo for multiple bubble.

  22. Diego Balboa On 12th January 2009 at 21:01

    Buenissimo!

    Would like to know, how can I have multiple bubbles within a page? I tried $(’.bubbleinfo’)+id.each…..

    where i would just assign an id to each different bubble, i.e. bubbleinfo1, bubbleinfo2… etc

    any help please?

  23. Rusya On 13th January 2009 at 21:01

    for fix the IE transparency it’s very simple .popup ID or Class { filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sProperties); } after all your class and id definition

    like me .popup td#topleft,.popup td#topright,.popup td#bottomleft,.popup td#bottomright, .popup td.bottom img, .popup td.bottom, .popup td.left, .popup td.right, .popup td.top { filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sProperties); } that work perfectly

  24. Art On 13th January 2009 at 21:01

    Great Tut!

    Quick question, when using the plug-in found at http://plugins.jquery.com/project/bubble-puff, you said:

    You need to point the selector at a div that contains the trigger element and the actual popup. Let me know how you get on.

    Where is this selector where I must point to the bubbleinfo class that holds my trigger and popup?

    Thank you.

  25. Chala On 14th January 2009 at 23:01

    Hello,

    Any way to make it so, that the bubble does not stay. Meaning, if I roll off the trigger the bubble will disappear. Even if my mouse goes into the bubble it will disappear, because it rolled off the trigger element

Comments are now closed.