requestAnimationFrame

早期动画循环

JS的动画很长时间以来都是使用计时器setInterval来达成的。就像这样:

  1. (function(){
  2. function updateAnimations(){
  3. doAnimation1();
  4. doAnimation2();
  5. }
  6. setInterval(updateAnimations, 100);
  7. })()

循环间隔的问题

但是这样使用是有问题的,最大的问题就是时间间隔的问题。不能太长,否则看起来就会卡卡的,也不能太短,更新速度超过了屏幕的刷新速度会造成丢帧。

而且浏览器的计时器其实精度是有限的,精度最高的chrome为4ms。且在页面没有显示在屏幕上的时候,大多数浏览器会对计时器的运行频率做出限制。
这样的动画绘制机制就造成了绘制下一帧动画的时机我们并不能准确掌握。最好的结果应该是正好在屏幕刷新的那一刻绘制下一帧,也就是动画绘制的速度与电脑屏幕刷新速度一致。

于是Mozilla就带头提出了requestAnimationFrame这个API, 如今这个API已经被标准化:

  1. function updateProgress(){
  2. var div = document.getElementById("status");
  3. div.style.width = (parseInt(div.style.width, 10) + 5) + "%";
  4. if (div.style.width != "100%"){
  5. requestAnimationFrame(updateProgress);
  6. }
  7. }
  8. requestAnimationFrame(updateProgress);

Page Visibility API

用户是否真的正在与页面交互是我们需要知道的。如果页面最小化了或隐藏起来了,那么有些功能是可以停下来的。API由3部分组成:

  • document.hidden:表示页面是否隐藏的布尔值
  • document.visibilityState:页面在后台,页面在前台,页面隐藏但正在被预览,页面在屏幕外执行预渲染处理
  • visibilitychange:在可见和不可见转换时触发的事件
  1. function handleVisibilityChange(){
  2. var output = document.getElementById("myDiv"),
  3. msg;
  4. if (document.hidden || document.msHidden || document.webkitHidden){
  5. msg = "Page is now hidden. " + (new Date()) + "<br>";
  6. } else {
  7. msg = "Page is now visible. " + (new Date()) + "<br>";
  8. }
  9. output.innerHTML += msg;
  10. }
  11. EventUtil.addHandler(document, "msvisibilitychange", handleVisibilityChange);
  12. EventUtil.addHandler(document, "webkitvisibilitychange", handleVisibilityChange);

可以看到多玩官网 就使用了上面相应的api, 页面没有显示的时候, title会改变.

Geolocation API

这个API让JS可以通过浏览器来获取用户的地理位置。当然,这是需要获得用户同意的。这个API在浏览器中的实现是navigator.geolocation。这个对象有3个方法:

  • getCurrentPosition(successFn,failFn,configObj): 通常用这个API获取经纬度
  • watchPosition: getCurrentPosition的定时版本
  • clearWatch: 取消监控

File API

不能直接访问用户计算机中的文件,一直都是 Web 应用开发中的一大障碍。2000 年以前,处理文件的唯一方式就是在表单中加入字段,仅此而已。File API(文件 API)的宗旨是为 Web 开发人员提供一种安全的方式,以便在客户端访问用户计算机中的文件,并更好地对这些文件执行操作。

File API 在表单中的文件输入字段的基础上,又添加了一些直接访问文件信息的接口。HTML5 在DOM 中为文件输入元素添加了一个 files 集合。在通过文件输入字段选择了一或多个文件时,files 集合中将包含一组 File 对象,每个 File 对象对应若一个文件。每个 File 对象都有下列只读属性。

  • name:本地文件系统中的文件名。
  • size:文件的字节大小。
  • type:字符串,文件的 MIME 类型。
  • lastModifiedDate:字符串,文件上一次被修改的时间(只有 Chrome 实现了这个属性)。

举个例子,通过侦听 change 事件并读取 files 集合就可以知道选择的每个文件的信息:

window.onload = function(){
    var filesList = document.getElementById("files-list");
    EventUtil.addHandler(filesList, "change", function(event){
        var info = "",
            output = document.getElementById("output"),
            files = EventUtil.getTarget(event).files,
            i = 0,
            len = files.length;

        while (i < len){
            info += files[i].name + " (" + files[i].type + ", " + files[i].size + " bytes)<br>";
            i++;
        }

        output.innerHTML = info;
    });
};

上面的例子只是简单读取了文件的信息, 但是File API还能做得更多, FileReader能读取文件中的数据.

FileReader类型

FileReader 类型实现的是一种异步文件读取机制。可以把FileReader 想象成XMLHttpRequest, 区别只是它读取的是文件系统,而不是远程服务器。为了读取文件中的数据,FileReader 提供了如下几个方法。

  • readAsText(file,encoding):以纯文本形式读取文件,将读取到的文本保存在 result 属性中。第二个参数用于指定编码类型,是可选的。
  • readAsDataURL(file):读取文件并将文件以数据 URI 的形式保存在 result 属性中。
  • readAsBinaryString(file):读取文件并将一个字符串保存在 result 属性中,字符串中的每个字符表示一字节。
  • readAsArrayBuffer(file) :读取文件并将一个包含文件内容的 ArrayBuffer 保存在result 属性中。

我们来看这个例子:

<!DOCTYPE html>
<html>
<head>
  <title>File API Example</title>
  <script src="EventUtil.js"></script>
</head>
<body>
  <p>This page is a demonstration of the File API. This works in the latest versions of all major browsers, but you may need to place this file on a web server to get it to work.</p>
  <p>Select a file below.</p>
  <input type="file" id="files-list">
  <script>
    window.onload = function(){

      var filesList = document.getElementById("files-list");
      EventUtil.addHandler(filesList, "change", function(event){
        var info = "",
        output = document.getElementById("output"),
        progress = document.getElementById("progress"),
        files = EventUtil.getTarget(event).files,
        type = "default",
        reader = new FileReader();

        if (/image/.test(files[0].type)){
          reader.readAsDataURL(files[0]);
          type = "image";
        } else {
          reader.readAsText(files[0]);
          type = "text";
        }

        reader.onerror = function(){
          output.innerHTML = "Could not read file, error code is " + reader.error.code;
        };

        reader.onprogress = function(event){
          if (event.lengthComputable){
            progress.innerHTML = event.loaded + "/" + event.total;
          }
        };

        reader.onload = function(){

          var html = "";

          switch(type){
            case "image":
            html = "<img src=\"" + reader.result + "\">";
            break;
            case "text":
            html = reader.result;
            break;

          }
          output.innerHTML = html;
        };
      });
    };

  </script>
  <div id="progress"></div>
  <pre id="output"></pre>
</body>
</html>

读取部分内容

略, 使用场景比较少.

对象URL

对象 URL 也被称为 blob URL,指的是引用保存在 File 或 Blob 中数据的 URL。使用对象 URL 的好处是可以不必把文件内容读取到 JavaScript 中而直接使用文件内容。为此,只要在需要文件内容的地方提供对象 URL 即可。

使用方法:

window.URL.createObjectURL(blob);        //传入file对象或者blob

当我们用第三方域名的图片(绝对地址)再canvas.toDataURL导出base64的时候, 浏览器会提示canvas被污染. 虽然我们可以对图片设置crossOrigin属性, 但还是存在兼容问题.

此时利用window.URL.createObjectURL就能解决上述问题:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <img id="demo">
  <script>
    var imgsrc = 'https://thinkerchan.com/images/avatar.jpg';

    function getBase64(img){
      var can = document.createElement('canvas');
      can.width = 200;
      can.height = 200;
      var ctx= can.getContext('2d');
      ctx.drawImage(img,0,0,200,200);
      var base64 = can.toDataURL("image/jpeg",0.6);
      console.log(base64)
      return base64;
    }

    function normalImg(src){
      var img = new Image();
      img.src =src;
      img.crossOrigin = '';
      img.onload = function(){
        demo.src = getBase64(img);
      }
    }

    normalImg(imgsrc); // safari报错


    function getImageBlob(url, cb) {
      var xhr          = new XMLHttpRequest();
      xhr.open("get", url, true);
      xhr.responseType = "blob";
      xhr.onload       = function() {
        if (xhr.status == 200) {
          cb &&cb(xhr.response);
        }
      };
      xhr.send();
    }

    getImageBlob(imgsrc, function(res){
      var src = window.URL.createObjectURL(res);  //利用这个能完美解决报错
      var img = new Image();
      img.src =src
      img.onload = function(){
        demo.src = getBase64(img);
      }
    })
  </script>
</body>
</html>

读取拖放的文件

<!DOCTYPE html>
<html>
<head>
  <title>File API Example</title>
  <script src="EventUtil.js"></script>
</head>
<body>
  <p>This page is a demonstration of the File API with Drag and Drop. This works in the latest versions of all major browsers, but you may need to place this file on a web server to get it to work.</p>
  <div id="droptarget" style="width: 500px; height: 200px; background: silver">
    Drop some files here
  </div>
  <script>
    window.onload = function(){
      var droptarget = document.getElementById("droptarget");
      function handleEvent(event){
        var info = "",
          output = document.getElementById("output"),
          files, i, len;
        EventUtil.preventDefault(event);
        if (event.type == "drop"){
          files = event.dataTransfer.files;
          i = 0;
          len = files.length;
          while (i < len){
            info += files[i].name + " (" + files[i].type + ", " + files[i].size + " bytes)<br>";
            i++;
          }
          output.innerHTML = info;
        }
      }
      EventUtil.addHandler(droptarget, "dragenter", handleEvent);
      EventUtil.addHandler(droptarget, "dragover", handleEvent);
      EventUtil.addHandler(droptarget, "drop", handleEvent);
    };
  </script>
  <pre id="output"></pre>
</body>
</html>

使用XHR上传文件

通过 File API 能够访问到文件内容,利用这一点就可以通过 XHR 直接把文件上传到服务器。当然啦,把文件内容放到 send()方法中,再通过 POST 请求,的确很容易就能实现上传。但这样做传递的是文件内容,因而服务器端必须收集提交的内容,然后再把它们保存到另一个文件中。

其实,更好的做法是以表单提交的方式来上传文件。这样使用 FormData 类型就很容易做到了(第 21 章介绍过 FormData)。首先,要创建一个 FormData对象,通过它调用 append()方法并传入相应的 File 对象作为参数。然后,再把 FormData 对象传递给 XHR 的 send()方法,结果与通过表单上传一模一样:

if (event.type == "drop"){
  data = new FormData();
    files = event.dataTransfer.files;
    i = 0;
    len = files.length;
    while (i < len){
        data.append("file" + i, files[i]);
        i++;
    }
    xhr = new XMLHttpRequest();
    xhr.open("post", "server.php", true);
    xhr.onreadystatechange = function(){
        if (xhr.readyState == 4){
            alert(xhr.responseText);
        }
    };
    xhr.send(data);
}

Web计时

略, 性能优化的时候 再讲这个

WebWorkers

略, 另开一篇文章讲解


本章完