I'm using the method getClientRects() in one of my JavaScript function. The function works correctly in Chrome, Edge and Safari but fails in Firefox. The reason is that getClientRects() behaves differently in Firefox.
The sample is available here in CodePen
var el = document.getElementById('parent');
var range = document.createRange();
range.selectNode(el);
var clientRects = range.getClientRects();
console.log(clientRects);
<div class="parent" id='parent'>
<div class="child">Bacon ipsum dolor amet meatball bresaola t-bone tri-tip brisket. Jowl pig picanha cupim landjaeger, frankfurter spare ribs chicken. Porchetta jowl pancetta drumstick shankle cow spare ribs jerky tail kevin biltong capicola brisket venison bresaola. Flank sirloin jowl andouille meatball venison salami ground round rump boudin turkey capicola t-bone. Sirloin filet mignon tenderloin beef, biltong doner bresaola brisket shoulder pork loin shankle turducken shank cow. Bacon ball tip sirloin ham.
</div>
<div id="info">Click somewhere in the paragraph above</div>
</div>
Run the sample in Chrome and Firefox to see the difference. Is this a bug in Firefox?
I tried to refer the documentation for any insights but unable to get clarity.
This is indeed an interop issue, and I opened an issue on the specs to get that clarified.
The specs ask that when the Range#getClientRects()
method is called,
For each element selected by the range, whose parent is not selected by the range, include the border areas returned by invoking
getClientRects()
on the element.
In your Range, there is only a single element whose parent is not selected by the range: #parent
.
So we are supposed to get a DOMRectList composed of #parent
's DOMRect. And for this, all browsers do agree, there is only one such DOMRect for this element:
var el = document.getElementById("parent");
// Element#getClientRects();
var clientRects = el.getClientRects();
console.log(clientRects.length); // 1 everywhere
<div class="parent" id='parent'>
<div class="child">Bacon ipsum dolor amet meatball bresaola t-bone tri-tip brisket. Jowl pig picanha cupim landjaeger, frankfurter spare ribs chicken. Porchetta jowl pancetta drumstick shankle cow spare ribs jerky tail kevin biltong capicola brisket venison bresaola. Flank sirloin jowl andouille meatball venison salami ground round rump boudin turkey capicola t-bone. Sirloin filet mignon tenderloin beef, biltong doner bresaola brisket shoulder pork loin shankle turducken shank cow. Bacon ball tip sirloin ham.
</div>
<div id="info">Click somewhere in the paragraph above</div>
</div>
But where browsers do disagree is on the second step.
For each Text node selected or partially selected by the range [...]
Firefox apparently considers only the Text nodes that are accessible at the first of the Range's content, while Chrome and Safari will traverse every elements looking for these Text nodes.
If you did select the content of this element instead, then Firefox would return two DOMRects, the one of .child
and the one of #info
:
var el = document.getElementById('parent');
var range = document.createRange();
// select the content, not the node itself
range.selectNodeContents(el);
var clientRects = range.getClientRects();
console.log(clientRects.length); // 2 in Firefox
<div class="parent" id='parent'>
<div class="child">Bacon ipsum dolor amet meatball bresaola t-bone tri-tip brisket. Jowl pig picanha cupim landjaeger, frankfurter spare ribs chicken. Porchetta jowl pancetta drumstick shankle cow spare ribs jerky tail kevin biltong capicola brisket venison bresaola. Flank sirloin jowl andouille meatball venison salami ground round rump boudin turkey capicola t-bone. Sirloin filet mignon tenderloin beef, biltong doner bresaola brisket shoulder pork loin shankle turducken shank cow. Bacon ball tip sirloin ham.
</div>
<div id="info">Click somewhere in the paragraph above</div>
</div>
And if you went even farther and selected the content of .child
, you'd finally get one box per line in every browsers:
var el = document.querySelector('.child');
var range = document.createRange();
// select the content, not the node itself
range.selectNodeContents(el);
var clientRects = range.getClientRects();
console.log(clientRects.length); // all browsers agree, 1 box per line
<div class="parent" id='parent'>
<div class="child">Bacon ipsum dolor amet meatball bresaola t-bone tri-tip brisket. Jowl pig picanha cupim landjaeger, frankfurter spare ribs chicken. Porchetta jowl pancetta drumstick shankle cow spare ribs jerky tail kevin biltong capicola brisket venison bresaola. Flank sirloin jowl andouille meatball venison salami ground round rump boudin turkey capicola t-bone. Sirloin filet mignon tenderloin beef, biltong doner bresaola brisket shoulder pork loin shankle turducken shank cow. Bacon ball tip sirloin ham.
</div>
<div id="info">Click somewhere in the paragraph above</div>
</div>
So if you want Chrome's behavior in all browsers, you'd have to manually walk through each Text node and retrieve their DOMRect one by one.