平滑的 CSS 渐变过渡

如果您尝试在纯 CSS 中动画或过渡渐变,我们最终会遇到一些问题。所有现代浏览器都不会在渐变中自然地平滑过渡颜色。因此,我们最终会发生突然的变化。这可以在下面的示例中看到,其中变换过渡非常好,但渐变却没有,即使我们使用transition: all 0.1s ease-out.
本机 CSS 行为..

#gradient-button {
    background: linear-gradient(180deg, #ff7147, #e0417f);
    padding: 0.5rem 1.5rem;
    transition: all 0.1s ease-out;
    font-weight: 500;
    font-size: 1.25rem;
    border-radius: 100px;
    transform: scale(1);
    margin: 0 0 2rem 1rem;
}

#gradient-button:hover {
    background: linear-gradient(45deg, #0037ff, #00adff);
    transform: scale(1.1);
}

使用 CSS 和 Javascript 动画和过渡渐变#

在本文中,我们将研究如何解决这个问题,并介绍如何使用 Javascript 平滑地为渐变过渡设置动画。这意味着创建一个能够在两种颜色之间平滑过渡的函数。

所有这些的完整代码都可以在 codepen 上找到

虽然没有原生的方式来实现这个效果,但是我们可以用 Javascript 来实现。下面的按钮解决了这个问题,当您将鼠标悬停在按钮上时,我们可以使用 Javascript 和一些 CSS 平滑地为渐变过渡设置动画。将鼠标悬停在我身上进行过渡..

在 CSS 中的两个渐变之间平滑过渡#

第一步,我们需要创建一个函数,它可以让我们找出两种颜色之间的颜色。为此,我们需要一开始的颜色,以及我们想要过渡到的颜色。

我们将创建的函数如下所示。它可以支持超过 2 种颜色的渐变 – 但为此我们只使用两种。我们还将采用初始渐变颜色,并将其应用于我们的按钮 – 这样我们就可以完全从我们的 Javascript 操作渐变。


let element = 'gradient-button-transition'; // <-- id of the button we're transitioning

// DEFINE YOUR GRADIENT COLORS HERE
// Pct refers to the percentage position of the gradient stop point.
const gradientStopOne = [
    { pct: 0,  color: { r: 255, g: 113, b: 71 } }, // The first color in your gradient
    { pct: 100, color: { r: 0, g: 55, b: 255 } }   // The color you want your first color to transition to
];
const gradientStopTwo = [
    { pct: 0,  color: { r: 224, g: 65, b: 127 } }, // The second color in your gradient
    { pct: 100, color: { r: 0, g: 173, b: 255 } }  // The color you want your second color to transition to
]

// Apply our gradient programmatically so we can completely manipulate the gradient from JS rather than CSS
let c1 = gradientStopOne[0].color;
let c2 = gradientStopTwo[0].color;
document.getElementById('gradient-button-transition').style.background = `linear-gradient(${angle}deg, rgb(${c1.r}, ${c1.g}, ${c1.b}), rgb(${c2.r}, ${c2.g}, ${c2.b}))`;
   
// This function transitions between two rgb colors
const getColor = function(pct, colorSet) {
    for (var i = 1; i < colorSet.length - 1; i++) {
        if (pct < colorSet[i].pct) {
            break;
        }
    }
    // This conversion figures out the transition between two rgb values
    var lower = colorSet[i - 1];
    var upper = colorSet[i];
    var range = upper.pct - lower.pct;
    var rangePct = (pct - lower.pct) / range;
    var pctLower = 1 - rangePct;
    var pctUpper = rangePct;
    var color = {
        r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
        g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
        b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)
    };
    // And returns the rgb code
    return `rgb(${color.r}, ${color.g}, ${color.b})`;
}

现在我们有了一个可以让我们在两种颜色之间转换的函数,并且已经定义了我们的渐变,我们可以开始在它们之间转换。我们将创建一个设置间隔的函数
根据用户是否悬停,我们将操纵动画的方向

下面代码中的注释解释了我们在这里尝试做的事情。间隔每 16.67 毫秒运行一次,即每秒运行 60 次。这将为我们提供每秒 60 帧的平滑动画。在间隔函数中,我们计算总帧数,并在过渡时间到时停止动画。


let transitionTime = 1000           // <-- 100 ms - time our animation will last
let previousTime, start = 0;        // <-- stores data on animation
let angle = 180;                    // <-- angle of gradient
let animationDirection = 'forwards' // <-- stores the animation direction
let intervalFrame;                  // <-- stores the interval frame
let currentPct = 0;                 // <-- current percentage through the animation
let elapsed = 0;                    // <-- number of frames which have ellapsed 
    
// This is our animation which we run on hover
const animateGradient = function() {
    if(intervalFrame === undefined) {
        intervalFrame = setInterval(() => {
            let time = transitionTime / 1000; // time in seconds
            let numberOfFrames = time * 60; // 60 frames per second -> 1 second = 60 frames
            
            // If the animation is going forward
            if(animationDirection === 'forwards') {
                // Add 1 to elapsed
                elapsed += 1;
                // The elapsed frames out of max frames
                currentPct = Math.min(elapsed / numberOfFrames, 1) * 100;
            }
            else {
                // Otherwise we're going back - subtract 1 from ellapsed
                elapsed -= 1;
                // The elapsed frames out of max frames
                currentPct = Math.max(elapsed / numberOfFrames, 0) * 100;
            }
            
            // Calculate current color in this time for each gradient color
            let colorOne = getColor(currentPct, gradientStopOne);
            let colorTwo = getColor(currentPct, gradientStopTwo);

            // Generate CSS string
            let generateGradient = `linear-gradient(${angle}deg, ${colorOne}, ${colorTwo})`;

            // Add it to our background.
            document.getElementById(element).style.backgroundImage = generateGradient;

            // End the interval when we're done
            if(currentPct === 100 || currentPct === 0) {
                clearInterval(intervalFrame);
                intervalFrame = undefined;
            }
        }, 16.667); // 60 frames per second
    }
};

最后,我们在悬停和悬停时运行所有这些。当用户悬停时,我们更新动画方向,因此我们可以将渐变移向我们想要的颜色。

// On hover, run our animation
document.getElementById('gradient-button-transition').addEventListener('mouseover', function() {
    animationDirection = 'forwards';
    animateGradient();
});
// On hover out, run our animation again, but backwards
document.getElementById('gradient-button-transition').addEventListener('mouseleave', function() {
    animationDirection = 'backwards';
    animateGradient();
});

多种颜色渐变过渡#

由于我们可以运行多种颜色,并且可以随时运行 – 我们可以创建一些有趣的效果。这是一个在 4 种不同渐变之间自动转换的按钮:自动渐变过渡

结论#

虽然现在用 CSS 是不可能的,但 Javascript 实际上为我们提供了更多的灵活性来平滑地动画渐变过渡。

你可以在这里找到关于 codepen 的完整代码