diff --git a/lib/checks/mobile/target-offset-evaluate.js b/lib/checks/mobile/target-offset-evaluate.js index e799cb68f1..8e45fc8164 100644 --- a/lib/checks/mobile/target-offset-evaluate.js +++ b/lib/checks/mobile/target-offset-evaluate.js @@ -1,11 +1,18 @@ import { findNearbyElms, isFocusable, isInTabOrder } from '../../commons/dom'; import { getRoleType } from '../../commons/aria'; -import { getOffset } from '../../commons/math'; +import { getOffset, rectHasMinimumSize } from '../../commons/math'; const roundingMargin = 0.05; export default function targetOffsetEvaluate(node, options, vNode) { const minOffset = options?.minOffset || 24; + // Bail early to avoid hitting very expensive calculations. + // Targets are so large they are unlikely to fail. + if (rectHasMinimumSize(minOffset * 10, vNode.boundingClientRect)) { + this.data({ messageKey: 'large', minOffset }); + return true; + } + const closeNeighbors = []; let closestOffset = minOffset; for (const vNeighbor of findNearbyElms(vNode, minOffset)) { diff --git a/lib/checks/mobile/target-offset.json b/lib/checks/mobile/target-offset.json index 2aebbdf9f4..c06ee11d7a 100644 --- a/lib/checks/mobile/target-offset.json +++ b/lib/checks/mobile/target-offset.json @@ -7,7 +7,10 @@ "metadata": { "impact": "serious", "messages": { - "pass": "Target has sufficient space from its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px which is at least ${data.minOffset}px.", + "pass": { + "default": "Target has sufficient space from its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px which is at least ${data.minOffset}px.", + "large": "Target far exceeds the minimum size of ${data.minOffset}px." + }, "fail": "Target has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px.", "incomplete": { "default": "Element with negative tabindex has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px. Is this a target?", diff --git a/lib/checks/mobile/target-size-evaluate.js b/lib/checks/mobile/target-size-evaluate.js index 3d692606e3..6ee45bc9c5 100644 --- a/lib/checks/mobile/target-size-evaluate.js +++ b/lib/checks/mobile/target-size-evaluate.js @@ -13,6 +13,13 @@ import { export default function targetSizeEvaluate(node, options, vNode) { const minSize = options?.minSize || 24; const nodeRect = vNode.boundingClientRect; + // Bail early to avoid hitting very expensive calculations. + // Targets are so large they are unlikely to fail. + if (rectHasMinimumSize(minSize * 10, nodeRect)) { + this.data({ messageKey: 'large', minSize }); + return true; + } + const hasMinimumSize = rectHasMinimumSize.bind(null, minSize); const nearbyElms = findNearbyElms(vNode); const overflowingContent = filterOverflowingContent(vNode, nearbyElms); diff --git a/lib/checks/mobile/target-size.json b/lib/checks/mobile/target-size.json index 649d075ae8..302e4f985e 100644 --- a/lib/checks/mobile/target-size.json +++ b/lib/checks/mobile/target-size.json @@ -9,7 +9,8 @@ "messages": { "pass": { "default": "Control has sufficient size (${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px)", - "obscured": "Control is ignored because it is fully obscured and thus not clickable" + "obscured": "Control is ignored because it is fully obscured and thus not clickable", + "large": "Target far exceeds the minimum size of ${data.minSize}px." }, "fail": { "default": "Target has insufficient size (${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px)", diff --git a/locales/_template.json b/locales/_template.json index ef56f9abdf..a911c60144 100644 --- a/locales/_template.json +++ b/locales/_template.json @@ -865,7 +865,10 @@ "fail": "${data} on tag disables zooming on mobile devices" }, "target-offset": { - "pass": "Target has sufficient space from its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px which is at least ${data.minOffset}px.", + "pass": { + "default": "Target has sufficient space from its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px which is at least ${data.minOffset}px.", + "large": "Target far exceeds the minimum size of ${data.minOffset}px." + }, "fail": "Target has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px.", "incomplete": { "default": "Element with negative tabindex has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px. Is this a target?", @@ -875,7 +878,8 @@ "target-size": { "pass": { "default": "Control has sufficient size (${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px)", - "obscured": "Control is ignored because it is fully obscured and thus not clickable" + "obscured": "Control is ignored because it is fully obscured and thus not clickable", + "large": "Target far exceeds the minimum size of ${data.minSize}px." }, "fail": { "default": "Target has insufficient size (${data.width}px by ${data.height}px, should be at least ${data.minSize}px by ${data.minSize}px)", diff --git a/test/checks/mobile/target-offset.js b/test/checks/mobile/target-offset.js index 47ff219834..367968f448 100644 --- a/test/checks/mobile/target-offset.js +++ b/test/checks/mobile/target-offset.js @@ -125,21 +125,15 @@ describe('target-offset tests', () => { for (let i = 0; i < 100; i++) { html += `