javascriptjqueryjquery-uidraggableadobe-illustrator

Javascript drag/drop - Illustrator style 'smart guides'


I'm looking for a way to implement Adobe Illustrator style 'smart guides' when dragging/dropping in Javascript. I'm currently using jQuery UI's draggable:

$('.object').draggable({
    containment: 'parent',
    snap: '.other-objects',
    snapTolerance: 5
})

This does 90% of what I want - I can drag .object around within it's parent, and it will snap it's edges to .other-objects when it gets close enough.

What I want, however, is for a line of some kind (or a guide of some kind) to appear, if it's in line with the edge of another object, so I can snap stuff in a row without them being directly next to each other.

Does anybody know if this is possible, or how I'd go about doing it?


Solution

  • I started messing around with a jsFiddle. It's not perfect, but it should get you started.

    The bulk of the logic is within jQuery UI's drag event handler:

    function (event, ui) {
    
            // You'll want to debounce this function so that it doesn't run every mouse move (e.g. see Ben Alman's site @ http://tinyurl.com/37dyjug)
            var debounceTime = 200; // milliseconds
            setTimeout(function () {
    
                // Loop through all 'other-object's and see if we're lined up
                $(".other-object").each(function (idx, other) {
                    var $other = $(other);
    
                    // Determine whether we're "close enough" to display the line
                    var padding = 1;
                    var closeToLeft = Math.abs($other.offset().left - ui.offset.left) < padding;
                    var closeToTop = Math.abs($other.offset().top - ui.offset.top) < padding;
                    // You can add closeToRight/closeToBottom, but you may need to do some calculation, e.g. right = left + width
    
                    // If we're close, display a line, otherwise remove that same line
                    // TODO: Find a better way of tagging which 'other-object' this line belongs to, using IDs or something more stable than the index of the jQuery each() function!
                    var id = 'leftOther' + idx;
                    if (closeToLeft) {
                        console.debug(idx, 'left');
                        $('.parent').not(':has(#' + id + ')').append('<div id="' + id + '" class="line vertical" style="left: ' + $other.offset().left + 'px;"/>');
                    } else {
                        $('#' + id).remove();
                    }
    
                    id = 'topOther' + idx;
                    if (closeToTop) {
                        console.debug(idx, 'top');
                        $('.parent').not(':has(#' + id + ')').append('<div id="topOther' + idx + '" class="line horizontal" style="top: ' + $other.offset().top + 'px;"/>');
                    } else {
                        $('#' + id).remove();
                    }
                }); // End of 'other-object' loop
    
            }, debounceTime); // End of setTimeout
        } // End of drag function
    

    If I have some time later I'll come back and give it some more thought, but figured you'd appreciate a semi-answer so start you off for now =)