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 posts

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. chris On 22nd January 2009 at 14:01

    Very nice, I have wondered what this effect was for a long time! I think I will implement within my website whenever I feel it is beneficial!

    Many thanks!

    Chris

  2. sanat On 22nd January 2009 at 16:01

    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.

  3. Neuro92 On 24th January 2009 at 17:01

    Awesome site!!! very easy to nav..wondering anyone got a fix to support Multiple Bubbles? I can chop and hack jquery thats about it.

  4. AlphaHot1 On 26th January 2009 at 02:01

    This might be useful: I’ve modified the plugin version (multiple bubbles on the same page) adding a timeout for mouseover event. This way the bubble will be triggered only if you hover the trigger for a certain amount of time, preventing “massive bubbleage” as stated before. Just like hoverIntent plugin, wihch I’ve found difficult to integrate (not much a scrpter…;)). Enjoy: http://jsbin.com/avapa (blog author edit - I moved your code to JS Bin rather than inline - keeps comments from going mad!)

  5. AlphaHot1 On 30th January 2009 at 19:01

    Yep. it also went funny with the formatting ;)

    I want to add, that to solve the ugly png border in ie/chrome, the solution is to disable the fade effect (not a jquery problem but a browser alpha loader bug) for those browsers.

    Also i put opacity back at 1, because sometimes it trimmed the text.

    So my final code is: http://jsbin.com/avoge

  6. Anna On 5th February 2009 at 23:02

    I LOVE this code, thanks for the tutorial!

    I was able to create multiple instances, but even in separate divs or in a list the pop-ups fall behind other instances. I’ve tried lots of different z-index’s in different places but can’t seem to get it to work. Any ideas?

  7. “Lida Dai Dai Hua Jiao Nang Seo Yarışması” On 8th February 2009 at 20:02

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

  8. Nix On 11th February 2009 at 23:02

    This is freaking awesome! Thank you for sharing it :)

  9. AlphaHot1 On 20th February 2009 at 16:02

    My code got removed… here it is again: http://pastebin.com/f1573b213

  10. Lee On 22nd February 2009 at 17:02

    I struggling here and have been for a few days.

    I see the plugin & advice on doing multiples but I cannot get this to work. If Remy cant halp can someone else? Maybe AlphaHot1? Have you got a moment just to hold my hand on how to get two instances of the bubble on on page?

    Sorry to ask, I really am

  11. Quatog On 27th February 2009 at 01:02

    @AlphaHot1 - I agree with Lee. Can you please guide us as we are not quite adept with the plug in. Can you create a tutorial at a layman’s level? Thanks.

  12. Rajneesh Rana On 1st March 2009 at 13:03

    Hi, i just want to say that it is really awesome….

    i’ll surely try to use it.

    Thanks a lot for the tutorial

  13. Nick On 2nd March 2009 at 20:03

    anyone else having IE6 problems pertaining to z-index? i have multiple instances of this in a results set. product thumbs are the trigger popping the bubble, and the bubble lies on top of the spawning thumb and strangely any .trigger thumb to the LEFT of the bubble, but any triggers to the right of the active bubble show through. everything works great in all other browsers of course.

  14. Nick On 3rd March 2009 at 18:03

    actually, this problem is happening in all browsers - not just an IE6 issue…

  15. Jake On 5th March 2009 at 12:03

    Hi there,

    A big thanks for this tutorial- I’m relatively new to JavaScript and just beginning to learn JQuery. This was brilliant and exactly what I wanted.

    I was just wondering though- how can I use multiple bubbles, at different coordinates, on the same image? I’ve got a map that I want to have multiple bubbles on top of, if possible?

    Many thanks to anyone that can help out

  16. AH On 10th March 2009 at 17:03

    I’m a little but confused.

    I have the bubble working - it appears when I put my mouse over the link.

    However, the side/corner images have a gap around them and I don’t know why. Here’s a grab if you want to take a look: http://homepage.mac.com/andrew.holland/bubblegrab.jpg

  17. Mark Creative On 22nd March 2009 at 14:03

    Guys there are a few problems with this coda bubble effect :

    Mainly being that it doesn’t work in Internet Explorer 6/7/8. With IE having the biggest market share of browsers it makes this method obsolete

    Fortunately there is a work around that i found here:

    http://www.mediasurgery.co.uk/2009/03/22/coda-style-jquery-bubble-now-works-in-ie/

  18. GMJ On 26th March 2009 at 03:03

    Thanks for this great tutorial and for the plugin.

    We are using the plugin version and wondered if it was possible to centre multiple lines of text inside the bubble on the fly.

    Sometime the bubble contains 1 line of text, sometimes 2, and on occasions 3 lines of text.

    Can this be done?

    Thanks

  19. patte On 26th March 2009 at 15:03

    I’m trying to write a wordpress plug-in for the pop-up bubble. Since I’m kind of new to javascript and php, I’m only able to start jquery itself via enqueue_script.

    The bubble works when I’m pasting the Source Code from your tutorial in my header.php file, but this is the only way I get it to work.

    I hope it’s easy for one of you guys to tell me how to implement the Source Code in Wordpress… so far I tried the following (which obviously won’t work), but it’s a start:

    
    function activate_bubble_javascript()  {
    $('.bubbleInfo').each(function () {
        // options
        var distance = 10;
        var time = 250;
        var hideDelay = 500;
    ...
    SOURCE CODE
    ...
    });
    add_action ( 'wp_head', 'activate_bubble_javascript' );

    PS: absolutely great site/project. I just discovered it but already are deeply in love.

    cheers, patte

  20. patte On 26th March 2009 at 15:03

    Tried another thing which didn’t work:

    
    function bubble_init() {
        jQuery(function(){
      jQuery('.bubbleInfo').each(function () {
    …
    SOURCE CODE HERE
    …
    });
    }
    
    // Actions
    add_action('wp_head', 'bubble_init');
    
  21. John Boxall On 27th March 2009 at 23:03

    Well done!

    I took the jQuery code and make it into a mini-plugin so it can work on arbitrary elements:

    http://gist.github.com/86956

  22. Luke On 7th April 2009 at 04:04

    Great tutorial, but I’m coming from an AS / designer background and these long chains are super confusing - and I know I’m not the only one as I’ve seen a few critiques of this on blogs. Anyway I’m not a jQuery expert by any means but the code below makes much more sense to me:

        $(document).ready(function(){

        var trigger=$(".trigger");
        var popup=$(".popup");
        var hideDelayTimer = null;
        var hideDelay=500;
    
        trigger.hover(mouseOver,mouseOut);
        popup.hover(mouseOver,mouseOut);
    
        function mouseOver() {
            // stops the hide event if we move from the trigger to the popup element
            if (hideDelayTimer) {
                clearTimeout(hideDelayTimer);
            }
    
            if (popup.is(':animated,:visible')) {
                return;
            }
    
            // reset position
            popup.css({top:-100,left:-33,display:'block'});
            // fade in and up
            popup.animate({top:'-=10px',opacity:1},250,'swing');
        }
        function mouseOut() {       
            // reset the timer if we get fired again - avoids double animations
            if (hideDelayTimer) {
                clearTimeout(hideDelayTimer);
            }
            hideDelayTimer=setTimeout(runOut,hideDelay)
        }
    
        function runOut() {
            hideDelayTimer=null;
            // fade out and up
            popup.animate({top:'-=10px',opacity: 0}, 250, 'swing', finishOut);
        }
    
        function finishOut() {
          // 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');
        }
    
    });
    

  23. Cormac On 20th April 2009 at 13:04

    Hi,

    Thanks for the tutorial, single instances work great. I’d like to add my voice to the requests for a demo of it working with multiple popups on one page though, I can’t get it working with the plug in except for on the first instance of the popup.

    Thanks in advance

    Cormac

  24. Puck On 21st April 2009 at 16:04

    @patte: Tried another thing which didn’t work: [code] function bubble_init() { jQuery(function(){ jQuery(’.bubbleInfo’).each(function () { … SOURCE CODE HERE … }); }

    // Actions add_action(’wp_head’, ‘bubble_init’); [/code] Why this doesn’t work is that the last row: add_action(’wp_head’, ‘bubble_init’); must be add_action(’wp_head’, bubble_init); that shall work!

  25. Двигатели складского хранения On 22nd April 2009 at 04:04

    Guys there are a few problems with this coda bubble effect :

    Mainly being that it doesn’t work in Internet Explorer 6/7/8. With IE having the biggest market share of browsers it makes this method obsolete

Leave your own comment
  • http://