Created
April 10, 2020 16:09
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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