Q:

Transition back from keyframe mid-animation

I am looking for a way to transition back from a rotate transition mid-keyframe. I have a long-running keyframe which repeatedly transforms from 0deg to 360deg.
When that animation is stopped (via class removal), I want the element to go back to 0deg, from the current position. Currently, I am using a second keyframe to animate back to 0deg, but that one always spins the full 360deg back to 0deg, which is not what I want.
I made a JSFiddle to demonstrate the problem: https://jsfiddle.net/egdct6qz/2/
Thanks for your help!

A:

You can achieve that using the following combination of CSS and JavaScript, which uses getComputedStyle ().

const circle = document.querySelector (".circle");

circle.onclick = () => {
    if (circle.classList.contains ("circle--spinning")) {
     // Get rotation and start animating back
        let currentRotation = getRotationDegrees (circle);
        animateRotationBack (currentRotation);
    }
    
    // Toggle the spinning itself
    circle.classList.toggle ("circle--spinning");
} 

function animateRotationBack (angle) {
 const keyframe = `
     @keyframes spin-back {
         from { transform: rotate(${angle}deg); }
            to { transform: rotate(0deg); }
        }
    `;

    // Get inserted style-tag or create a new one
    const style = document.getElementById ("spin-back-kf") || document.createElement ("style");
    style.textContent = keyframe;
    style.id = "spin-back-kf";
    
    // Remove previously inserted style tag if existant
    style.parentNode && style.parentNode.removeChild (style);
    
    // Append style tag and set animation
    document.body.appendChild (style);
}

function getRotationDegrees (element) {
    // get the computed style object for the element
    const style = window.getComputedStyle (element);
     
    // this string will be in the form 'matrix(a, b, c, d, tx, ty)'
    let transformString = style["-webkit-transform"]
                       || style["-moz-transform"]
                       || style["transform"];
                       
    if (!transformString || transformString === "none")
        return 0;
        
    // Remove matrix notation
    transformString = transformString
     .substr (0, transformString.length - 2)
        .substr (7);
    
    // Split and parse to floats
    const parts = transformString
     .split (",")
        .map (num => parseFloat (num));
        
    // Destructure in a and b
    let [a, b] = parts;
    
    // Doing atan2 on (b, a) will give you the angle in radians
    // Convert it to degrees and normalize it from (-180...180) to (0...360)
    const degrees = ((180 * Math.atan2 (b, a) / Math.PI) + 360) % 360;
    
    return degrees;
}
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

.wrapper {
    margin: 5% auto;
    text-align: center;
}

.circle {
    padding: 3em;
    border-radius: 3em;
    position: relative;
    background-color: #EE5622;
    display: inline-block;
    cursor: pointer;
    
    animation: .5s spin-back forwards;
}

.circle--spinning {
    animation: 2s spin infinite linear;
}

.square {
    padding: 1em;
    background-color: #F1F7ED;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: inline-block;
}

@keyframes spin {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}
<div class="wrapper">
    <span class="circle circle--spinning">
        <span class="square"></span>
    </span>
</div>

