javascriptaccessibilityweb-animations-api

Issues with ScaleX() on browser Zoom


When zooming in the browser, the scaleX() property is not staying consistent. TranslateX() works exactly as it should which is why I'm reaching out.

I am building out a stepped progress bar with the Web Animations API and Vanilla JS, the intention is that there will be a form inserted into this so as we step through form steps the animation/steps will show progress through it. The issue I am encountering is when I am testing for ADA compliance, specifically when zooming in on the page. And even more specifically, it's only when the zoom percentage is not a multiple of 100. So 100, 200, 300, and 400% work perfectly. But 110, 125, 250%, just to name a few, are having issues. The dot that slides across the screen is working as it should.

The unexpected behavior is in the bar that expands across the screen along with the dot, sometimes it goes too far sometimes it doesn't go far enough. The thing that is really confusing me is that both the bar and the dot are both being "controlled" by the same measurements, which is taking the parent div's width and dividing by 3 and then multiplying by the current step. This is what leads me to assuming the issue is in the scaleX transform. I am still testing this overall in IE, encountering the issue in chrome and firefox.

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>My stepped progress bar</title>
    <link href="style.css" type="text/css" rel="stylesheet" />
    <link href="fonts.css" type="text/css" rel="stylesheet" />
    <!-- Web Animation API polyfill-->
    <script src="https://rawgit.com/web-animations/web-animations-js/master/web-animations.min.js"></script>
</head>
<body>
    <section>
        <div class="progress__container">

                <div class="progress__bar">
                        <div id="progress__fill" class="step1"></div>
                        <div class="circ__container">
                            <div class="circ" id="circ__1"></div>
                            <div class="circ" id="circ__2"></div>
                            <div class="circ" id="circ__3"></div>
                            <div class="circ" id="circ__4"></div>
                        </div>




                        <div id="progress__dot" class="prog__1"></div>
                    </div>
            <div class="backBar"></div>
            <div class="flexrow">

                    <span class="stepName tab-1">Account</span>
                    <span class="stepName tab-2">Frequency</span>
                    <span class="stepName tab-3">Amount</span>
                    <span class="stepName tab-4">Taxes</span>
                </div>
            <div class="button__container">
                <button class="buttonStep" id="back">Back</button>
                <button class="buttonStep is-active" id="next">Next</button>
            </div>
    </div>

    </section>
<script src="script-api.js"></script>
</body>
</html>

CSS:

/* General Styles */
body {
    font-family: Arial, helvetica, sans-serif;
}



/* Slider Bar Animation */
#progress__fill {
    height:2px;
    position: absolute;
    top: 7px;
    left: 0;
    background-color: darkred;
    width: 1px;
}
#progress__dot {
    background-color: darkred;
    color: #fff;
    border-radius: 50%;
    height: 8px;
    width: 8px;
    position: absolute;
    text-align:center;
    line-height: 8px;
    padding: 6px;
    top: 0;
    font-size: 12px;


}





/* Static Bar Elements */
.progress__container {
 width: 600px;
 margin: 20px auto;
 position: relative;

}
.backBar {
    height:2px;
    width:96%;
    position: absolute;
    top: 7px;
    left: 2%;
    background-color: lightgrey;
}
.progress__bar {
    z-index: 100;
    position: relative;
    width: 96%;
    margin: 0 auto;
}
.circ {
    background-color: #fff;
    border: 2px solid lightgrey;
    border-radius: 50%;
    height: 8px;
    width: 8px;
    display: inline-block;
    position: absolute;

}

.hide {
    visibility: hidden
}
.flexrow {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
}
.circ__container {
    padding-top: 3px;
}
.flexrow {
    margin-top: 20px;
}
.stepName {
    font-size: 12px;
    text-align: center;
}
.stepName:first-child {
    text-align: left;
}
.stepName:last-child {
    text-align: right;
}
.stepName.bold {
    font-weight: 600;
}


/* Buttons */
.button__container {
    padding-top: 100px;
}
.buttonStep {
    background: grey;
    color: #fff;
    padding: 10px 25px;
    border-radius: 10px;
    font-size: 16px;
}
#back {
    float: left;
}
#next {
    float: right;
}
.is-active {
    background: darkred;
}

JS:

// give a starting value for the transformation
var slideBarWidth = 0,
    slideBarScalePoint = 0,
    currentStep = 1,
    dot = document.getElementById('progress__dot'),
    boxWidth = dot.parentElement.offsetWidth;

// insert the current step number into the progress dot
dot.innerHTML = currentStep;

// place the background dots on the bar
for (var x = 1; x < 5; x++) {
    document.getElementById('circ__' + x).setAttribute('style', 'left: ' + ((boxWidth / 3) * (x - 1)) + 'px');
    if (x == 4) {
        document.getElementById('circ__' + x).setAttribute('style', 'left: ' + (((boxWidth / 3) * (x - 1)) - document.getElementById('circ__' + x).offsetWidth)+ 'px');
    }
}

// define the timing for progress dot
var dotTiming = {
  duration: 500,
  fill: "both",
  easing: 'ease-in-out'
}
// define the timing for sliding bar
var barTiming = {
    duration: 500,
    fill: "both",
    easing: 'ease-in-out'
  }
  var passedTiming = {
      fill: "both"
  }

// make the first step name bold
document.getElementsByClassName('tab-' + currentStep)[0].classList.add('bold');





// on click fire the animation
document.getElementById('next').addEventListener('click', function() {
    // make sure the slider does not go further than it should
    if (currentStep > 3){return;}



    // define the keyframes for the progress dot
    if (currentStep == 3) {

        var moveDot = [  
            {transform: 'translateX(' + ((boxWidth / 3) * (currentStep - 1)) +  'px)'},
            {transform: 'translateX(' + (((boxWidth / 3) * (currentStep)) - dot.offsetWidth) + 'px)'}

        ];
    } else {
        var moveDot = [
            {transform: 'translateX(' + ((boxWidth / 3) * (currentStep - 1)) +  'px)'},
            {transform: 'translateX(' + ((boxWidth / 3) * (currentStep)) +  'px)'},

        ];
    }


    // define the keyframes for the sliding bar
    var slideBar = [
        {
            transform: 'scaleX(' + ((boxWidth / 3) * (currentStep - 1)) + ')',
            transformOrigin: 'left'
        },
        {
            transform: 'scaleX(' + ((boxWidth / 3) * (currentStep)) + ')',
            transformOrigin: 'left'
        }

    ];
    var showDot = [
        {backgroundColor: '#fff', border: '2px solid lightgrey' },
        {backgroundColor: 'darkred', border: '2px solid darkred' }
    ];




    // putting the keyframes and timings together (progress dot)
    var movingDot = document.getElementById("progress__dot").animate(
    moveDot,
    dotTiming
    );

    // putting the keyframes and timings together (sliding bar)
    var slidingBar = document.getElementById("progress__fill").animate(
        slideBar,
        barTiming
        );
    var passingDot = document.getElementById('circ__' + currentStep).animate(
            showDot,
            passedTiming
        );



    // making the animation play forwards 
    movingDot.playbackRate = 1;
    slidingBar.playbackRate = 1;
    passingDot.playbackRate = 1;

    // starting the animations
    movingDot.play();
    slidingBar.play();
    movingDot.onfinish = passingDot;

    // incrementing and setting the step counter
    currentStep++;
    document.getElementById("progress__dot").innerHTML = currentStep;

    if (currentStep > 1) {
        document.getElementById('back').classList.add('is-active');
    }
    if (currentStep > 3) {
        document.getElementById('next').classList.remove('is-active');
    }

    // toggling the bold class for the step names
    document.getElementsByClassName('tab-' + (currentStep - 1))[0].classList.remove('bold');

    setTimeout(() =>
        {
            document.getElementsByClassName('tab-' + currentStep)[0].classList.add('bold');
        }, 600);
});


document.getElementById('back').addEventListener('click', function() {
    // make sure the slider does not go back past the beginning
    if (currentStep < 2){return;}

    // define the keyframes
    if (currentStep == 4) {
        var moveDot = [
            {transform: 'translateX(' + ((boxWidth / 3) * (currentStep - 2)) + 'px)'},
            {transform: 'translateX(' + (((boxWidth / 3) * (currentStep - 1)) - dot.offsetWidth) + 'px)'}

        ];
    } else {
        var moveDot = [
            {transform: 'translateX(' + ((boxWidth / 3) * (currentStep - 2)) + 'px)'},
            {transform: 'translateX(' + ((boxWidth / 3) * (currentStep - 1)) + 'px)'}

        ];
    }

    var slideBar = [
        {
            transform: 'scaleX(' + ((boxWidth / 3) * (currentStep - 2)) + ')',
            transformOrigin: 'left'
        },
        {
            transform: 'scaleX(' + ((boxWidth / 3) * (currentStep -1 )) + ')',
            transformOrigin: 'left'
        }

    ];
    var showDot = [
        {backgroundColor: 'darkred', border: '2px solid darkred' },
        {backgroundColor: '#fff', border: '2px solid lightgrey' }    
    ];


    // putting the keyframes and timings together
    var movingDot = document.getElementById("progress__dot").animate(
        moveDot,
        dotTiming
    );

    var slidingBar = document.getElementById("progress__fill").animate(
        slideBar,
        barTiming
        );

        var passingDot = document.getElementById('circ__' + currentStep).animate(
            showDot,
            passedTiming
        );


    // making the animation reverse
    movingDot.playbackRate = -1;
    slidingBar.playbackRate = -1;
    passingDot.playbackrate = -1;

    // starting the animation
    movingDot.play();
    slidingBar.play();
    movingDot.onfinish = passingDot;





    // decrementing and setting the step counter
    currentStep--;

    // set the current step number as the number in the progress dot on the page
    document.getElementById("progress__dot").innerHTML = currentStep;

    if (currentStep < 4) {
        document.getElementById('next').classList.add('is-active');
    }
    if (currentStep < 2) {
        document.getElementById('back').classList.remove('is-active');
    }

     // toggling the bold class for the step names
    document.getElementsByClassName('tab-' + (currentStep + 1))[0].classList.remove('bold');

    setTimeout(() =>
        {
            document.getElementsByClassName('tab-' + currentStep)[0].classList.add('bold');
        }, 400);



});

I expect the dot and the slider to be aligned as they go across the page, regardless of zoom percentage


Solution

  • In doing some experimenting, I figured out that I could just use "width" to transform the item rather than scaleX. Here is what I ended up using:

    next button event:

     var slideBar = [
            {
                width: ((boxWidth / 3) * (currentStep - 1)) + 'px'
            },
            {
                width: ((boxWidth / 3) * (currentStep)) + 'px'
            }
    
        ];
    

    back button event:

    var slideBar = [
            {
                width: ((boxWidth / 3) * (currentStep - 2)) + 'px'
            },
            {
                width: ((boxWidth / 3) * (currentStep -1 )) + 'px'
            }
    
        ];