1

Short version of the question

Is it possible to find only elements on a page that have a background-image or background: url set (including in stylesheets) without looping through every element on the page and using getComputedStyle(el);.

If not is it possible to optimise the elements I look through to reduce JS execution time?

Longer version of the question

As part of this related question I am trying to find a solution to gathering the size of all elements above the fold that may impact the "visually complete" state of the page.

The related question covers checking all CSS etc. is loaded so I am left with images (including background images) to check.

I am looking to make the following functions as performant as possible (as I may have to call it multiple times if I am unable to solve the main problem in the other question).

The main function is getRects(). I have included the checkRectangle function for completeness but the main concern is the way I am gathering candidates for the checkRectangle function (having to loop through every element on the page).

var doc = window.document;
var browserWidth = window.innerWidth || doc.documentElement.clientWidth;
var browserHeight = window.innerHeight || doc.documentElement.clientHeight;

function checkRectangle(el){
    var rtrn = false;
    
    
    if (el.getBoundingClientRect) {
       var rect =  el.getBoundingClientRect();
       //check if the bottom is above the top to ensure the element has height, same for width.
       //Then the last 4 checks are to see if the element is in the above the fold viewport.
       
       if (rect.bottom <= rect.top || rect.right <= rect.left || rect.right < 0 || rect.left > browserWidth || rect.bottom < 0 || rect.top > browserHeight) {
           rtrn = false;
       }else{
           rtrn = {};
           rtrn.bot = rect.bottom;
           rtrn.top = rect.top;
           rtrn.left = rect.left;
           rtrn.right = rect.right;
       }
   }
   
   return rtrn;
}



//function to get the rectangles above the fold (I do other things to check fonts are loaded etc. so images are the only thing left to check)
function getRects(){
    
    var rects = [];
     var elements = doc.getElementsByTagName('*');
    var re = /url\(.*(http.*)\)/ig;
    for (var i = 0; i < elements.length; i++) {
        var el = elements[i];
        var style = getComputedStyle(el);
        
        if(el.tagName == "IMG"){
            var rect = checkRectangle(el);
            if(rect){
                //The URL is stored here for later processing where I match performance timings to the element, it is not relevant other than to show why I convert the `getBoundingClientRect()` to a simple object. 
                rect.url = el.src;
                rects.push(rect);
            }
        }
        //I also need to check for background images set in either CSS or with inline styles.
        if (style['background-image']) {
             var rect = checkRectangle(el);
             if(rect){
                var matches = re.exec(style['background-image']);
                if (matches && matches.length > 1){
                    rect.url = matches[1].replace('"', '');
                    rects.push(rect);
                }
                
             }
            
        }
        
    }

Concerns / things that I can't work out

  1. I see no way of not looping through all elements on the page and using getComputedStyle(el) to check if they have a background-image set. If I can reduce the candidates sufficiently that would solve my problems.
  2. At the moment (due to having to call the function multiple times) I am not doing a check for background: url but that needs adding in as an efficient way as possible.
  3. Is there a way of discarding some elements on the page that I can guarantee are not "above the fold" that wouldn't carry a massive performance penalty (bearing in mind anything could be position: fixed at the top of the page?).

Things I know I can do

  1. If I can find a better way of checking for background and background-image then I know images become easier as I can use querySelectorAll and limit that list.

Additional information / thoughts

I am already tracking every network request using PerformanceObserver.

Is there perhaps a way I could look at every request, grab the file name if it is an image and then use the filename to work out where that image is displayed on the page, even if it is a background-image or background: url set in external CSS?

Alternative way of phrasing the question.

How could I possibly limit a list of elements that can make a network call for an image and how can I then check if they are above the fold as efficiently as possible?

GrahamTheDev
  • 22,724
  • 2
  • 32
  • 64
  • 2
    I don't think there is a way where you don't need to iterate over the all or some elements. However, one suggestion I can give is that, not all elements can have a background-image property, so you can run a querySelectorAll( ) on all the elements that can have the css background-image property and then loop over that nodelist to check if they have a background image or not. – Link Nov 09 '20 at 14:09
  • Do you happen to know where I can see what elements are valid (I am happy for just the majority to exclude edge cases) so I can build that selector? That would be a great help and possibly reduce the number of elements enough to make the function performant. – GrahamTheDev Nov 09 '20 at 14:11
  • [the spec](https://drafts.csswg.org/css-backgrounds-3/#background-image) seems to suggest it can apply to all elements from my understanding? – GrahamTheDev Nov 09 '20 at 14:13
  • You're right, my bad, it does apply to all elements. Sorry! – Link Nov 09 '20 at 14:17
  • hehe not an issue at all, at the moment any suggestions are welcome suggestions! – GrahamTheDev Nov 09 '20 at 14:34

0 Answers0