Positioning Elements with jQuery.offset

jQuery.offset is handy for positioning elements, but it only works properly if the element you get the offset from is visible

by Patrick Hund

HTML

<p>
    <label for="method">positioning method:</label>
    <select id="method">
        <option>offset</option>
        <option>position</option>
    </select>
</p>
<p>
    <label for="calculate-instant">calculate coordinates</label>
    <select id="calculate-instant">
        <option value="after">after toggling the popup</option>
        <option value="before">before toggling the popup</option>
    </select>
</p>
<p>
    opener position: <span id="position"></span>
    <br>
    opener offset: <span id="offset"></span>
</p>
<p>Bacon ipsum dolor amet tenderloin tail filet mignon corned beef biltong. Meatball porchetta t-bone, pancetta bacon capicola corned beef filet mignon picanha pork loin. Swine frankfurter picanha turkey porchetta biltong. Turkey corned beef ball tip, filet mignon rump tongue shankle frankfurter ground round. Swine andouille kevin short ribs capicola t-bone. Boudin prosciutto cupim bresaola ball tip landjaeger flank salami ground round short ribs cow pork loin turkey.</p>
<p>Kevin shank chuck turkey, landjaeger ball tip strip steak andouille rump swine doner spare ribs sirloin. Tail fatback beef jowl corned beef ham ground round. Jerky salami rump meatloaf tri-tip drumstick, shoulder beef ribs chuck. Jowl pork belly prosciutto tri-tip kevin corned beef alcatra landjaeger brisket porchetta tongue venison filet mignon. Prosciutto capicola cow drumstick pastrami sirloin. Shank ribeye hamburger tenderloin, andouille drumstick picanha turducken swine landjaeger.</p>
<div id="hotpink" class="box">
    <div class="arrow"></div>
    <div class="lime"></div> <b>position: relative</b><br>
    The green popup is in the same container div as the opener button. In this case, we can use
    jQuery.position() to set the coordinates of the popup, even before the popup is made visible.
    <button class="opener" type="button">click me</button>
</div>
<div id="powderblue" class="box">
    <div class="arrow"></div>
    <div class="lime"></div>
    <b>position:...

CSS

.box {
    width: 500px;
    height: 200px;
    padding: 15px;
}
#hotpink {
    position: relative;
    background-color: hotpink;
}
#powderblue {
    position: absolute;
    top: 550px;
    left: 200px;
    background-color: powderblue;
}
#chocolate {
    position: absolute;
    top: 20px;
    left: 450px;
    background-color: chocolate;
}
.lime {
    position: absolute;
    background-color: lime;
    width: 300px;
    height: 200px;
    display: none;
    z-index: 10;
}
.arrow {
    position: absolute;
    width: 0;
    height: 0;
    border-top: 14px solid transparent;
    border-bottom: 14px solid transparent;
    border-right: 14px solid lime;
    display: none;
    z-index: 10;
}

JavaScript

function setPosition($opener, $popup, $arrow) {
    $popup.css({
        left: $opener.position().left + $opener.outerWidth() + 10 + "px",
        top: $opener.position().top - ($popup.outerHeight() / 2) + ($opener.outerHeight() / 2) + "px"
    });
    $arrow.css({
        left: $opener.position().left + $opener.outerWidth() - $arrow.outerWidth() + 10 + "px",
        top: $opener.position().top - ($arrow.outerHeight() / 2) + ($opener.outerHeight() / 2) + "px"
    });
}

function setOffset($opener, $popup, $arrow) {
    $popup.offset({
        left: $opener.offset().left + $opener.outerWidth() + 10,
        top: $opener.offset().top - ($popup.outerHeight() / 2) + ($opener.outerHeight() / 2)
    });
    $arrow.offset({
        left: $opener.offset().left + $opener.outerWidth() - $arrow.outerWidth() + 10,
        top: $opener.offset().top - ($arrow.outerHeight() / 2) + ($opener.outerHeight() / 2)
    });
}

function displayCoordinates($opener) {
    $("#position").html("left " + $opener.position().left + " / top " + $opener.position().top);
    $("#offset").html("left " + $opener.offset().left + " / top " + $opener.offset().top);
}

function togglePopup($opener, $popup, $arrow) {
    var calculateInstant = $("#calculate-instant").val();
    
    if (calculateInstant === "before") {
        layout();
    }
    
    $popup.toggle();
    $arrow.toggle();
    displayCoordinates($opener);
    
    if (calculateInstant === "after") {
        layout();
    }
    
    function layout() {
        if ($("#method").val() === "position") {
            setPosition($opener, $popup, $arrow);
        } else {
            setOffset($opener, $popup, $arrow);
        }
    }
}


$(".opener").click(function () {
    var $opener = $(this),
        $popup = $opener.siblings(".lime"),
        $arrow = $opener.siblings(".arrow");
    togglePopup($opener, $popup, $arrow);
});

$("#chocolate-opener").click(function () {
    var $opener = $(this),
        $popup =...