Skip to content

Instantly share code, notes, and snippets.

@tkadlec
Last active August 17, 2024 19:33
Show Gist options
  • Save tkadlec/683b26344cde774170b94c0fcf0088b4 to your computer and use it in GitHub Desktop.
Save tkadlec/683b26344cde774170b94c0fcf0088b4 to your computer and use it in GitHub Desktop.
CSS used to highlight potential performance issues
:root {
--violation-color: red; /* used for clear issues */
--warning-color: orange; /* used for potential issues we should look into */
}
/* IMAGES */
/*
* Lazy-Loaded Images Check
* ====
* Highlight any lazy loaded images so we can see if any are inside the viewport
*
* Uses an outline so it can pair with Unsized Images and Legacy Format checks
* Credit: https://twitter.com/csswizardry/status/1346477682544951296
*/
img[loading=lazy] {
outline: 10px solid var(--warning-color) !important;
}
/*
* Unsized Images Check
* ====
* Highlight images that don't have a height or width attribute set
*
* Uses a border so it can pair with Lazy-Loaded and Legacy Format checks
*/
img:not([height]), img:not([width]) {
border: 10px solid var(--violation-color) !important;
}
/*
* Legacy Format Check
* ====
* Highlight tiff's and bmp's because we can do better
* Also JPG's because maybe we can use something like webp or avif instead
*
* Use opacity so we don't conflict with Lazy-Loaded and Unsized Images checks
*/
img[src*='.jpg'],
img[src*='.tiff'],
img[src*='.bmp']{
opacity: .5 !important;
}
/* SCRIPTS */
/* Synchronous Scripts Check
* ====
* Display any blocking synchronous scripts
*
* Credit: https://twitter.com/csswizardry/status/1336007323337285633
*/
head,
script[src] {
display: block;
border: 10px solid var(--violation-color);;
}
/*
* Display the URL/filepath of external scripts
*/
script[src]::before {
content: attr(src);
font-size: 1rem;
}
/**
* Hide other head content and non-blocking scripts
*/
head *,
script[src][async], script[src][defer], script[src][type=module] {
display: none;
}
@tkadlec
Copy link
Author

tkadlec commented Jan 22, 2021

@tpiros Yeah....very true and definitely a limitation of checking via CSS. Still handy for a quick "oh, I should double check that quick" kinda thing.

@tpiros
Copy link

tpiros commented Jan 22, 2021

I was thinking of an alternative but nothing came to mind. Probably you can also add some minimal JS to achieve that.

@tpiros
Copy link

tpiros commented Jan 22, 2021

I have an idea. I will post it here soon which may work nicely.

@tpiros
Copy link

tpiros commented Jan 25, 2021

Took me a while, but here's an interesting solution. Probably longer than I wanted but gives nice, in-place detection for images. These are steps that are needed to make this work:

  1. Add the following to the stylesheet (OPTIONAL):
picture:after {
  content: '⚠️';
  margin: -1em;
  font-size: xx-large;
}
  1. Add the following JS to the page:
document.addEventListener('DOMContentLoaded', async () => {
  const isFirefox = navigator.userAgent.includes('Firefox');
  const isChrome = navigator.userAgent.includes('Chrome');
  const isSafari = navigator.userAgent.includes('Safari');
  let accept = [];
  let headers = {
    'User-Agent': navigator.userAgent,
  };

  if (isFirefox) {
    const firefoxVersion = navigator.userAgent
      .split('/')
      .pop()
      .split('.')[0];
    if (firefoxVersion >= 65) {
      headers = {
        ...headers,
        Accept: 'image/webp',
      };
      accept.push('image/webp');
    } else {
      accept.push('image/jpeg');
    }
  }

  if (isChrome) {
    headers = {
      ...headers,
      Accept: 'image/webp',
    };
    accept.push('image/webp');
  }

  if (isSafari && !isChrome) {
    accept.push('image/jp2');
  }

  const getImageData = async (image) => {
    const response = await fetch(image.src, {
      method: 'HEAD',
      headers,
    });

    const contentType = (await response.headers
      .get('content-type')
      .includes(';'))
      ? await response.headers.get('content-type').split(';')[0]
      : await response.headers.get('content-type');

    const data = {
      contentType,
    };
    return data;
  };
  const images = document.getElementsByTagName('img');

  const wrap = (elementToWrap) => {
    const wrappingElement = document.createElement('picture');
    elementToWrap.parentNode.appendChild(wrappingElement);
    return wrappingElement.appendChild(elementToWrap);
  };

  for (let image of images) {
    if (!accept.includes((await getImageData(image)).contentType)) {
      wrap(image);
      image.style.opacity = 0.5;
    }
  }
});

The wrap function is optional. Since there's no way to use the :after pseudo-selector for an img element, we need to wrap all imgs to a picture element. This is only a convenient way to differentiate with the "default" opacity behaviour specified in the original gist.

Attached is an example for an image that is served from Cloudinary:

<img
  src="https://res.cloudinary.com/tamas-demo/image/upload/w_500/woman.jpg"
  alt="photo of a woman"
/>

Screenshot 2021-01-25 at 13 33 13

This renders the warning, since the image is viewed in Chrome but it has a JPG extension. Adding f_auto to the URL, will render a WebP:

<img
  src="https://res.cloudinary.com/tamas-demo/image/upload/w_500,f_auto/woman.jpg"
  alt="photo of a woman"
/>

Screenshot 2021-01-25 at 13 33 22

Please remember however that some Image CDNs do analyse the image and it may be more optimal to load a JPEG as opposed to a WebP image in Chrome as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment