diff --git a/src/color/mix.js b/src/color/mix.js index 42de503c..28ddad4e 100644 --- a/src/color/mix.js +++ b/src/color/mix.js @@ -49,9 +49,11 @@ function mix(weight: number | string, color: string, otherColor: string): string // http://sass-lang.com/documentation/Sass/Script/Functions.html#mix-instance_method const alphaDelta = color1.alpha - color2.alpha const x = parseFloat(weight) * 2 - 1 - const y = x * alphaDelta === -1 ? x : x + alphaDelta const z = 1 + x * alphaDelta - const weight1 = (y / z + 1) / 2.0 + // When `x * alphaDelta === -1`, `z` is `0`, so fall back to `x` rather than + // dividing by it (matches the reference Sass implementation and avoids NaN). + const y = x * alphaDelta === -1 ? x : (x + alphaDelta) / z + const weight1 = (y + 1) / 2.0 const weight2 = 1 - weight1 const mixedColor = { diff --git a/src/color/test/mix.test.js b/src/color/test/mix.test.js index 74a8bb4b..9b2baa96 100644 --- a/src/color/test/mix.test.js +++ b/src/color/test/mix.test.js @@ -41,4 +41,11 @@ describe('mix', () => { it('should return the second color when weight is 0', () => { expect(mix(0, 'rgba(0, 0, 0, 1)', 'rgba(255, 255, 255, 0)')).toEqual('rgba(255, 255, 255, 0)') }) + + it('should not produce NaN channels when the alpha delta cancels the weight', () => { + // weight 1 makes `x === 1`, and the opposing alphas make `alphaDelta === -1`, + // so `1 + x * alphaDelta === 0`. The previous implementation divided by that + // zero and returned `rgba(NaN,NaN,NaN,0)`. weight 1 should yield color1. + expect(mix(1, 'rgba(255, 0, 0, 0)', 'rgba(0, 0, 255, 1)')).toEqual('rgba(255,0,0,0)') + }) })