core(responsive-images): find offscreen images larger than viewport (#10506)

This commit is contained in:
Patrick Hulce 2020-04-06 11:31:05 -05:00 коммит произвёл GitHub
Родитель ec45d53f3f
Коммит 2f11010a8a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 68 добавлений и 27 удалений

Двоичные данные
lighthouse-cli/test/fixtures/byte-efficiency/lighthouse-2048x1356.webp поставляемый Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 41 KiB

Просмотреть файл

@ -85,15 +85,21 @@ setTimeout(() => {
<!-- PASS(optimized): image is JPEG optimized -->
<!-- FAIL(webp): image is not WebP optimized -->
<!-- FAIL(responsive): image is 25% used at DPR 2 -->
<!-- FAIL(responsive): image is 50% used at DPR 3 -->
<!-- PASS(offscreen): image is onscreen -->
<img style="width: 256px; height: 170px;" src="lighthouse-1024x680.jpg">
<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image is WebP optimized -->
<!-- FAIL(responsive): image is 25% used at DPR 3 -->
<!-- PASS(offscreen): image is offscreen but lazily loaded -->
<img style="width: 0px; height: 0px;" src="lighthouse-2048x1356.webp?size0" loading="lazy">
<!-- PASS(optimized): image is JPEG optimized -->
<!-- FAIL(webp): image is not WebP optimized -->
<!-- PASS(responsive): image is fully used at DPR 2 -->
<!-- PASS(responsive): image is fully used at DPR 3 -->
<!-- PASS(offscreen): image is onscreen -->
<img style="width: 240px; height: 160px;" src="lighthouse-480x320.jpg">
<img style="width: 160px; height: 110px;" src="lighthouse-480x320.jpg">
<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image has insigificant WebP savings -->
@ -103,7 +109,7 @@ setTimeout(() => {
<!-- PASS(optimized): image is JPEG optimized -->
<!-- PASS(webp): image is WebP optimized -->
<!-- FAIL(responsive): image is 25% used at DPR 2 (but small savings) -->
<!-- FAIL(responsive): image is 50% used at DPR 3 (but small savings) -->
<!-- FAIL(offscreen): image is offscreen -->
<img style="margin-top: 1000px; width: 120px; height: 80px;" src="lighthouse-480x320.webp">

Просмотреть файл

@ -212,13 +212,13 @@ const expectations = [
},
},
'uses-responsive-images': {
displayValue: 'Potential savings of 53\xa0KB',
details: {
overallSavingsBytes: '>50000',
overallSavingsBytes: '82000 +/- 5000',
items: {
0: {wastedPercent: '<46'},
1: {wastedPercent: '<46'},
length: 2,
0: {wastedPercent: '45 +/- 5', url: /lighthouse-1024x680.jpg/},
1: {wastedPercent: '72 +/- 5', url: /lighthouse-2048x1356.webp\?size0/},
2: {wastedPercent: '45 +/- 5', url: /lighthouse-480x320.webp/},
length: 3,
},
},
},

Просмотреть файл

@ -48,28 +48,42 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
/**
* @param {LH.Artifacts.ImageElement} image
* @param {number} DPR devicePixelRatio
* @param {LH.Artifacts.ViewportDimensions} ViewportDimensions
* @return {null|Error|LH.Audit.ByteEfficiencyItem};
*/
static computeWaste(image, DPR) {
static computeWaste(image, ViewportDimensions) {
// Nothing can be done without network info.
if (!image.resourceSize) {
return null;
}
let usedPixels = image.displayedWidth * image.displayedHeight *
Math.pow(ViewportDimensions.devicePixelRatio, 2);
// If the image has 0 dimensions, it's probably hidden/offscreen, so we'll be as forgiving as possible
// and assume it's the size of two viewports. See https://github.com/GoogleChrome/lighthouse/issues/7236
if (!usedPixels) {
const viewportWidth = ViewportDimensions.innerWidth;
const viewportHeight = ViewportDimensions.innerHeight * 2;
const imageAspectRatio = image.naturalWidth / image.naturalHeight;
const viewportAspectRatio = viewportWidth / viewportHeight;
let usedViewportWidth = viewportWidth;
let usedViewportHeight = viewportHeight;
if (imageAspectRatio > viewportAspectRatio) {
usedViewportHeight = viewportWidth / imageAspectRatio;
} else {
usedViewportWidth = viewportHeight * imageAspectRatio;
}
usedPixels = usedViewportWidth * usedViewportHeight *
Math.pow(ViewportDimensions.devicePixelRatio, 2);
}
const url = URL.elideDataURI(image.src);
const actualPixels = image.naturalWidth * image.naturalHeight;
const usedPixels = image.displayedWidth * image.displayedHeight * Math.pow(DPR, 2);
const wastedRatio = 1 - (usedPixels / actualPixels);
const totalBytes = image.resourceSize;
const wastedBytes = Math.round(totalBytes * wastedRatio);
// If the image has 0 dimensions, it's probably hidden/offscreen, so let the offscreen-images
// audit handle it instead.
if (!usedPixels) {
return null;
}
if (!Number.isFinite(wastedRatio)) {
return new Error(`Invalid image sizing information ${url}`);
}
@ -88,7 +102,7 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
*/
static audit_(artifacts) {
const images = artifacts.ImageElements;
const DPR = artifacts.ViewportDimensions.devicePixelRatio;
const ViewportDimensions = artifacts.ViewportDimensions;
/** @type {string[]} */
const warnings = [];
@ -103,7 +117,7 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
continue;
}
const processed = UsesResponsiveImages.computeWaste(image, DPR);
const processed = UsesResponsiveImages.computeWaste(image, ViewportDimensions);
if (!processed) continue;
if (processed instanceof Error) {

Просмотреть файл

@ -38,7 +38,11 @@ describe('Page uses responsive images', () => {
const description = `identifies when an image is ${condition}`;
it(description, () => {
const result = UsesResponsiveImagesAudit.audit_({
ViewportDimensions: {devicePixelRatio: data.devicePixelRatio || 1},
ViewportDimensions: {
innerWidth: 1000,
innerHeight: 1000,
devicePixelRatio: data.devicePixelRatio || 1,
},
ImageElements: [
generateImage(
generateSize(...data.clientSize),
@ -89,9 +93,26 @@ describe('Page uses responsive images', () => {
sizeInKb: 1,
});
testImage('offscreen and within viewport size', {
listed: false,
devicePixelRatio: 2,
clientSize: [0, 0], // 0 dimensions will be treated as 2 viewport sized
naturalSize: [2000, 3000],
sizeInKb: 1000,
});
testImage('offscreen and larger than viewport size', {
listed: true,
devicePixelRatio: 2,
clientSize: [0, 0], // 0 dimensions will be treated as 2 viewport sized
naturalSize: [5000, 5000],
sizeInKb: 1000,
expectedWaste: 840, // 1000 * 21/25
});
it('handles images without network record', () => {
const auditResult = UsesResponsiveImagesAudit.audit_({
ViewportDimensions: {devicePixelRatio: 2},
ViewportDimensions: {innerWidth: 1000, innerHeight: 1000, devicePixelRatio: 2},
ImageElements: [
generateImage(
generateSize(100, 100),
@ -106,7 +127,7 @@ describe('Page uses responsive images', () => {
it('identifies when images are not wasteful', () => {
const auditResult = UsesResponsiveImagesAudit.audit_({
ViewportDimensions: {devicePixelRatio: 2},
ViewportDimensions: {innerWidth: 1000, innerHeight: 1000, devicePixelRatio: 2},
ImageElements: [
generateImage(
generateSize(200, 200),
@ -138,7 +159,7 @@ describe('Page uses responsive images', () => {
const recordA = generateRecord(100, 300, 'image/svg+xml');
const auditResult = UsesResponsiveImagesAudit.audit_({
ViewportDimensions: {devicePixelRatio: 1},
ViewportDimensions: {innerWidth: 1000, innerHeight: 1000, devicePixelRatio: 1},
ImageElements: [
generateImage(generateSize(10, 10), naturalSizeA, recordA, urlA),
],
@ -153,7 +174,7 @@ describe('Page uses responsive images', () => {
const recordA = generateRecord(100, 300);
const auditResult = UsesResponsiveImagesAudit.audit_({
ViewportDimensions: {devicePixelRatio: 1},
ViewportDimensions: {innerWidth: 1000, innerHeight: 1000, devicePixelRatio: 1},
ImageElements: [
{...generateImage(generateSize(10, 10), naturalSizeA, recordA, urlA), isCss: true},
],
@ -168,7 +189,7 @@ describe('Page uses responsive images', () => {
const recordA = generateRecord(100, 300);
const auditResult = UsesResponsiveImagesAudit.audit_({
ViewportDimensions: {devicePixelRatio: 1},
ViewportDimensions: {innerWidth: 1000, innerHeight: 1000, devicePixelRatio: 1},
ImageElements: [
generateImage(generateSize(10, 10), naturalSizeA, recordA, urlA),
],
@ -187,7 +208,7 @@ describe('Page uses responsive images', () => {
const recordB = generateRecord(10, 20); // make it small to still test passing
const auditResult = UsesResponsiveImagesAudit.audit_({
ViewportDimensions: {devicePixelRatio: 1},
ViewportDimensions: {innerWidth: 1000, innerHeight: 1000, devicePixelRatio: 1},
ImageElements: [
generateImage(generateSize(10, 10), naturalSizeA, recordA, urlA),
generateImage(generateSize(450, 450), naturalSizeA, recordA, urlA),