SVG 1.1 Support in Firefox:
- SVGSVGElement:
- Unimplemented bindings: getIntersectionList, getEnclosureList, checkIntersection, checkEnclosure, deselectAll
SVG 1.1 spec: 5.11.2 Interface SVGSVGElement
Since Firefox does not support getIntersectionList, getEnclosureList, checkIntersection, checkEnclosure methods, is there a polyfill? Or how to write a polyfill for the 4 methods in JavaScript?
SVG 1.1 Support in Firefox:
- SVGSVGElement:
- Unimplemented bindings: getIntersectionList, getEnclosureList, checkIntersection, checkEnclosure, deselectAll
SVG 1.1 spec: 5.11.2 Interface SVGSVGElement
Since Firefox does not support getIntersectionList, getEnclosureList, checkIntersection, checkEnclosure methods, is there a polyfill? Or how to write a polyfill for the 4 methods in JavaScript?
Share Improve this question edited Jun 11, 2022 at 0:51 Hasan Haghniya 2,5554 gold badges22 silver badges32 bronze badges asked Jan 20, 2016 at 7:14 cuixipingcuixiping 25.5k9 gold badges87 silver badges94 bronze badges 1- 3 As an alternative until Firefox implements the said methods, have you tried kld-intersections ? – Alex Pappas Commented Oct 24, 2019 at 2:53
2 Answers
Reset to default 1I dont think there is any official polyfill for those functions in Firefox right now unfortunately.
I gave this a shot to see if there was a way to easily polyfill those functions with available API.
I, intentionaly, assigned a second function so we can pare the original supported function (when using a browser that natively support it like Chrome) with the polyfill.
So, here we are for getIntersectionList
:
const getIntersectionListPolyfill = function(rect, referenceElement) {
var intersectionList = [];
var root = this.ownerSVGElement || this;
// Get all elements that intersect with rect
var elements = root.querySelectorAll('*');
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
if (element !== this && element instanceof SVGGraphicsElement) {
var bbox = element.getBBox();
if (rect.width && rect.height && bbox.width && bbox.height) {
if (bbox.x + bbox.width > rect.x &&
bbox.y + bbox.height > rect.y &&
bbox.x < rect.x + rect.width &&
bbox.y < rect.y + rect.height) {
intersectionList.push(element);
}
}
}
}
// Sort elements in document order
intersectionList.sort(function(a, b) {
return (a.pareDocumentPosition(b) & 2) ? 1 : -1;
});
// Filter elements by referenceElement
if (referenceElement) {
intersectionList = intersectionList.filter(function(element) {
return element === referenceElement || element.contains(referenceElement);
});
}
return intersectionList;
}
if (!SVGElement.prototype.getIntersectionList) {
SVGElement.prototype.getIntersectionList = getIntersectionListPolyfill;
}
// The code below is for the snippet only
SVGElement.prototype.getIntersectionList2 = getIntersectionListPolyfill;
const mySVG = document.getElementById('mySVG');
const myRect = mySVG.createSVGRect();
myRect.width = myRect.height = 1;
myRect.x = myRect.y = 20;
console.log('Original', mySVG.getIntersectionList(myRect, null).length);
console.log('Polyfill', mySVG.getIntersectionList2(myRect, null).length);
svg {
display: block;
border: 1px solid #000;
margin: 20px 0;
visibility: visible;
}
rect, circle {
fill: rgba(255, 0, 0, 0.2);
visibility: visiblePainted;
}
<svg id="mySVG" width="500" height="400">
<rect x="10" y="10" width="200" height="100"></rect>
<rect x="20" y="20" width="200" height="100"></rect>
<circle cx="70" cy="70" r="50"></circle>
</svg>
This polyfill extends the SVGElement
prototype with a getIntersectionList
function that imit the native implementation. It uses querySelectorAll
to get every elements of the SVG and checks if they intersect with the given rect. It then sorts the elements in document order and filters them by the reference element, if provided.
getEnclosureList
is pretty similar:
const getEnclosureListPolyfill = function(rect, referenceElement) {
var enclosureList = [];
var root = this.ownerSVGElement || this;
// Get all elements that are pletely enclosed by rect
var elements = root.querySelectorAll('*');
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
if (element !== this && element instanceof SVGGraphicsElement) {
var bbox = element.getBBox();
if (rect.width && rect.height && bbox.width && bbox.height) {
if (bbox.x >= rect.x &&
bbox.y >= rect.y &&
bbox.x + bbox.width <= rect.x + rect.width &&
bbox.y + bbox.height <= rect.y + rect.height) {
enclosureList.push(element);
}
}
}
}
// Sort elements in document order
enclosureList.sort(function(a, b) {
return (a.pareDocumentPosition(b) & 2) ? 1 : -1;
});
// Filter elements by referenceElement
if (referenceElement) {
enclosureList = enclosureList.filter(function(element) {
return element === referenceElement || element.contains(referenceElement);
});
}
return enclosureList;
};
if (!SVGElement.prototype.getEnclosureList) {
SVGElement.prototype.getEnclosureList = getEnclosureListPolyfill;
}
// The code below is for the snippet only
SVGElement.prototype.getEnclosureList2 = getEnclosureListPolyfill;
const mySVG = document.getElementById('mySVG');
const myRect = mySVG.createSVGRect();
myRect.width = myRect.height = 210;
myRect.x = myRect.y = 0;
console.log('Original', mySVG.getEnclosureList(myRect, null).length);
console.log('Polyfill', mySVG.getEnclosureList2(myRect, null).length);
svg {
display: block;
border: 1px solid #000;
margin: 20px 0;
visibility: visible;
}
rect, circle {
fill: rgba(255, 0, 0, 0.2);
visibility: visiblePainted;
}
<svg id="mySVG" width="500" height="400">
<rect x="10" y="10" width="200" height="100"></rect>
<rect x="20" y="20" width="200" height="100"></rect>
<circle cx="70" cy="70" r="50"></circle>
</svg>
This polyfill extends the SVGElement
prototype with a getEnclosureList
function that imit the native implementation. It uses querySelectorAll
to get every elements of the SVG and checks if they pletely enclosed by the given rect. It then sorts the elements in document order and filters them by the reference element, if provided.
checkIntersection
now:
const checkIntersectionPolyfill = function(element, rect) {
var root = this.ownerSVGElement || this;
// Get the bounding boxes of the two elements
var bbox1 = element.getBBox();
var bbox2 = rect;
// Check if the two bounding boxes intersect
if (bbox1.x + bbox1.width > bbox2.x &&
bbox1.y + bbox1.height > bbox2.y &&
bbox2.x + bbox2.width > bbox1.x &&
bbox2.y + bbox2.height > bbox1.y) {
// Check if the two elements actually intersect
var intersection = root.createSVGRect();
intersection.x = Math.max(bbox1.x, bbox2.x);
intersection.y = Math.max(bbox1.y, bbox2.y);
intersection.width = Math.min(bbox1.x + bbox1.width, bbox2.x + bbox2.width) - intersection.x;
intersection.height = Math.min(bbox1.y + bbox1.height, bbox2.y + bbox2.height) - intersection.y;
return intersection.width > 0 && intersection.height > 0;
} else {
return false;
}
};
if (!SVGElement.prototype.checkIntersection) {
SVGElement.prototype.checkIntersection = checkIntersectionPolyfill;
}
// The code below is for the snippet only
SVGElement.prototype.checkIntersection2 = checkIntersectionPolyfill;
const mySVG = document.getElementById('mySVG');
const myRect1 = document.getElementById('myRect1');
const myRect2 = mySVG.createSVGRect();
myRect2.width = myRect2.height = 100;
myRect2.x = myRect2.y = 0;
console.log('Original', mySVG.checkIntersection(myRect1, myRect2));
console.log('Polyfill', mySVG.checkIntersection2(myRect1, myRect2));
svg {
display: block;
border: 1px solid #000;
margin: 20px 0;
visibility: visible;
}
rect, circle {
fill: rgba(255, 0, 0, 0.2);
visibility: visiblePainted;
}
<svg id="mySVG" width="500" height="400">
<rect id="myRect1" x="10" y="10" width="200" height="100"></rect>
</svg>
This polyfill extends the SVGElement
prototype with a checkIntersection
function that imit the native implementation. It checks if the 2 elements intersect using a simple algorithm based on rectangle intersection. If the two bounding boxes intersect, it creates an SVGRect
object representing the intersection and checks if it has a positive area, indicating that the two elements actually intersect.
checkEnclosure
is probably the easiest to implement:
const checkEnclosurePolyfill = function(element, rect) {
var root = this.ownerSVGElement || this;
// Get the bounding boxes of the two elements
var bbox1 = rect;
var bbox2 = element.getBBox();
// Check if bbox2 is pletely enclosed by bbox1
return bbox1.x <= bbox2.x &&
bbox1.y <= bbox2.y &&
bbox1.x + bbox1.width >= bbox2.x + bbox2.width &&
bbox1.y + bbox1.height >= bbox2.y + bbox2.height;
};
if (!SVGElement.prototype.checkEnclosure) {
SVGElement.prototype.checkEnclosure = checkEnclosurePolyfill;
}
// The code below is for the snippet only
SVGElement.prototype.checkEnclosure2 = checkEnclosurePolyfill;
const mySVG = document.getElementById('mySVG');
const myRect1 = document.getElementById('myRect1');
const myRect2 = mySVG.createSVGRect();
myRect2.width = myRect2.height = 250;
myRect2.x = myRect2.y = 0;
console.log('Original', mySVG.checkEnclosure(myRect1, myRect2));
console.log('Polyfill', mySVG.checkEnclosure2(myRect1, myRect2));
svg {
display: block;
border: 1px solid #000;
margin: 20px 0;
visibility: visible;
}
rect, circle {
fill: rgba(255, 0, 0, 0.2);
visibility: visiblePainted;
}
<svg id="mySVG" width="500" height="400">
<rect id="myRect1" x="10" y="10" width="200" height="100"></rect>
</svg>
This polyfill extends the SVGElement
prototype with a checkEnclosure
function that imit the native implementation. It checks if the first element is pletely enclosed by the second one.
I think there is a minor bug in the above polyfills--
// Filter elements by referenceElement
if (referenceElement) {
enclosureList = enclosureList.filter(function(element) {
return element === referenceElement || element.contains(referenceElement);
});
}
it should instead be referenceElement.contains(element)
https://www.w3/TR/SVG11/struct.html#__svg__SVGSVGElement__getEnclosureList
SVGElement referenceElement
If not null, then any intersected element that doesn't have the referenceElement as ancestor must not be included in the returned NodeList.