svg-drag-select
v0.4.2
Published
A vanilla-js module for adding select-on-drag behavior to inline SVG elements.
Downloads
519
Maintainers
Readme
svg-drag-select
Add select-on-drag behavior to inline SVG elements.
No dependencies and lightweight (~ 1.8 kB minified gzipped).
Demo
Installation
via npm (with a module bundler)
$ npm i svg-drag-select
import svgDragSelect from "svg-drag-select"
via CDN (jsDelivr)
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script>/* `window.svgDragSelect` function is available */</script>
Usage and options
- JavaScript
const {
cancel, // cleanup funciton.
// please call `cancel()` 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.
// (in case of Safari, a `MouseEvent` 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.
// (in case of Safari, a `MouseEvent` or a `TouchEvent` is used instead.)
selectedElements, // selected element array.
previousSelectedElements, // previous selected element array.
newlySelectedElements, // `selectedElements - previousSelectedElements`
newlyDeselectedElements, // `previousSelectedElements - selectedElements`
}) {
// 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.
// (in case of Safari, a `MouseEvent` 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
/* please setup drag area overlay styles. for example: */
.svg-drag-select-area-overlay {
border: 1px dotted gray;
background-color: rgba(255,255,255,.4);
}
Custom Selector
You may need to implement your own selector function because:
- If
"intersection"
is specified asselector
option,- If
svg.getIntersectionList
is available,svg.getIntersectionList(referenceElement, dragAreaInInitialSvgCoordinate)
is used.- Chrome and Safari's
getIntersectionList()
implementation is poor: they seem to check only bounding boxes.
- Chrome and Safari's
- If
svg.getIntersectionList
is not available,svg-drag-select
uses its own implementation that checks only bounding boxes.
- If
- Implementing a good
"intersection"
selector is so hard for me because stricity and performance are in a trade-off relationship.- (BTW, IE 11 seems to have a good
SVGSVGElement.prototype.getIntersectionList()
implementation...)
- (BTW, IE 11 seems to have a good
The following is a custom selector example written for demo.
const strictIntersectionSelector = ({
svg, // the svg element.
referenceElement, // please select only descendants of this SVGElement if specified.
pointerEvent, // a `PointerEvent` instance with either a "pointerdown" event or a "pointermove" event.
// (in case of Safari, a `MouseEvent` or a `TouchEvent` is used instead.)
dragAreaInClientCoordinate, // a `SVGRect` that represents the dragging area in client coordinate.
dragAreaInSvgCoordinate, // a `SVGRect` that represents the dragging area in svg coordinate.
dragAreaInInitialSvgCoordinate, // a `SVGRect` 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 <path>s.
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,
/* ... */
})