Is there a polyfill for getIntersectionList, getEnclosureList, checkIntersection, checkEnclosure for Firefox SVG?
Asked Answered
D

2

48

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?

Destinydestitute answered 20/1, 2016 at 7:14 Comment(1)
As an alternative until Firefox implements the said methods, have you tried kld-intersections ?Chichi
C
1

I 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 compare 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.compareDocumentPosition(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 completely 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.compareDocumentPosition(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 completely 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 completely 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 completely enclosed by the second one.

Consumable answered 20/2, 2023 at 19:46 Comment(0)
U
0

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.org/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.

Unfamiliar answered 4/10, 2023 at 16:23 Comment(1)
We also had difficulty implementing this polyfill in Firefox because it determines the boundingboxes in the svg space. Our SVG's include scale and translate transformations I think we had trouble with getBBox() returning a meaningful rectangle. The hack that we ended up going with was to use getBoundingClientRect() -- which worked for us but isn't a direct polyfill of the SVGSVGElement methods.Unfamiliar

© 2022 - 2024 — McMap. All rights reserved.