-
Notifications
You must be signed in to change notification settings - Fork 9.4k
/
image-aspect-ratio.js
131 lines (114 loc) · 5.64 KB
/
image-aspect-ratio.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Checks to see if the aspect ratio of the images used on
* the page are equal to the aspect ratio of their display sizes. The
* audit will list all images that don't match with their display size
* aspect ratio.
*/
import {Audit} from './audit.js';
import UrlUtils from '../lib/url-utils.js';
import * as i18n from '../lib/i18n/i18n.js';
const UIStrings = {
/** Title of a Lighthouse audit that provides detail on the aspect ratios of all images on the page. This descriptive title is shown to users when all images use correct aspect ratios. */
title: 'Displays images with correct aspect ratio',
/** Title of a Lighthouse audit that provides detail on the aspect ratios of all images on the page. This descriptive title is shown to users when not all images use correct aspect ratios. */
failureTitle: 'Displays images with incorrect aspect ratio',
/** Description of a Lighthouse audit that tells the user why they should maintain the correct aspect ratios for all images. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
description: 'Image display dimensions should match natural aspect ratio. ' +
'[Learn more about image aspect ratio](https://developer.chrome.com/docs/lighthouse/best-practices/image-aspect-ratio/).',
/** Label for a column in a data table; entries in the column will be the numeric aspect ratio of an image as displayed in a web page. */
columnDisplayed: 'Aspect Ratio (Displayed)',
/** Label for a column in a data table; entries in the column will be the numeric aspect ratio of the raw (actual) image. */
columnActual: 'Aspect Ratio (Actual)',
};
const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
const THRESHOLD_PX = 2;
/** @typedef {Required<LH.Artifacts.ImageElement>} WellDefinedImage */
class ImageAspectRatio extends Audit {
/**
* @return {LH.Audit.Meta}
*/
static get meta() {
return {
id: 'image-aspect-ratio',
title: str_(UIStrings.title),
failureTitle: str_(UIStrings.failureTitle),
description: str_(UIStrings.description),
requiredArtifacts: ['ImageElements'],
};
}
/**
* @param {WellDefinedImage} image
* @return {{url: string, node: LH.Audit.Details.NodeValue, displayedAspectRatio: string, actualAspectRatio: string, doRatiosMatch: boolean}}
*/
static computeAspectRatios(image) {
const url = UrlUtils.elideDataURI(image.src);
const actualAspectRatio = image.naturalDimensions.width / image.naturalDimensions.height;
const displayedAspectRatio = image.displayedWidth / image.displayedHeight;
const targetDisplayHeight = image.displayedWidth / actualAspectRatio;
const targetDisplayWidth = image.displayedHeight * actualAspectRatio;
// Small rounding errors in aspect ratio can lead to large differences in target width/height
// if the aspect ratio is close to 0.
//
// In these cases, we should compare the smaller dimension because any rounding errors will
// affect that dimension less.
const doRatiosMatch = targetDisplayHeight < targetDisplayWidth
? Math.abs(targetDisplayHeight - image.displayedHeight) < THRESHOLD_PX
: Math.abs(targetDisplayWidth - image.displayedWidth) < THRESHOLD_PX;
return {
url,
node: Audit.makeNodeItem(image.node),
displayedAspectRatio: `${image.displayedWidth} x ${image.displayedHeight}
(${displayedAspectRatio.toFixed(2)})`,
actualAspectRatio: `${image.naturalDimensions.width} x ${image.naturalDimensions.height}
(${actualAspectRatio.toFixed(2)})`,
doRatiosMatch,
};
}
/**
* @param {LH.Artifacts} artifacts
* @return {LH.Audit.Product}
*/
static audit(artifacts) {
const images = artifacts.ImageElements;
/** @type {Array<{url: string, node: LH.Audit.Details.NodeValue, displayedAspectRatio: string, actualAspectRatio: string, doRatiosMatch: boolean}>} */
const results = [];
images.filter(image => {
// - filter out css background images since we don't have a reliable way to tell if it's a
// sprite sheet, repeated for effect, etc
// - filter out images that don't have following properties:
// networkRecord, width, height, `object-fit` property
// - filter all svgs as they have no natural dimensions to audit
// - filter out images that have falsy naturalWidth or naturalHeight
return !image.isCss &&
UrlUtils.guessMimeType(image.src) !== 'image/svg+xml' &&
image.naturalDimensions &&
image.naturalDimensions.height > 5 &&
image.naturalDimensions.width > 5 &&
image.displayedWidth &&
image.displayedHeight &&
image.computedStyles.objectFit === 'fill';
}).forEach(image => {
const wellDefinedImage = /** @type {WellDefinedImage} */ (image);
const processed = ImageAspectRatio.computeAspectRatios(wellDefinedImage);
if (!processed.doRatiosMatch) results.push(processed);
});
/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
{key: 'node', valueType: 'node', label: ''},
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
{key: 'displayedAspectRatio', valueType: 'text', label: str_(UIStrings.columnDisplayed)},
{key: 'actualAspectRatio', valueType: 'text', label: str_(UIStrings.columnActual)},
];
return {
score: Number(results.length === 0),
details: Audit.makeTableDetails(headings, results),
};
}
}
export default ImageAspectRatio;
export {UIStrings};