A vanilla-js module for adding select-on-drag behavior to inline SVG elements.
npm install svg-drag-selectAdd select-on-drag behavior to inline SVG elements.
No dependencies and lightweight (~ 1.8 kB minified gzipped).
Demo
```
$ npm i svg-drag-select
`js`
import svgDragSelect from "svg-drag-select"
`html`
* JavaScript
`jscancel()
const {
cancel, // cleanup funciton.
// please call when the select-on-drag behavior is no longer needed.
dragAreaOverlay, // a div element overlaying dragging area.
// you can customize the style of this element.
// this element has "svg-drag-select-area-overlay" class by default.
} = svgDragSelect({
// the svg element (required).
svg: document.getElementById("my-svg"),
// followings are optional parameters with default values.
referenceElement: null, // selects only descendants of this SVGElement if specified.
selector: "enclosure", // "enclosure": selects enclosed elements using getEnclosureList().
// "intersection": selects intersected elements using getIntersectionList().
// function: custom selector implementation
// followings are optional selection handlers
onSelectionStart({
svg, // the svg element.
pointerEvent, // a PointerEvent instance with "pointerdown" type.MouseEvent
// (in case of Safari, a or a TouchEvent is used instead.)
cancel, // cancel() cancels.
}) {
// for example: handles mouse left button only.
if (pointerEvent.button !== 0) {
cancel()
return
}
// for example: clear "data-selected" attribute
const selectedElements = svg.querySelectorAll('[data-selected]')
for (let i = 0; i < selectedElements.length; i++) {
selectedElements[i].removeAttribute('data-selected')
}
},
onSelectionChange({
svg, // the svg element.
pointerEvent, // a PointerEvent instance with either a "pointerdown" event or a "pointermove" event.MouseEvent
// (in case of Safari, a or a TouchEvent is used instead.)selectedElements - previousSelectedElements
selectedElements, // selected element array.
previousSelectedElements, // previous selected element array.
newlySelectedElements, // previousSelectedElements - selectedElements
newlyDeselectedElements, //
}) {
// for example: toggle "data-selected" attribute
newlyDeselectedElements.forEach(element => element.removeAttribute('data-selected'))
newlySelectedElements.forEach(element => element.setAttribute('data-selected', ''))
},
onSelectionEnd({
svg, // the svg element.
pointerEvent, // a PointerEvent instance with either a "pointerup" event or a "pointercancel" event.MouseEvent
// (in case of Safari, a or a TouchEvent is used instead.)
selectedElements, // selected element array.
}) {
},
})
// cleanup for when the select-on-drag behavior is no longer needed
// (including unbinding of the event listeners)
cancel()
`
* CSS
`css`
/ please setup drag area overlay styles. for example: /
.svg-drag-select-area-overlay {
border: 1px dotted gray;
background-color: rgba(255,255,255,.4);
}
You may need to implement your own selector function because:
* If "intersection" is specified as selector option,svg.getIntersectionList
* If is available, svg.getIntersectionList(referenceElement, dragAreaInInitialSvgCoordinate)
is used.getIntersectionList()
* Chrome and Safari's implementation is poor: they seem to check only bounding boxes.svg.getIntersectionList
* If is not available, svg-drag-select
uses its own implementation that checks only bounding boxes.getIntersectionList()
* Firefox does not yet support and getEnclosureList()."intersection"
* Implementing a good selector is so hard for me because stricity and performance are in a trade-off relationship.SVGSVGElement.prototype.getIntersectionList()
* (BTW, IE 11 seems to have a good implementation...)
The following is a custom selector example written for demo.
`jsPointerEvent
const strictIntersectionSelector = ({
svg, // the svg element.
referenceElement, // please select only descendants of this SVGElement if specified.
pointerEvent, // a instance with either a "pointerdown" event or a "pointermove" event.MouseEvent
// (in case of Safari, a or a TouchEvent is used instead.)SVGRect
dragAreaInClientCoordinate, // a that represents the dragging area in client coordinate.SVGRect
dragAreaInSvgCoordinate, // a that represents the dragging area in svg coordinate.SVGRect
dragAreaInInitialSvgCoordinate, // a that represents the dragging area in initial viewport coordinate of the svg.getEnclosures()
getEnclosures, // returns elements enclosed in the dragging area.getIntersections()
getIntersections, // returns elements intersect the dragging area.
// Chrome, Safari and Firefox checks only bounding box intersection.
}) => getIntersections().filter(element => {
// the element that the pointer event raised is considered to intersect.
if (pointerEvent.target === element) {
return true
}
// strictly check only
if (!(element instanceof SVGPathElement)) {
return true
}
// check if there is at least one enclosed point in the path.
for (let i = 0, len = element.getTotalLength(); i <= len; i += 4 / arbitrary /) {
const { x, y } = element.getPointAtLength(i)
if (
dragAreaInSvgCoordinate.x <= x && x <= dragAreaInSvgCoordinate.x + dragAreaInSvgCoordinate.width &&
dragAreaInSvgCoordinate.y <= y && y <= dragAreaInSvgCoordinate.y + dragAreaInSvgCoordinate.height
) {
return true
}
}
return false
})
svgDragSelect({
selector: strictIntersectionSelector,
/ ... /
})
``