Search code examples
svgbatik

Drawing an SVG Selection Box with Batik


I notice on many SVG builder tools that almost each element can be resized and rotated. As shown below

enter image description hereenter image description here

What is the most common way to implement that whenever an element whenever is clicked, a bordering line will appear a long side with the small rectangles used for resizing as well as rotating ?

Are these objects "hidden" by default and only visible during mouse click ? Or they have to be drawn on every mousedown and removed on every mouseup ?


Solution

  • Are these objects "hidden" by default and only visible during mouse click ?

    Unless you're using a library that provides that functionality i.e FabricJS, then no you have to draw that selection rectangle yourself.


    All the tools you see floating around essentially have code somewhere to create this rectangle you see exactly on top of the selected item - this rectangle is called the visible Bounding Box of the element - in modern browsers this is fetched using the el.getBBox() method.

    The returned bounding box gives you all the information you need(x, y , width, height) to draw that selection rectangle yourself.

    Typically you have a canvas on which you work (might be a <div>, might be an SVG rectangle etc...) - the concept is not exclusive to HTML5 canvas.

    • You draw the selection rectangle when clicking an element.
    • You remove the selection rectangle when clicking on a canvas empty area.

    Here is a snippet I've made for this which also allows multi-selection by holding 'Shift'.

    const svgns = 'http://www.w3.org/2000/svg'
    
    // Draw selection box if we click on one or more elements.
    $('.element').click(function() {
      const bbox = $(this)[0].getBBox()
    
      if (shiftKeyDown) {
        drawSelectionRect(bbox.x, bbox.y, bbox.width, bbox.height)
    
        return
      }
    
      $('.selectionRect').remove()
      drawSelectionRect(bbox.x, bbox.y, bbox.width, bbox.height)
    })
    
    
    // Remove selection box(es) if we click on empty area.
    $('#svgCanvas').click(() => {
      $('.selectionRect').remove()
    })
    
    
    // Helper function which draws a selection box.
    const drawSelectionRect = (x, y, width, height) => {
      var rect = document.createElementNS(svgns, 'rect')
      rect.setAttributeNS(null, 'x', x)
      rect.setAttributeNS(null, 'y', y)
      rect.setAttributeNS(null, 'height', height)
      rect.setAttributeNS(null, 'width', width)
    
      rect.setAttributeNS(null, 'stroke-width', '2px')
      rect.setAttributeNS(null, 'stroke', 'red')
      rect.setAttributeNS(null, 'fill', 'none')
      rect.setAttributeNS(null, 'stroke-dasharray', '5,1')
      rect.setAttributeNS(null, 'class', 'selectionRect')
    
      document.getElementById('workarea').appendChild(rect)
    }
    
    
    // Determine if Shift key is being pressed.
    let shiftKeyDown = false
    
    $(document).keydown(e => {
      if (e.keyCode == 16) {
        shiftKeyDown = true
      }
    })
    
    $(document).keyup(e => {
      shiftKeyDown = false
    })
    #container {
      display: block;
      height: 320px;
      background-color: #eee;
    }
    
    .element {
      cursor: pointer;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <ul>
      <li>Click on any element to draw it's selection box.</li>
      <li>Hold down "Shift" to allow multi-selection.</li>
      <li>Click anywhere else on the canvas to remove selections.</li>
    </ul>
    
    <div id="container">
      <svg  id="workarea" width="100%" height="100%">
        <rect id="svgCanvas" width="100%" height="100%" x="0" y="0" style="fill:transparent"/>
        <rect class="element" width="100" height="100" x="50" y="100" style="fill:#009688;" />
        <rect class="element" width="75" height="100" x="250" y="150" style="fill:#009688;" />
      </svg>
    </div>