From 2efcedf8a499ff2bd958d0de879be2251387798e Mon Sep 17 00:00:00 2001 From: Alexander Kireev Date: Sat, 27 Jun 2026 03:42:55 +0700 Subject: [PATCH] fix(mix): avoid NaN channels when alpha delta cancels the weight When `x * alphaDelta === -1`, `z` (`1 + x * alphaDelta`) is `0`, but the result was still computed as `(x / z + 1) / 2`, dividing by zero and producing `rgba(NaN,NaN,NaN,0)`. Restructure to match the reference Sass implementation: compute `z` first and skip the division in that case (`combinedWeight1 = x`). The normal path is algebraically unchanged. e.g. `mix(1, 'rgba(255, 0, 0, 0)', 'rgba(0, 0, 255, 1)')` now returns `rgba(255,0,0,0)` instead of `rgba(NaN,NaN,NaN,0)`. --- src/color/mix.js | 6 ++++-- src/color/test/mix.test.js | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) 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)') + }) })