Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would you create a JQuery / svg click-drag select outline effect?

Not sure exactly what to call it, but I am looking for a way to create a dotted outline/selection box effect via javascript/svg when you click and drag over an area, and then goes away on mouseUp (that could be added if it wasn't an original part) .

A jQuery library would be nice if it exists. I've done some looking around, and haven't found exactly what I am looking for.
I guess the theory would be get the coord from the first click, track the mouse coord moment and adjust the box accordingly.

But not writing it from scratch would be nice.

like image 414
jmhead Avatar asked Jul 26 '12 16:07

jmhead


2 Answers

Here's a demo I made just for you :)

Demo (Static): http://jsfiddle.net/HNH2f/1/

Demo (Animated): http://jsfiddle.net/HNH2f/2/

You can use CSS to control the visual style of the marquee. You can pass one or two functions to the trackMarquee method; both will be called with four arguments: the x1,y1,x2,y2 bounds of the marquee. The first function will be called when the marquee is released. The second function (if present) will be called each time the marquee moves (so that you can, for example, calculate what items are within that bounding box).

When you start dragging on the SVG document (or whatever element you choose to track) it will create a <rect class="marquee" />; during dragging it will adjust the size of the rectangle. Use CSS (as seen in the demo) to style this rectangle however you want. I'm using the stroke-dasharray property to make the border dotted.

For Stack Overflow posterity, here's the code (on the off chance that JSFiddle is down):

(function createMarquee(global){
  var svgNS = 'http://www.w3.org/2000/svg',
      svg   = document.createElementNS(svgNS,'svg'),
      pt    = svg.createSVGPoint();

  // Usage: trackMarquee( mySVG, function(x1,y1,x2,y2){}, function(x1,y1,x2,y2){} );
  // The first function (if present) will be called when the marquee is released
  // The second function (if present) will be called as the marquee is changed
  // Use the CSS selector `rect.marquee` to select the marquee for visual styling
  global.trackMarquee = function(forElement,onRelease,onDrag){
    forElement.addEventListener('mousedown',function(evt){
      var point0 = getLocalCoordinatesFromMouseEvent(forElement,evt);
      var marquee = document.createElementNS(svgNS,'rect');
      marquee.setAttribute('class','marquee');
      updateMarquee(marquee,point0,point0);
      forElement.appendChild(marquee);
      document.documentElement.addEventListener('mousemove',trackMouseMove,false);
      document.documentElement.addEventListener('mouseup',stopTrackingMove,false);
      function trackMouseMove(evt){
        var point1 = getLocalCoordinatesFromMouseEvent(forElement,evt);
        updateMarquee(marquee,point0,point1);
        if (onDrag) callWithBBox(onDrag,marquee);
      }
      function stopTrackingMove(){
        document.documentElement.removeEventListener('mousemove',trackMouseMove,false);
        document.documentElement.removeEventListener('mouseup',stopTrackingMove,false);
        forElement.removeChild(marquee);
        if (onRelease) callWithBBox(onRelease,marquee);
      }
    },false);
  };

  function callWithBBox(func,rect){
    var x = rect.getAttribute('x')*1,
        y = rect.getAttribute('y')*1,
        w = rect.getAttribute('width')*1,
        h = rect.getAttribute('height')*1;
    func(x,y,x+w,y+h);
  }

  function updateMarquee(rect,p0,p1){
    var xs = [p0.x,p1.x].sort(sortByNumber),
        ys = [p0.y,p1.y].sort(sortByNumber);
    rect.setAttribute('x',xs[0]);
    rect.setAttribute('y',ys[0]);
    rect.setAttribute('width', xs[1]-xs[0]);
    rect.setAttribute('height',ys[1]-ys[0]);
  }

  function getLocalCoordinatesFromMouseEvent(el,evt){
    pt.x = evt.clientX; pt.y = evt.clientY;
    return pt.matrixTransform(el.getScreenCTM().inverse());
  }

  function sortByNumber(a,b){ return a-b }
})(window);
like image 168
Phrogz Avatar answered Oct 21 '22 01:10

Phrogz


You are lucky I just made this myself. I'm using jQuery SVG plugin ( http://keith-wood.name/svg.html )

$("#paper2").mousedown(function(ev) {
    ev.preventDefault(); 
    var pX= (ev.pageX - this.offsetLeft) * viewBox[2]/parseInt($("#paper2").css("width"));
    var pY= (ev.pageY - this.offsetTop)  * viewBox[3]/parseInt($("#paper2").css("height"));
    var rect = svg2.rect(
        pX, //X
        pY, //Y 
        1,1, //width and height
        { //Settings, you can make the box dotted here
            fill: 'black', "fill-opacity": 0.3, stroke: 'red', strokeWidth: 3, id:rect
        }
    )

    $("#paper2").mousemove(function(ev) {
        ev.preventDefault();
        var rect= $('#rect');
        var pX= (ev.pageX - this.offsetLeft) * viewBox[2]/parseInt($("#paper2").css("width")) - rect.attr("x");
        var pY= (ev.pageY - this.offsetTop)  * viewBox[3]/parseInt($("#paper2").css("height")) - rect.attr("y");
        rect.attr("width", pX);
        rect.attr("height", pY);
    });

    $("#paper2").mouseup(function(ev) {
        ev.preventDefault();
        var div= $("#paper2");
        div.unbind('mousemove');
        div.unbind('mouseup');
    })
});

paper2 is a div in which I have an svg element (so the svg element and the div have the same height/width). This is how I created the svg2 element:

var svg2;
var root2;
$(document).ready(function() {
    $("#paper2").svg({
        onLoad: function() {
            svg2= $("#paper2").svg('get');
            svg2.configure({id: 'svg2'});
            var div= $("#paper2");
            root2= svg2.root();
            $("#svg2").attr("viewBox", viewBox[0]+','+viewBox[1]+','+viewBox[2]+','+viewBox[3]);    
        },
        settings: {}
    });
}

If you not using viewbox on the svg element you don't need this on the calculations:

 * viewBox[2]/parseInt($("#paper2").css("*****"));

viewbox[2] would be the viewbox width and viewbox[3] would be the viewbox height.

like image 36
Hoffmann Avatar answered Oct 21 '22 01:10

Hoffmann