Progress Bar (індикатор виконання) використовується для відображення процесу виконання операції.
Розглянемо способи реалізації progress bar на веб-сторінці, та змінення індикації виконання за допомогою JavaScript.
progress
У HTML 5 є елемент progress для для відображення індикатора який показує хід виконання операції.
Елемент progress має два основні атрибути: max - максимальне значення позиції процесу, value - поточне значення позиції процесу. Зазвичай вказується max=100 і value=від 0 до 100.
var progress=document.getElementById('progres');
progress.value=50;
Progress bar за допомогою CSS
Для створення Progress Bar на сайті за допомогою CSS використовується два елемента.
<div id="progressbar">
<div id="progressbar_position"></div>
</div>
<style>
#progressbar{
width:300px;
height:18px;
background-color:#d8d9d9;
}
#progressbar_position{
width:10%;
height:100%;
background-color:#4caf50;
}
</style>
Зміна значення через JavaScript вказується стиль width від 0 до 100% :
var progressbar_position=document.querySelector('#progressbar_position');
progressbar_position.style.width='80%'; //від 0 до 100 %
Відображення під час циклу
Під час циклу операцій у JavaScript відображати стан progress виявляється не так просто. Це пов'язано з тим що JavaScript є однопотоковим, тобто усі операції відбуваються в одному потоці і цикл блокує цей потік виконання.
function myFunc(){
var s='', n=12345, progres=document.getElementById('progress2');
progres.value=0;
progres.max=n;
for(var i=0;i<n;i++){
s+=(Math.random()*i)+' ';
progres.value=i;
}
return s;
}
myFunc();
Щоб не блокувати потік виконання можна розбити цикл на частини і виконувати їх ці частини через таймер. Це дозволить не блокувати потік виконання і показувати процес виконання операції у progress.
При цьому загальний час виконання функції може не суттєво збільшитися.
//необхідні змінні оголошуємо як глобальні (глобальна видимість)
var s='', n=123456, progres=document.getElementById('progress3');
function myFunc(a){
if(a==undefined){ //якщо перший виклик функції (не через таймер)
a=0;
progres.value=0;
progres.max=n;
}
for(var i=a;i<a+100;i++){
//якщо i більше n тоді дійшли операцію завершено
if(i>n){ /* що необхідно зробити при завершенні операції */ }
s+=(Math.random()*i)+' ';
}
progres.value=i;
if(a<n)setTimeout(myFunc, 25, a+100); //викликаємо функцію через 25мс з параметром a+100
}
myFunc(); //виклик функції
Функція-конструктор myFunc2 створює об'єкт приймаючи параметри: progress - CSS селектор елемента progress, функція яка виконається коли progres досягне "100%".
function myFunc2(progress, onfinish){
this.start=function(){
for(var i=this.index;i<this.index+this.count;i++){
if(i>this.max){
this.onfinish(this.result);
this._clear();
return;
}
//виконуємо потрібні операції
this.result+=Math.random()*100;
}
this.index=i;
this.progress.value=this.index;
setTimeout(this.start.bind(this),25,this.index);
}
this._clear=function(){
this.result=0;
this.max=123456; //загальна кількість необхідних циклів
this.count=1000; //кількість циклів в одному фрагменті виконанні функції
this.index=0;
this.progress.value=this.index;
this.progress.max=this.max;
}
this.progress=document.querySelector(progress);
this._clear();
this.onfinish=onfinish || function(){};
}
var ob=new myFunc2('#progress4', function(res){ alert('результат виконання: '+res); });
ob.start();
Реалізація на основі Promise:
function myFunc3(progress){
return new Promise(function(resolve){
var ob={};
ob.start=function(){
for(var i=this.index;i<this.index+this.count;i++){
if(i>this.max){
this.onfinish(this.result);
this._clear();
return;
}
//виконуємо потрібні операції
this.result+=Math.random()*100;
}
this.index=i;
this.progress.value=this.index;
setTimeout(this.start.bind(this),25,this.index);
}
ob._clear=function(){
this.result=0;
this.max=123456; //загальна кількість необхідних циклів
this.count=1000; //кількість циклів в одному фрагменті виконанні функції
this.index=0;
this.progress.value=this.index;
this.progress.max=this.max;
}
ob.progress=document.querySelector(progress);
ob._clear();
ob.onfinish=resolve || function(){};
ob.start();
});
}
myFunc3('#progress5').then(function(res){ alert('результат виконання: '+res); });
Визначаємо скільки ще часу залишилося до повного виконання.
Час будемо отримувати з performance.now() який дає більш точний результат.
function myFunc4(progress, label){
return new Promise(function(resolve){
var ob={};
ob.start=function(){
for(var i=this.index;i<this.index+this.count;i++){
if(i>this.max){
this.onfinish(this.result);
this._clear();
return;
}
//виконуємо потрібні операції
this.result+=Math.random()*100;
}
this.index=i;
//визначаємо скільки необхідно часу
var p=(this.index/this.max)*100; //визначаємо відсоток % виконання
var tm=performance.now();
var pt=(tm-this.time)/p; //скільки часу виконання зайняв 1%
var tz=parseInt(((100-p)*pt)/1000); //визначаємо скільки часу потрібно на % які ще залишилися
this.label.innerText='залишилося '+(isNaN(tz)?'??:??':this.secToTime(tz));
this.progress.value=this.index;
setTimeout(this.start.bind(this),25,this.index);
}
ob._clear=function(){
this.result=0;
this.max=1234567; //загальна кількість необхідних циклів
this.count=1000; //кількість циклів в одному фрагменті виконанні функції
this.index=0;
this.progress.value=this.index;
this.progress.max=this.max;
}
ob.secToTime=function(s){
var h,m,s;
h=parseInt(s/3600); //кількість годин
m=parseInt((s-(0*3600))/60); //кількість хвилин
s=parseInt(s-(m*60)); //кількість секунд
return (h<10?'0'+h:h)+':'+(m<10?'0'+m:m)+':'+(s<10?'0'+s:s);
};
ob.progress=document.querySelector(progress);
ob.label=document.querySelector(label);
ob._clear();
ob.onfinish=resolve || function(){};
ob.time=performance.now();
ob.start();
});
}
myFunc4('#progress6', '#label6').then(function(res){ alert('результат виконання: '+res); });