<script>document.getElementById('progress').style.width = '1%';</script>
XMLHttpRequest.onreadystatechange
? - Yes. We can even use HTTP headers to send a pre-notification of the total duration of the task being performed (if this is possible in principle).onreadystatechange
handler onreadystatechange
we, by getting byte by byte, will be able to conclude about the progress of the task. <?php set_time_limit(0); for ($i = 0; $i < 50; $i++) // , 50 { sleep(1); // echo ' '; }
XMLHttpRequest.onreadystatechange
handler: xhr.onreadystatechange = function() { if (this.readyState == 3) { var progress = this.responseText.length; document.getElementById('progress').style.width = progress + '%'; } };
readyState == 2
we can get information from the headers. Let's take this and use to determine the number of iterations: header('X-Progress-Max: 50');
var progressMax = 100; xhr.onreadystatechange = function() { if (this.readyState == 2) { progressMax = +this.getResponseHeader('X-Progress-Max') || progressMax; } else if (this.readyState == 3) { var progress = 100 * this.responseText.length / progressMax; document.getElementById('progress').style.width = progress + '%'; } };
output_buffering
option is enabled in PHP, you need to take this into account. Everything is simple here: if it is enabled, then when the script is run ob_get_level()
will be greater than 0. It is necessary to bypass buffering. Also, if you use the Nginx FastCGI PHP bundle, you need to consider that both FastCGI and Nginx itself will buffer the output. The latter will do this if it is going to compress the data for sending. The problem is solved simply: header('Content-Encoding: none', true);
fastcgi_keep_conn on;
onreadystatechange
handler onreadystatechange
they need to be ignored. In my opinion, since the entire configuration component is transmitted in the headers, then this number of ignored spaces is also better to be conveyed in the header. Let's call it padding . <?php header('X-Progress-Padding: 20', true); echo str_repeat(' ', 20); flush(); // ...
var progressMax = 100, progressPadding = 0; xhr.onreadystatechange = function() { if (this.readyState == 2) { progressMax = +this.getResponseHeader('X-Progress-Max') || progressMax; progressPadding = +this.getResponseHeader('X-Progress-Padding') || progressPadding; } else if (this.readyState == 3) { var progress = 100 * (this.responseText.length - progressPadding) / progressMax; document.getElementById('progress').style.width = progress + '%'; } };
output_buffering
. If you have complex buffering and you do not want to break it, you can use this function: function ob_ignore($data, $flush = false) { $ob = array(); while (ob_get_level()) { array_unshift($ob, ob_get_contents()); ob_end_clean(); } echo $data; if ($flush) flush(); foreach ($ob as $ob_data) { ob_start(); echo $ob_data; } return count($ob); }
function ProgressLoader(url, callbacks) { var _this = this; for (var k in callbacks) if (typeof callbacks[k] != 'function') callbacks[k] = false; delete k; function getXHR() { var xhr; try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } catch (E) { xhr = false; } } if (!xhr && typeof XMLHttpRequest != 'undefined') xhr = new XMLHttpRequest(); return xhr; } this.xhr = getXHR(); this.xhr.open('GET', url, true); var contentLoading = false, progressPadding = 0, progressMax = -1, progress = 0, progressPerc = 0; this.xhr.onreadystatechange = function() { if (this.readyState == 2) { contentLoading = false; progressPadding = +this.getResponseHeader('X-Progress-Padding') || progressPadding; progressMax = +this.getResponseHeader('X-Progress-Max') || progressMax; if (callbacks.start) callbacks.start.call(_this, this.status); } else if (this.readyState == 3) { if (!contentLoading) contentLoading = !!this.responseText .replace(/^\s+/, ''); // .trimLeft() — _ if (!contentLoading) { progress = this.responseText.length - progressPadding; progressPerc = progressMax > 0 ? progress / progressMax : -1; if (callbacks.progress) { callbacks.progress.call(_this, this.status, progress, progressPerc, progressMax ); } } else if (callbacks.loading) callbacks.loading.call(_this, this.status, this.responseText); } else if (this.readyState == 4) { if (callbacks.end) callbacks.end.call(_this, this.status, this.responseText); } }; if (callbacks.abort) this.xhr.onabort = callbacks.abort; this.xhr.send(null); this.abort = function() { return this.xhr.abort(); }; this.getProgress = function() { return progress; }; this.getProgressMax = function() { return progressMax; }; this.getProgressPerc = function() { return progressPerc; }; return this; }
<?php function ob_ignore($data, $flush = false) { $ob = array(); while (ob_get_level()) { array_unshift($ob, ob_get_contents()); ob_end_clean(); } echo $data; if ($flush) flush(); foreach ($ob as $ob_data) { ob_start(); echo $ob_data; } return count($ob); } if (($work = @$_GET['work']) > 0) { header("X-Progress-Max: $work", true, 200); header("X-Progress-Padding: 20"); ob_ignore(str_repeat(' ', 20), true); for ($i = 0; $i < $work; $i++) { usleep(rand(100000, 500000)); ob_ignore(' ', true); } echo $work.' done!'; die(); }
<!DOCTYPE html> <html> <head> <title>ProgressLoader</title> <script type="text/javascript" src="progress-loader.js"></script> <style> progress, button { display: inline-block; vertical-align: middle; padding: 0.4em 2em; margin-right: 2em; } </style> </head> <body> <progress id="progressbar" value="0" max="0" style="display: none;"></progress> <button id="start">Start/Stop</button> <script> var progressbar = document.getElementById('progressbar'), btnStart = document.getElementById('start'), worker = false; btnStart.onclick = function() { if (!worker) { var url = 'process.php?work=42'; worker = new ProgressLoader(url, { start: function(status) { progressbar.style.display = 'inline-block'; }, progress: function(status, progress, progressPerc, progressMax) { progressbar.value = +progressbar.max * progressPerc; }, end: function(status, s) { progressbar.style.display = 'none'; worker = false; }, }); } else { worker.abort(); progressbar.style.display = 'none'; worker = false; } }; </script> </body> </html>
Source: https://habr.com/ru/post/257053/
All Articles