Skip to content

Instantly share code, notes, and snippets.

@victor-homyakov
Created April 10, 2020 16:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save victor-homyakov/9c29b553270c5c71135a1746ab93325d to your computer and use it in GitHub Desktop.
Save victor-homyakov/9c29b553270c5c71135a1746ab93325d to your computer and use it in GitHub Desktop.
Detect resized images on a page. Outline images and write detailed info in console.
/**
* Detect resized images.
*/
(function() {
var Img = function(imgElement) {
this.img = imgElement;
this.src = this.img.src;
this.className = this.img.className;
};
Img.prototype = {
ignoredImageSources: [
// e.g. 'https://example.com/placeholder.gif'
],
calculateDimensions: function() {
this.w = this.img.offsetWidth;
this.h = this.img.offsetHeight;
this.nw = this.img.naturalWidth;
this.nh = this.img.naturalHeight;
this.calculateScale();
return Promise.resolve();
},
calculateScale: function() {
this.xScale = this.nw / this.w;
this.yScale = this.nh / this.h;
},
checkDimensions: function() {
if (this.shouldIgnoreImageBefore()) {
return;
}
this.calculateDimensions().then(function() {
if (this.shouldIgnoreImageAfter()) {
return;
}
var w = this.w,
h = this.h,
nw = this.nw,
nh = this.nh;
if (w === 0 || h === 0) {
// Hidden image
// this.report('Hidden image, could be made lazy');
return;
}
if (nw <= w && nh <= h) {
// Magnified image, do not report
return;
}
if (this.xScale < 3 && this.xScale > 1 / 3 && this.yScale < 3 && this.yScale > 1 / 3) {
// Scale less than 3x is OK
return;
}
var extraTrafficKb = Math.round((nw * nh - w * h) / 10000);
if (extraTrafficKb < 5) {
return;
}
this.report(
'Scaled image: appx. extra traffic ' + extraTrafficKb + 'kB' +
' visible size: ' + w + 'x' + h
);
}.bind(this));
},
shouldIgnoreImageBefore: function() {
return this.ignoredImageSources.indexOf(this.src) !== -1;
},
matches: function(props) {
for (var prop in props) {
if (props.hasOwnProperty(prop) && props[prop] !== this[prop]) {
return false;
}
}
return true;
},
/**
* Customizable list of ignored images.
* Each entry should have one or more properties, e.g.
* { w: 16, h: 16 } - ignore all 16x16 images
* { className: 'image thumb', nw: 500 } - ignore .image.thumb images with natural width 500px
*/
ignoredImages: [
// 1x1 placeholder
{ nw: 1, nh: 1 },
// iPhone 2x retina
{ xScale: 2, yScale: 2 },
// iPhoneX 3x retina
{ xScale: 3, yScale: 3 }
],
shouldIgnoreImageAfter: function() {
return this.ignoredImages.some(function(props) {
return this.matches(props);
}, this);
},
report: function(message) {
message += ' natural size: ' + this.nw + 'x' + this.nh;
message += ' class: "' + this.className + '"';
if (!this.src.startsWith('data:image')) {
message += ' src: ' + this.src;
}
console.log(message, this.img);
this.img.style.outline = '1px dotted red';
}
};
var BgImg = function(container) {
var backgroundImage = getComputedStyle(container).backgroundImage;
if (
backgroundImage.startsWith('url(') && backgroundImage.indexOf('url(', 5) !== -1
) {
// Check only first url() TODO check all urls
backgroundImage = backgroundImage.replace(/,\s*url\(.+$/, '');
}
this.src = backgroundImage.replace(/^url\("?|"?\)/g, '');
this.img = container;
this.className = container.className;
};
BgImg.prototype = {
calculateDimensions: function() {
return Promise.all([
this.calculateImgDimensions(),
this.calculateBgDimensions()
]).then(function() {
this.calculateScale();
}.bind(this));
},
calculateImgDimensions: function() {
return new Promise(function(resolve) {
var img = new Image();
img.onload = function() {
img.onload = img.onerror = null;
this.nw = img.naturalWidth;
this.nh = img.naturalHeight;
resolve();
}.bind(this);
img.onerror = function() {
// Ignore errors
img.onload = img.onerror = null;
this.nw = this.nh = 0;
resolve();
}.bind(this);
img.src = this.src;
}.bind(this));
},
calculateBgDimensions: function() {
var backgroundSize = this.img.style.backgroundSize;
if (backgroundSize) {
var match = backgroundSize.match(/(\d+)px (\d+)px/);
if (match) {
this.w = parseInt(match[1]);
this.h = parseInt(match[2]);
return;
}
}
this.w = this.img.offsetWidth || 0;
this.h = this.img.offsetHeight || 0;
},
shouldIgnoreImageBefore: function() {
var src = this.src;
if (
src === 'none' ||
this.ignoredImageSources.indexOf(src) !== -1
) {
return true;
}
if (src.startsWith('data:image/')) {
// Do not check short data-url
return src.length < 1000;
}
return !/^(https?:\/\/|\/\/)/.test(src);
},
report: Img.prototype.report,
ignoredImageSources: Img.prototype.ignoredImageSources,
ignoredImages: Img.prototype.ignoredImages,
matches: Img.prototype.matches,
calculateScale: Img.prototype.calculateScale,
checkDimensions: Img.prototype.checkDimensions,
shouldIgnoreImageAfter: Img.prototype.shouldIgnoreImageAfter
};
var i;
var images = document.querySelectorAll('img[src]');
// console.log('Found', images.length, 'images');
for (i = 0; i < images.length; i++) {
new Img(images[i]).checkDimensions();
}
/*
var elementsWithBgImages = document.querySelectorAll('[style*="background"][style*="url("]');
console.log('Found', elementsWithBgImages.length, 'background images');
for (i = 0; i < elementsWithBgImages.length; i++) {
new BgImg(elementsWithBgImages[i]).checkDimensions();
}
*/
var allElements = document.querySelectorAll('*');
for (i = 0; i < allElements.length; i++) {
new BgImg(allElements[i]).checkDimensions();
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment