Search code examples
htmlcsssvg

Transition on Fill property with linearGradient inside of SVG


I'm trying to achieve some kind of Stepper. It all works well except for the transition on the fill.

let step = 0;
document.querySelector("button").addEventListener('click', () => {
   step++;
   document.querySelector("svg").style.fill = `url(#lg-step-${step})`
})
.stepper {
    transition-property: fill;
    transition-duration: 1s;
    transition-timing-function: ease-in-out;
}
<svg id="test_svg" viewBox="0 0 620 50" width="300" height="20" fill="url(#lg-step-0)" stroke="black" stroke-width="1" class="stepper">
    <path d="m 71 29 h -60 c -14 0 -14 -19 0 -19 h 60 c 6 -10 19 -10 25 0 h 200 c 6 -10 19 -10 25 0 h 200 c 6 -10 19 -10 25 0 h 60 c 14 0 14 19 0 19 h -60 c -6 10 -19 10 -25 0 h -200 c -6 10 -19 10 -25 0 h -200 c -6 10 -19 10 -25 0" />
    <linearGradient id="lg-step-0">
        <stop offset="0%" stop-color="blue" />
        <stop offset="6%" stop-color="blue" stop-opacity="60%" />
        <stop offset="6%" stop-color="transparent" />
    </linearGradient>
    <linearGradient id="lg-step-1" >
        <stop offset="0%" stop-color="blue" />
        <stop offset="14%" stop-color="blue" stop-opacity="60%"/>
        <stop offset="14%" stop-color="transparent" />
    </linearGradient>
    <linearGradient id="lg-step-2">
        <stop offset="0%" stop-color="blue" />
        <stop offset="50%" stop-color="blue" stop-opacity="60%" />
        <stop offset="50%" stop-color="transparent" />
    </linearGradient>
    <linearGradient id="lg-step-3">
        <stop offset="0%" stop-color="blue" />
        <stop offset="87%" stop-color="blue" stop-opacity="60%" />
        <stop offset="87%" stop-color="transparent" />
    </linearGradient>

</svg>
<button> Next step </button>

All js here is not from my actual code but for testing purpose on SO.

I'd like to achieve some smooth transition between all my steps, but as you can see no transition is ever fired. Any help is appreciated.

Ps I'm actually working on Blazor so the less Js the better is the solution


Solution

  • transition can only interpolate between numbers of some sort, not between strings, and certainly not if those are references to complete DOM subtrees.

    What you actually want to interpolate is the values of the offset attribute of the <stop> element. That is possible, but not with CSS. SVG has two sorts of attributes: those that are pure XML attributes, and "presentation attributes" where you can choose to write them as attributes or as CSS properties. Only the second are accessible by CSS transitions.

    offset belongs to the first category. It can be animated, but only with a SMIL animation. The result is bulky, but it works.

    let step = 0;
    document.querySelector("button").addEventListener('click', () => {
       step++;
       document.querySelectorAll(`.lg-step-${step}`).forEach(el => {
          el.beginElement();
       })
    })
    <svg id="test_svg" viewBox="0 0 620 50" width="300" height="20" fill="url(#lg-step-0)" stroke="black" stroke-width="1" class="stepper">
        <path d="m 71 29 h -60 c -14 0 -14 -19 0 -19 h 60 c 6 -10 19 -10 25 0 h 200 c 6 -10 19 -10 25 0 h 200 c 6 -10 19 -10 25 0 h 60 c 14 0 14 19 0 19 h -60 c -6 10 -19 10 -25 0 h -200 c -6 10 -19 10 -25 0 h -200 c -6 10 -19 10 -25 0" />
        <linearGradient id="lg-step-0">
            <stop offset="0%" stop-color="blue" />
            <stop offset="6%" stop-color="blue" stop-opacity="60%">
                <animate class="lg-step-1" attributeName="offset" from=".06" to=".14"
                         begin="indefinite" dur="1s" fill="freeze"
                         calcMode="spline" keySplines="0.42, 0, 0.58, 1" />
                <animate class="lg-step-2" attributeName="offset" from=".14" to=".5"
                         begin="indefinite" dur="1s" fill="freeze"
                         calcMode="spline" keySplines="0.42, 0, 0.58, 1" />
                <animate class="lg-step-3" attributeName="offset" from=".5" to=".87"
                         begin="indefinite" dur="1s" fill="freeze"
                         calcMode="spline" keySplines="0.42, 0, 0.58, 1" />
            </stop>
            <stop offset="6%" stop-color="transparent">
                <animate class="lg-step-1" attributeName="offset" from=".04" to=".14"
                         begin="indefinite" dur="1s" fill="freeze"
                         calcMode="spline" keySplines="0.42, 0, 0.58, 1" />
                <animate class="lg-step-2" attributeName="offset" from=".14" to=".50"
                         begin="indefinite" dur="1s" fill="freeze"
                         calcMode="spline" keySplines="0.42, 0, 0.58, 1" />
                <animate class="lg-step-3" attributeName="offset" from=".5" to=".87"
                         begin="indefinite" dur="1s" fill="freeze"
                         calcMode="spline" keySplines="0.42, 0, 0.58, 1" />
            </stop>
        </linearGradient>
    </svg>
    <button> Next step </button>