JavaScript如何将线性渐变函数改写为贝塞尔平滑渐变函数
JS实现贝塞尔缓动渐变方案
现有实现说明
你当前编写的线性渐变生成器运行逻辑通顺,已实现黑色到绿色的均匀线性过渡效果,核心插值逻辑为:
- 计算输入值
a(起点值)、b(终点值)的差值 - 根据传入的0-1区间比例参数
decimal计算线性插值:decimal=0.5时返回两值中点,decimal=0.3时返回更靠近起点a的对应值
现有完整代码如下:
// Gradient Function function linearGradient(a, b, decimal) { // find distance between a and b const dist = b - a /* if you multiply the distance by the decimal and add the minimum again, it gives you the point at which the decimal interacts with the number */ return a + dist * decimal } /* ------- EXAMPLE USE ------- */ // Green & Black colour definition const color_black = [0, 0, 0] const color_green = [0, 255, 0] // Make a for-loop to find the Red, Green and Blue values of each stage from black to green const resolution = 20 for (let i = 0; i < resolution; i ++) { red = linearGradient(color_black[0], color_green[0], i / resolution) green = linearGradient(color_black[1], color_green[1], i / resolution) blue = linearGradient(color_black[2], color_green[2], i / resolution) }
当前线性渐变的插值沿直线匀速从起点过渡到终点,视觉效果偏生硬。
改造思路
你需要的「起步逐步加速、到达中点后逐步减速」的自然过渡效果,本质是缓入缓出(ease-in-out)贝塞尔曲线,不需要改动原有线性插值的核心逻辑,只需要新增一步比例映射:
- 保留原有
linearGradient线性插值函数完全不变 - 新增贝塞尔缓动函数,把原始的均匀比例值(即循环里的
i/resolution)映射为符合贝塞尔变化规律的新比例值 - 把映射后的新比例值传入
linearGradient计算颜色通道值即可
具体实现
轻量版:二次方缓入缓出
如果只需要标准的平滑S型过渡,不需要自定义曲线弧度,直接用计算量极小的二次方缓动公式即可,效果完全匹配需求:
// 新增:二次方缓入缓出贝塞尔映射 function easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2 } // 原有线性插值函数保持不变 function linearGradient(a, b, decimal) { const dist = b - a return a + dist * decimal } /* ------- 调用示例 ------- */ const color_black = [0, 0, 0] const color_green = [0, 255, 0] const resolution = 20 for (let i = 0; i < resolution; i ++) { // 先对原始比例做贝塞尔缓动映射 const ratio = easeInOutQuad(i / resolution) // 再用映射后的比例计算颜色值 const red = linearGradient(color_black[0], color_green[0], ratio) const green = linearGradient(color_black[1], color_green[1], ratio) const blue = linearGradient(color_black[2], color_green[2], ratio) // 后续使用red/green/blue绘制即可 }
可调版:自定义三次贝塞尔
如果需要灵活调整过渡的加速、减速节奏(比如让起步更快、收尾拖尾更长),可以用通用三次贝塞尔实现,和CSS的cubic-bezier参数规则完全一致:
// 新增:三次贝塞尔缓动映射,参数对应CSS cubic-bezier的4个控制点值 function cubicBezier(p1x, p1y, p2x, p2y) { const NEWTON_ITERATIONS = 4 const NEWTON_MIN_SLOPE = 0.001 const SUBDIVISION_PRECISION = 0.0000001 const SUBDIVISION_MAX_ITERATIONS = 10 function A(aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1 } function B(aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1 } function C(aA1) { return 3.0 * aA1 } function calcBezier(aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT } function getSlope(aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1) } function binarySubdivide(aX, aA, aB, mX1, mX2) { let currentX, currentT, i = 0 do { currentT = aA + (aB - aA) / 2.0 currentX = calcBezier(currentT, mX1, mX2) - aX if (currentX > 0) aB = currentT else aA = currentT } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS) return currentT } function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) { for (let i = 0; i < NEWTON_ITERATIONS; ++i) { const currentSlope = getSlope(aGuessT, mX1, mX2) if (currentSlope === 0.0) return aGuessT const currentX = calcBezier(aGuessT, mX1, mX2) - aX aGuessT -= currentX / currentSlope } return aGuessT } const _p1x = p1x, _p1y = p1y, _p2x = p2x, _p2y = p2y return function(t) { if (t === 0 || t === 1) return t const currentSlope = getSlope(t, _p1x, _p2x) if (currentSlope !== 0 && currentSlope >= NEWTON_MIN_SLOPE) { let t2 = newtonRaphsonIterate(t, t, _p1x, _p2x) return calcBezier(t2, _p1y, _p2y) } else if (currentSlope === 0) { return calcBezier(t, _p1y, _p2y) } else { let t2 = binarySubdivide(t, 0, 1, _p1x, _p2x) return calcBezier(t2, _p1y, _p2y) } } } // 初始化标准ease缓动曲线,对应控制点(0.25, 0.1, 0.25, 1),和你要的加速减速规律完全匹配 const ease = cubicBezier(0.25, 0.1, 0.25, 1) // 原有线性插值函数保持不变 function linearGradient(a, b, decimal) { const dist = b - a return a + dist * decimal } /* ------- 调用示例 ------- */ const color_black = [0, 0, 0] const color_green = [0, 255, 0] const resolution = 20 for (let i = 0; i < resolution; i ++) { // 用自定义贝塞尔曲线映射比例 const ratio = ease(i / resolution) const red = linearGradient(color_black[0], color_green[0], ratio) const green = linearGradient(color_black[1], color_green[1], ratio) const blue = linearGradient(color_black[2], color_green[2], ratio) }
你可以通过修改cubicBezier的四个控制点参数调整过渡手感:
- 前两个参数控制起步阶段的曲线弧度,数值越小起步加速越平缓
- 后两个参数控制收尾阶段的曲线弧度,数值越大收尾减速越柔和
内容的提问来源于stack exchange,提问作者Joachim
相关产品推荐
相关产品推荐

