如何在JavaScript中下载一个小文件时显示进度条?

5
我写了以下函数来处理一个AJAX请求,以获取数据:
var xhr = createCORSRequest('GET', url);
if (!xhr) {
    alert('CORS not supported');
    return;
}
xhr.onload = function() {
    var txt1 = xhr.responsetxt1;
    var heading = getheading(txt1);

    if (heading == 'PASS') {
        var file = "My_URL" + ".js";
        downloadFile(file);
        //My code to display a progress bar here?
    } else {
        //Logic to handle failure to load
    }
};

这是我的downloadFile函数,用于下载文件。但是,我不知道如何:
  • 检查下载是否完成。
  • 显示进度条以展示进度。
如果您能添加一份说明,让它更易懂,那就太好了。谢谢。
function downloadFile(fileName) {
    (function(d) {
        var ref = d.getElementsByTagName('script')[0];
        var js = d.createElement('script');
        js.src = fileName;
        ref.parentNode.insertBefore(js, ref);
        // My code to display a progress bar here?
    }(document));
}

看起来像是 https://dev59.com/X2Ml5IYBdhLWcg3wyJfz - Josh Lin
3个回答

3
据我所知,脚本元素没有进度事件。你最好使用XHR获取脚本的主体,然后依靠浏览器缓存进行第二次获取。问题是,你的脚本需要被浏览器解析,似乎没有相应的事件。
我的解决方案是纯JS的,因此你可以将其适应于任何框架。它假设实际下载将占总时间的约70%,并分配20%用于浏览器解析。我使用了非压缩版本的three.js 3D库作为一个较大的源文件。
由于它在另一个沙盒中,进度计算不准确,但如果你提供自己的脚本,那就不会有问题。
请记住,这是一个相当简化的实现。例如,我使用了一个简单的HR作为进度条。

//this is a rough size estimate for my example file
let TOTAL_ESTIMATE = 1016 * 1024;
// I use a hr as a 
let bar = document.getElementById("progressbar");
let button = document.getElementById("dlbtn");

var js; // to hold the created dom element
var fileName; // to hold my cacheBusted script adress

/* this function will be called several times during (the first) download, with info about how much data is loaded */

function onProgress(e) {
  var percentComplete = e.loaded / TOTAL_ESTIMATE;
  if (e.lengthComputable) {
    percentComplete = e.loaded / e.total;
  }
  p = Math.round(percentComplete * 100);
  console.log("progress", p + "%,", e.loaded, "bytes loaded")
  bar.style = "width: " + (5 + .6 * p) + "%"; // I just assume dl will be around 60-70% of total time

}

/* this function is called when info comes. at the end of the initial download, the readystate will be 4 so we then set the file's src attribute, triggering a re-download but taking advantage of the browser's cache. It's not ideal, and simply `eval` ing the data would probably yield better results. I just assumed you wanted a <script> tag on your page, and for it to be evaluated. */ 
function onReadyState(e) {
  let r = e.target;
  //this is lifted almost verbatim from http://vanilla-js.com/ ;)
  if (r.readyState != 4 || r.status != 200)
    return;
  let l = r.responseText.length;
  console.log("Success !", l, "bytes total (" + Math.round(l / 1024) + " KB )");
  bar.style = "width: 70%";
  //just add / to next line to toggle ending methods
  /* you could speed up the proces by simply eval()ing the returned js. like so (please be aware of security concerns) :
  eval.bind(window)(r.responseText);
  onScriptLoaded();
  /*/

  js.src = fileName;
  bar.style = "width: 80%";
  var ref = document.getElementsByTagName('script')[0];
  ref.parentNode.insertBefore(js, ref);
  //*/
  
};

//this is called when the script has been evaluated :
function onScriptLoaded() {
  bar.style = "width: 100%; background-color: lightgreen;";
  button.disabled = false;
  console.log("script has been evaluated ?", THREE ? "yes" : "no"); // the demo file exposes window.THREE
}

function downloadFile(file) {
  button.disabled = true;
  (function(d) {
    // this helps to test this script multiple times. don't keep it
    fileName = file + "?bustCache=" + new Date().getTime();


    console.log("inserting new script");
    js = d.createElement('script');
    js.type = "text/javascript";
    js.defer = "defer";
    js.async = "async";
    var r = new XMLHttpRequest();
    bar.style = "width: 5%"; //always react ASAP
    r.addEventListener("progress", onProgress);
    r.open("GET", fileName, true);
    r.onreadystatechange = onReadyState;
    js.onload = onScriptLoaded;
    r.send();
    // My code to display a progress bar here?
  }(document));
}
#progressbar {
  height: 6px;
  border-radius: 3px;
  width: 0%;
  border-color: green;
  background-color: green;
}
<button onclick="downloadFile('https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.js', this)" id="dlbtn">Download</button>
<script id="dummy" type="text/javascript">
  console.log("dummy script ready")
</script>
<hr id="progressbar" align="left" />


1
是的,这是一个异步下载。你不能在同步下载上有进度条,因为它们是同步的,会阻塞你的进度条 :)。为了准确显示进度,你需要知道文件大小。如果文件大小总是相同的,你可以使用静态大小(就像我用 TOTAL_ESTIMATE 一样)。 - Boris
1
我已经编辑添加了注释和“更好”的评估方法...您可以通过复制我的片段并将行/* you could speed ...更改为//* you could speed ...来尝试它。 - Boris
谢谢,快速问题,"脚本已被评估"是什么意思?谢谢。另外,eval.bind是什么? - Zac
1
我们正在加载JavaScript(用于我的演示Three.js)。一旦加载完成,我假设您希望运行该脚本;这就是您的脚本通过两行代码js.src = fileName;ref.parentNode.insertBefore(js, ref);所完成的。运行已加载的脚本就是我所说的评估。我的代码只是检查加载后是否确实存在名为“THREE”的变量。 - Boris
再次感谢。如果我再打扰你一次,你提到“在(第一次)下载期间,此函数将被多次调用,其中包含有关加载了多少数据的信息”-为什么会运行多次?难道不应该只运行一次吗?在我的另一个带有CSS进度条上显示百分比的ProgressBar中,我认为它会导致它来回移动几次。我是对的吗?为什么会这样呢?此外,var THREE的存在是一个很好的测试,证明脚本已经在内存中,并且将在另一个页面上执行吗?谢谢! - Zac
显示剩余3条评论

1
免责声明:我没有在Android上测试过这个。我只在Chrome(桌面版)上进行了测试。您应该在浏览器控制台中看到进度事件的写入。
var url = 'url/to/file/';
var request = new XMLHttpRequest();
request.responseType = "blob"; // Not sure if this is needed
request.open("POST", url);

var self = this;
request.onreadystatechange = function () {
    if (request.readyState === 4) {
        var file = $(self).data('file');            
        var anchor = document.createElement('a');
        anchor.download = file;
        anchor.href = window.URL.createObjectURL(request.response);
        anchor.click();            
    }
};

request.addEventListener("progress", function (e) {
    if(e.lengthComputable) {
        var completedPercentage = e.loaded / e.total;
        console.log("Completed: ", completedPercentage , "%");
    }
}, false);
request.send();

希望这有所帮助。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接