Search code examples
htmlcssblazorprogress-barbootstrap-5

Why isn't my progress-bar transition behaving correctly on mobile browsers?


I am developing a Blazor project and I am facing an issue with bootstrap progress-bar on mobile browser.

I showcase my issue by having 2 buttons and a progress-bar, one button start it and the other one stop it. animation are managed using the transition property. It all works as expected on my desktop and laptop but fails on mobile browsers (however it also works on emulated mobile browser using dev tool).

On mobile browsers, I start to experience issue after the first press on the Stop button. The second press on Start does not reset properly the progress bar which instead start to increase slowly despite the 'transition: none' instruction. After that successive presses on the stop and start buttons are broken (a screen recording of the bug observed on mobile is available on swisstransfer file share service: https://www.swisstransfer.com/d/ec018149-090a-46bf-b798-8527055f0dc8).

The code in question is below and the site can be tested on Azure.

Thank you in advance


    @page "/"
    
    
    @* The progress bar ===================================================================*@
    <div style="padding-bottom: 5px;">
        <div class="progress" style="height: 10px;">
            <div class="progress-bar" style="width: @_progressPercent%; background-color: @_progressColor; transition: @_transition"></div>
        </div>
    </div>
    
    
    <button disabled="@_startButtonIsDisabled" @onclick="StartProgressBar">Start</button>
    <button disabled="@_stopButtonIsDisabled" @onclick="StopProgressBar">Stop</button>
    
    
    
    @code {
    
        private Timer? _timer;
        private int _counter;
        private string _progressColor="grey";
        private string _transition="none";
        private int _progressPercent=100;
        private double _t1 = 4;
        private double _t2 = 8;
        private double _tend = 16;
        private DateTime _timerStartTime;
        private bool _startButtonIsDisabled = false;
        private bool _stopButtonIsDisabled = true;
    
    
        private void TimerCallback(object? o)
        {
            _counter++;
            switch (_counter)
            {
                case 0:
                    _progressPercent = (int)(100 * ( 1 - _t1 / _tend));
                    _transition = $"width {_t1}s linear";
                    InvokeAsync(StateHasChanged); // Update the UI
                    break;
                case 1:
                    _progressPercent = (int)(100 * ( 1 - _t2 / _tend));
                    _progressColor = "orange";
                    _transition = $"width {_t2-_t1}s linear";
                    InvokeAsync(StateHasChanged); // Update the UI
                    _timer.Change(1000*(int)(_t2-_t1), Timeout.Infinite); // Set timer to _t2-_t1 seconds for the next event
                    break;
                case 2:
                    _progressPercent = 0;
                    _progressColor = "red";
                    _transition = $"width {_tend-_t2}s linear";
                    InvokeAsync(StateHasChanged); // Update the UI
                    _timer.Change(1000*(int)(_tend-_t2), Timeout.Infinite); // Set timer to _tend-_t2 seconds for the next event
                    break;
                case 3:
                    InvokeAsync(StateHasChanged); // Update the UI
                    _timer.Dispose(); // Stop the timer
                    break;
            }
        }
    
    
        void StartProgressBar()
        {
            _startButtonIsDisabled = true;
            _stopButtonIsDisabled = false;
            _timerStartTime = DateTime.Now;
    
            // reset the progress bar ==================
            _counter = -1;
            ResetProgressBar();
            InvokeAsync(StateHasChanged); 
    
            _timer = new Timer(TimerCallback, null, 0, (int)_t1 * 1000);
        }
    
        
        void ResetProgressBar()
        {
            _progressPercent = 100;
            _progressColor = "green";
            _transition = "none";
        }
    
    
        void StopProgressBar()
        {
            _startButtonIsDisabled = false;
            _stopButtonIsDisabled = true;
            _timer.Dispose(); // Stop the timer
            var ts = DateTime.Now - _timerStartTime;
            _progressPercent = (int)(100 * ( 1 - ts.TotalMilliseconds / (_tend*1_000)));
            _transition = "none";
        }
    
    
    }

 

Solution

  • The bug was from the following lines of code in the function StartProgressBar():

    ResetProgressBar();
    InvokeAsync(StateHasChanged);     
    _timer = new Timer(TimerCallback, null, 0, (int)_t1 * 1000);
    

    on mobile, it seems that for some reasons the creation of the new Timer occurs before the execution of ResetProgressBar() and InvokeAsync(StateHasChanged) had complete.

    To fix this I needed to add a dueTime to the timer construction, like this: _timer = new Timer(TimerCallback, null, 5, (int)_t1 * 1000); to delay it in this case by 5 ms.