-
Notifications
You must be signed in to change notification settings - Fork 795
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(color-contrast): account for text-shadow (#2334)
* fix(color-contrast): account for text-shadow * chore: IE text-shadow issues * chore: more IE fixes * chore: more IE tests * chore: fix failing tests * chore: fix IE stuff * chore: ci fix * Update lib/commons/color/get-text-shadow-colors.js Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com> * chore: support hsl colors * Apply suggestions from code review Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com> * revert code-highlighting.js Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com>
- Loading branch information
1 parent
2b5507e
commit 3eb6d2c
Showing
8 changed files
with
272 additions
and
10 deletions.
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
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,98 @@ | ||
import Color from './color'; | ||
import assert from '../../core/utils/assert'; | ||
|
||
/** | ||
* Get text-shadow colors that can impact the color contrast of the text | ||
* @param {Element} node DOM Element | ||
* @param {Array} [bgElms=[]] Colors used in text-shadow | ||
*/ | ||
function getTextShadowColors(node) { | ||
const style = window.getComputedStyle(node); | ||
const textShadow = style.getPropertyValue('text-shadow'); | ||
if (textShadow === 'none') { | ||
return []; | ||
} | ||
|
||
const shadows = parseTextShadows(textShadow); | ||
return shadows.map(({ colorStr, pixels }) => { | ||
// Defautls only necessary for IE | ||
colorStr = colorStr || style.getPropertyValue('color'); | ||
const [offsetY, offsetX, blurRadius = 0] = pixels; | ||
|
||
return textShadowColor({ colorStr, offsetY, offsetX, blurRadius }); | ||
}); | ||
} | ||
|
||
/** | ||
* Parse text-shadow property value. Required for IE, which can return the color | ||
* either at the start or the end, and either in rgb(a) or as a named color | ||
*/ | ||
function parseTextShadows(textShadow) { | ||
let current = { pixels: [] }; | ||
let str = textShadow.trim(); | ||
const shadows = [current]; | ||
if (!str) { | ||
return []; | ||
} | ||
|
||
while (str) { | ||
let colorMatch = | ||
str.match(/^rgba?\([0-9,.\s]+\)/i) || | ||
str.match(/^[a-z]+/i) || | ||
str.match(/^#[0-9a-f]+/i); | ||
let pixelMatch = str.match(/^([0-9.-]+)px/i) || str.match(/^(0)/); | ||
|
||
if (colorMatch) { | ||
assert( | ||
!current.colorStr, | ||
`Multiple colors identified in text-shadow: ${textShadow}` | ||
); | ||
str = str.replace(colorMatch[0], '').trim(); | ||
current.colorStr = colorMatch[0]; | ||
} else if (pixelMatch) { | ||
assert( | ||
current.pixels.length < 3, | ||
`Too many pixel units in text-shadow: ${textShadow}` | ||
); | ||
str = str.replace(pixelMatch[0], '').trim(); | ||
const pixelUnit = parseFloat( | ||
(pixelMatch[1][0] === '.' ? '0' : '') + pixelMatch[1] | ||
); | ||
current.pixels.push(pixelUnit); | ||
} else if (str[0] === ',') { | ||
// multiple text-shadows in a single string (e.g. `text-shadow: 1px 1px 1px #000, 3px 3px 5px blue;` | ||
assert( | ||
current.pixels.length >= 2, | ||
`Missing pixel value in text-shadow: ${textShadow}` | ||
); | ||
current = { pixels: [] }; | ||
shadows.push(current); | ||
str = str.substr(1).trim(); | ||
} else { | ||
throw new Error(`Unable to process text-shadows: ${textShadow}`); | ||
} | ||
} | ||
|
||
return shadows; | ||
} | ||
|
||
function textShadowColor({ colorStr, offsetX, offsetY, blurRadius }) { | ||
if (offsetX > blurRadius || offsetY > blurRadius) { | ||
// Shadow is too far removed from the text to impact contrast | ||
return new Color(0, 0, 0, 0); | ||
} | ||
|
||
const shadowColor = new Color(); | ||
shadowColor.parseString(colorStr); | ||
shadowColor.alpha *= blurRadiusToAlpha(blurRadius); | ||
|
||
return shadowColor; | ||
} | ||
|
||
function blurRadiusToAlpha(blurRadius) { | ||
// This formula is an estimate based on various tests. | ||
// Different people test this differently, so opinions may vary. | ||
return 3.7 / (blurRadius + 8); | ||
} | ||
|
||
export default getTextShadowColors; |
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 |
---|---|---|
@@ -0,0 +1,128 @@ | ||
describe('axe.commons.color.getTextShadowColors', function() { | ||
'use strict'; | ||
|
||
var fixture = document.getElementById('fixture'); | ||
var getTextShadowColors = axe.commons.color.getTextShadowColors; | ||
|
||
afterEach(function() { | ||
fixture.innerHTML = ''; | ||
}); | ||
|
||
it('returns an empty array when there is no text-shadow', function() { | ||
fixture.innerHTML = '<span>Hello world</span>'; | ||
var span = fixture.querySelector('span'); | ||
var shadowColors = getTextShadowColors(span); | ||
assert.lengthOf(shadowColors, 0); | ||
}); | ||
|
||
it('returns a rgb values of each text-shadow color', function() { | ||
fixture.innerHTML = | ||
'<span style="text-shadow: ' + | ||
'1px 1px 2px #F00, rgb(0, 0, 255) 0 0 1em, \n0\t 0 0.2em green;' + | ||
'">Hello world</span>'; | ||
|
||
var span = fixture.querySelector('span'); | ||
var shadowColors = getTextShadowColors(span); | ||
|
||
assert.lengthOf(shadowColors, 3); | ||
assert.equal(shadowColors[0].red, 255); | ||
assert.equal(shadowColors[0].green, 0); | ||
assert.equal(shadowColors[0].blue, 0); | ||
|
||
assert.equal(shadowColors[1].red, 0); | ||
assert.equal(shadowColors[1].blue, 255); | ||
assert.equal(shadowColors[1].green, 0); | ||
|
||
assert.equal(shadowColors[2].red, 0); | ||
assert.equal(shadowColors[2].green, 128); | ||
assert.equal(shadowColors[2].blue, 0); | ||
}); | ||
|
||
it('returns transparent if the blur radius is greater than the offset', function() { | ||
fixture.innerHTML = | ||
'<span style="text-shadow: ' + | ||
'1px 3px 2px red, blue 10px 0 9px, 20px 20px 18px green;' + | ||
'">Hello world</span>'; | ||
var span = fixture.querySelector('span'); | ||
var shadowColors = getTextShadowColors(span); | ||
|
||
assert.lengthOf(shadowColors, 3); | ||
assert.equal(shadowColors[0].alpha, 0); | ||
assert.equal(shadowColors[1].alpha, 0); | ||
assert.equal(shadowColors[2].alpha, 0); | ||
}); | ||
|
||
it('returns an estimated alpha value based on blur radius', function() { | ||
fixture.innerHTML = | ||
'<span style="text-shadow: ' + | ||
'1px 1px 2px red, blue 0 0 10px, \n0\t 0 18px green;' + | ||
'">Hello world</span>'; | ||
|
||
var span = fixture.querySelector('span'); | ||
var shadowColors = getTextShadowColors(span); | ||
var expected0 = 3.7 / (2 + 8); | ||
var expected1 = 3.7 / (10 + 8); | ||
var expected2 = 3.7 / (18 + 8); | ||
|
||
assert.lengthOf(shadowColors, 3); | ||
assert.closeTo(shadowColors[0].alpha, expected0, 0.05); | ||
assert.closeTo(shadowColors[1].alpha, expected1, 0.05); | ||
assert.closeTo(shadowColors[2].alpha, expected2, 0.05); | ||
}); | ||
|
||
it('handles floating point values', function() { | ||
fixture.innerHTML = | ||
'<span style="text-shadow: ' + | ||
'0 0.1px .2px red' + | ||
'">Hello world</span>'; | ||
|
||
var span = fixture.querySelector('span'); | ||
var shadowColors = getTextShadowColors(span); | ||
var expectedAlpha = 3.7 / (0.12 + 8); | ||
|
||
assert.lengthOf(shadowColors, 1); | ||
assert.closeTo(shadowColors[0].alpha, expectedAlpha, 0.01); | ||
}); | ||
|
||
it('combines the blur radius alpha with the alpha of the text-shadow color', function() { | ||
fixture.innerHTML = | ||
'<span style="text-shadow: ' + | ||
'rgba(255, 0, 0, 0) 0 0 2px, rgba(255,0,0,0.5) 0 0 2px, rgba(255,0,0,0.8) 0 0 2px' + | ||
'">Hello world</span>'; | ||
|
||
var span = fixture.querySelector('span'); | ||
var shadowColors = getTextShadowColors(span); | ||
var expected1 = (3.7 / (2 + 8)) * 0.5; | ||
var expected2 = (3.7 / (2 + 8)) * 0.8; | ||
|
||
assert.lengthOf(shadowColors, 3); | ||
assert.closeTo(shadowColors[0].alpha, 0, 0.05); | ||
assert.closeTo(shadowColors[1].alpha, expected1, 0.05); | ||
assert.closeTo(shadowColors[2].alpha, expected2, 0.05); | ||
}); | ||
|
||
it('treats the blur radius as 0 when left undefined', function() { | ||
fixture.innerHTML = | ||
'<span style="text-shadow: ' + '1px 2px red' + '">Hello world</span>'; | ||
|
||
var span = fixture.querySelector('span'); | ||
var shadowColors = getTextShadowColors(span); | ||
|
||
assert.lengthOf(shadowColors, 1); | ||
assert.equal(shadowColors[0].alpha, 0); | ||
}); | ||
|
||
it('uses text color if text-shadow color is ommitted', function() { | ||
fixture.innerHTML = | ||
'<span style="color: red;' + | ||
'text-shadow: 1px 1px 1px;' + | ||
'">Hello world</span>'; | ||
var span = fixture.querySelector('span'); | ||
var shadowColors = getTextShadowColors(span); | ||
|
||
assert.lengthOf(shadowColors, 1); | ||
assert.equal(shadowColors[0].red, 255); | ||
assert.equal(shadowColors[0].green, 0); | ||
assert.equal(shadowColors[0].blue, 0); | ||
}); | ||
}); |
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