Sunday, September 2, 2012

Draggable Lightbox via HTML5

There is something with HTML lightboxes / modals that annoys me: they are usually not draggable.

The usual way to go is using draggable from jqueryUI . However I tend to flee from jqUI based solutions due to the code bloat it means (they are bullet-proof but they are heavy). Other solutions are more appealing. However I realized that almost all browsers provide support for 'native' drag and drop: why not to use it for my purpose?

The solution

The solution takes only ~20 lines of javascript (it controls that the modal is not moved out of bounds).
Suppose that your lightbox has a wrapper (the window that displays content) and an overlay. You have to bind the 'dragstart' and 'dragend' events to the wrapper (and do it only ONCE, for example after it is created). For the overlay, we bind the events 'drop' and 'dragover' (again, do it only once)

var opt = {};
$wrapper.bind('dragstart', opt,  function(ev){
    var opt = ev.data, 

    // we store the difference between the element top / left, and the mouse cursor, so that we can apply them once the object is drop
    $el = $(this), off =  $el.offset();
    opt.offX = off.left - ev.screenX;
    opt.offY = off.top - ev.screenY;

    var e = ev.originalEvent;
    e.dataTransfer.effectAllowed = e.dataTransfer.dropEffect = 'move';
    e.dataTransfer.setData('text', this.id);//we need to set some data
    $el.css({
        opacity:0.4
    });

}).bind('dragend', opt, function(ev){
    var opt = ev.data,
    //only if new top AND new left makes the box stay within the window
    $el = $(this), newX = ev.screenX + opt.offX, newY = ev.screenY + opt.offY, $win = $(window);
    newX = (newX < 0) ? 0 : Math.min(newX , $win.scrollLeft() + $win.width() - $el.outerWidth() );
    newY = (newY < 0) ? 0 : Math.min(newY , $win.scrollTop() + $win.height() - $el.outerHeight() );

    $el.css({
        top: newY,
        left: newX,
        opacity: 1
    });
});


$overlay
.bind('drop', function(ev){
    ev.stopPropagation(); // Stops some browsers from redirecting.
    ev.preventDefault();
    return false;       
}).bind('dragover',function(ev){
    if (ev.originalEvent.dataTransfer.getData("text")){ //allow to drop only our things
        ev.preventDefault(); //informs that we can drag here
        return false;
    }
});


Simple and straightforward!

Note: for a JS only implementation, simple and effective, take a look at http://css-tricks.com/snippets/jquery/draggable-without-jquery-ui/