在网页中点击某个按钮或链接下载文件的时候,从点击到文件开始下载的过程中有一个过程,如果没有对这个过程进行判断的话,用户会一直不停地进行点击,体验非常差。浏览器没有办法直接获取到下载文件的事件,一个讨巧的方法是可以使用iframe的onload事件,代码如下:
代码如下 | 复制代码 |
var url = "http://www.111cn.net"; var iframe = document.createElement('iframe'); iframe.src = url; iframe.style.display = 'none'; iframe.onload = function() { console.debug('start downloading...'); document.body.removeAttribute(iframe); } document.body.appendChild(iframe); |
这段代码在Firefox下面可以正常获取到onload的事件,但在Google Chrome和IE下面,如果iframe src属性的链接是一个网页的话,可以触发onload事件,但如果HTTP文件头中包含Content-disposition: attachment…即下载文件的链接的话,不会触发这个事件。浏览器对iframe具体的支持情况可见:Events to expect when dynamically loading iframes in javascript。
这一点有点可惜,有人提出了一个利用Cookies的思路,即在服务器端捕获文件下载的进度,然后随时将进度写入到Cookies中,前台javascript轮询Cookies,从而得到文件是否开始进行下载。这种方案详细的可以参考:Detecting the File Download Dialog In the Browser,后台如果使用Spring的话可以通过过滤器来判断文件下载的情况,
。我们需要借助threadlocal来实现线程安全。
代码如下 | 复制代码 |
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if(usePerformance){ StopWatch stopWatch = new StopWatch(handler.toString()); stopWatchLocal.set(stopWatch); stopWatch.start(handler.toString()); } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { if(usePerformance){ StopWatch stopWatch = stopWatchLocal.get(); stopWatch.stop(); String currentPath = request.getRequestURI(); String queryString = request.getQueryString(); queryString = queryString == null ? "":"?" + queryString; log.info("access url path:" + currentPath + queryString + " |time:" + stopWatch.getTotalTimeMillis()); stopWatchLocal.set(null); } } |
呵呵,如果你没有使用springMVC可以使用filter来完成
代码如下 | 复制代码 |
stopWatch.start(); doFilterChain(); stopWatch.stop(); |
但这种方案实施起来需要后台进行配合,前后台的代码都过于复杂,另外如果浏览器将Cookies功能关闭的话就会出错,后来无意中在Google Chrome下面运行这个Plunker代码,发现可以正常捕获iframe加载后的时间。原始代码是使用的AngularJS,代码如下:
代码如下 | 复制代码 |
var url = 'https://www.111cn.net'; var e = angular.element("<iframe id=export style='display:none'></iframe>"); e.attr('src', url); e.load(function() { console.debug('start downloading...'); }); angular.element('body').append(e); |
这里使用了element的load事件,即页面实际加载DOM元素的事件。但非常奇怪的是,如果我将上述代码中的url换成其他的下载文件之后依然无法激活这个事件。后来仔细分析对比了二者中的HTTP Response Header的差异,发现需要在服务器端增加下面两个HTTP头文件内容即可:
代码如下 | 复制代码 |
response.addHeader("X-Content-Type-Options", "nosniff"); // 提示浏览器不让其在frame或iframe中加载资源的文件内容 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/X-Frame-Options response.addHeader("X-Frame-Options", "deny"); |
如果使用jQuery的话,javascript代码如下:
代码如下 | 复制代码 |
var url = 'https://github.com/blueimp/jQuery-File-Upload/archive/master.zip'; var e = $("<iframe id=export style='display:none'></iframe>"); e.attr('src', url); e.load(function() { console.debug('start downloading...'); }); $('body').append(e); |