-
Notifications
You must be signed in to change notification settings - Fork 9.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add OffscreenImages Audit #1807
Merged
Merged
Changes from 3 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
938feb3
refator: rename ContentWidth to ViewportDimensions
patrickhulce fe5470d
feat: add UnusedImages audit
patrickhulce b80470c
style: rename to OffscreenImages
patrickhulce 7c2b75f
update viewport dimensions naming
patrickhulce 4c76bb1
Merge branch 'master' into unused_image_audit
patrickhulce 5145ce6
update variable names
patrickhulce f350914
update tests, comments
patrickhulce a8016ab
feat(trace-categories): add support for custom trace categories
patrickhulce c4e35a5
fix merge conflicts
patrickhulce 9bf51c8
description change
patrickhulce 54bfb23
Merge branch 'master' into unused_image_audit
patrickhulce 26a3cff
ignore lazily loaded images
patrickhulce c53b43b
Merge branch 'master' into unused_image_audit
patrickhulce 5a43312
Merge branch 'master' into unused_image_audit
patrickhulce d8eb491
more nits
patrickhulce File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
136 changes: 136 additions & 0 deletions
136
lighthouse-core/audits/byte-efficiency/offscreen-images.js
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/** | ||
* @license | ||
* Copyright 2017 Google Inc. All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
/** | ||
* @fileoverview Checks to see if images are displayed only outside of the viewport. | ||
*/ | ||
'use strict'; | ||
|
||
const Audit = require('./byte-efficiency-audit'); | ||
const URL = require('../../lib/url-shim'); | ||
|
||
const ALLOWABLE_OFFSCREEN_X = 100; | ||
const ALLOWABLE_OFFSCREEN_Y = 200; | ||
|
||
const IGNORE_THRESHOLD_IN_BYTES = 2048; | ||
|
||
class OffscreenImages extends Audit { | ||
/** | ||
* @return {!AuditMeta} | ||
*/ | ||
static get meta() { | ||
return { | ||
category: 'Images', | ||
name: 'offscreen-images', | ||
description: 'Offscreen images', | ||
helpText: 'Images that are not above the fold should be lazy loaded on interaction. ' + | ||
'Consider using the [IntersectionObserver](https://developers.google.com/web/updates/2016/04/intersectionobserver) API.', | ||
requiredArtifacts: ['ImageUsage', 'ViewportDimensions', 'networkRecords'] | ||
}; | ||
} | ||
|
||
/** | ||
* @param {!ClientRect} imageRect | ||
* @param {{scrollWidth: number, scrollHeight: number}} viewportDimensions | ||
* @return {number} | ||
*/ | ||
static computeVisiblePixels(imageRect, viewportDimensions) { | ||
const scrollWidth = viewportDimensions.scrollWidth; | ||
const scrollHeight = viewportDimensions.scrollHeight; | ||
|
||
const top = Math.max(imageRect.top, -1 * ALLOWABLE_OFFSCREEN_Y); | ||
const right = Math.min(imageRect.right, scrollWidth + ALLOWABLE_OFFSCREEN_X); | ||
const bottom = Math.min(imageRect.bottom, scrollHeight + ALLOWABLE_OFFSCREEN_Y); | ||
const left = Math.max(imageRect.left, -1 * ALLOWABLE_OFFSCREEN_X); | ||
|
||
return Math.max(right - left, 0) * Math.max(bottom - top, 0); | ||
} | ||
|
||
/** | ||
* @param {!Object} image | ||
* @param {{scrollWidth: number, scrollHeight: number}} viewportDimensions | ||
* @return {?Object} | ||
*/ | ||
static computeWaste(image, viewportDimensions) { | ||
const url = URL.getDisplayName(image.src, {preserveQuery: true}); | ||
const totalPixels = image.clientWidth * image.clientHeight; | ||
const visiblePixels = this.computeVisiblePixels(image.clientRect, viewportDimensions); | ||
const wastedRatio = 1 - visiblePixels / totalPixels; | ||
const totalBytes = image.networkRecord.resourceSize; | ||
const wastedBytes = Math.round(totalBytes * wastedRatio); | ||
|
||
if (!Number.isFinite(wastedRatio)) { | ||
return new Error(`Invalid image sizing information ${url}`); | ||
} | ||
|
||
return { | ||
url, | ||
preview: { | ||
url: image.networkRecord.url, | ||
mimeType: image.networkRecord.mimeType | ||
}, | ||
totalBytes, | ||
wastedBytes, | ||
wastedPercent: 100 * wastedRatio, | ||
}; | ||
} | ||
|
||
/** | ||
* @param {!Artifacts} artifacts | ||
* @return {{results: !Array<Object>, tableHeadings: Object, | ||
* passes: boolean=, debugString: string=}} | ||
*/ | ||
static audit_(artifacts) { | ||
const images = artifacts.ImageUsage; | ||
const viewportDimensions = artifacts.ViewportDimensions; | ||
|
||
let debugString; | ||
const resultsMap = images.reduce((results, image) => { | ||
if (!image.networkRecord) { | ||
return results; | ||
} | ||
|
||
const processed = OffscreenImages.computeWaste(image, viewportDimensions); | ||
if (processed instanceof Error) { | ||
debugString = processed.message; | ||
return results; | ||
} | ||
|
||
// Don't warn about an image that was also used appropriately | ||
const existing = results.get(processed.preview.url); | ||
if (!existing || existing.wastedBytes > processed.wastedBytes) { | ||
results.set(processed.preview.url, processed); | ||
} | ||
|
||
return results; | ||
}, new Map()); | ||
|
||
const results = Array.from(resultsMap.values()) | ||
.filter(item => item.wastedBytes > IGNORE_THRESHOLD_IN_BYTES); | ||
return { | ||
debugString, | ||
results, | ||
tableHeadings: { | ||
preview: '', | ||
url: 'URL', | ||
totalKb: 'Original', | ||
potentialSavings: 'Potential Savings', | ||
} | ||
}; | ||
} | ||
} | ||
|
||
module.exports = OffscreenImages; |
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
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
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,18 +21,21 @@ const Gatherer = require('./gatherer'); | |
/* global window */ | ||
|
||
/* istanbul ignore next */ | ||
function getContentWidth() { | ||
function getViewportDimensions() { | ||
// window.innerWidth to get the scrollable size of the window (irrespective of zoom) | ||
// window.outerWidth to get the size of the visible area | ||
// window.devicePixelRatio to get ratio of logical pixels to physical pixels | ||
return Promise.resolve({ | ||
scrollPosition: {x: window.scrollX, y: window.scrollY}, | ||
scrollWidth: window.innerWidth, | ||
scrollHeight: window.innerHeight, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see these now. Yea, I would switch these to use the same name as the property you're stashing. e.g. I typically use |
||
viewportWidth: window.outerWidth, | ||
viewportHeight: window.innerHeight, | ||
devicePixelRatio: window.devicePixelRatio, | ||
}); | ||
} | ||
|
||
class ContentWidth extends Gatherer { | ||
class ViewportDimensions extends Gatherer { | ||
|
||
/** | ||
* @param {!Object} options | ||
|
@@ -41,18 +44,21 @@ class ContentWidth extends Gatherer { | |
afterPass(options) { | ||
const driver = options.driver; | ||
|
||
return driver.evaluateAsync(`(${getContentWidth.toString()}())`) | ||
return driver.evaluateAsync(`(${getViewportDimensions.toString()}())`) | ||
|
||
.then(returnedValue => { | ||
if (!Number.isFinite(returnedValue.scrollWidth) || | ||
!Number.isFinite(returnedValue.scrollHeight) || | ||
!Number.isFinite(returnedValue.viewportWidth) || | ||
!Number.isFinite(returnedValue.viewportHeight) || | ||
!Number.isFinite(returnedValue.devicePixelRatio)) { | ||
throw new Error(`ContentWidth results were not numeric: ${JSON.stringify(returnedValue)}`); | ||
const results = JSON.stringify(returnedValue); | ||
throw new Error(`ViewportDimensions results were not numeric: ${results}`); | ||
} | ||
|
||
return returnedValue; | ||
}); | ||
} | ||
} | ||
|
||
module.exports = ContentWidth; | ||
module.exports = ViewportDimensions; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about something like, "if an image was used more than once, warn about the least wasteful usage of it"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done