如何将 FileSaver.js 与 canvas 一起使用

how to use FileSaver.js with canvas

关于如何使用 FileSaver.js 保存 svg 的任何详细示例 canvas?

我按照 https://github.com/eligrey/FileSaver.js 上的 canvas 示例 但是我在第 83 行看到一个 "undefined is not a function",这是一个空行。

查看我的示例代码...

<!doctype html>

<meta charset="utf-8">

<link rel="stylesheet" href="dagre_example.css">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="./dagre-d3.js"></script>
<script src="./Blob.js"></script>
<script src="./canvas-toBlob.js"></script>
<script src="./FileSaver.js"></script>


<h1>Dagre D3 Demo: Sentence Tokenization</h1>



<svg id="svg-canvas" width=960 height=600></svg>


<script id="js">
// Create the input graph
var g = new dagreD3.graphlib.Graph()
  .setGraph({})
  .setDefaultEdgeLabel(function() { return {}; });

// Here we"re setting nodeclass, which is used by our custom drawNodes function
// below.
g.setNode(0,  { label: "TOP",       class: "type-TOP" });
g.setNode(1,  { label: "S",         class: "type-S" });
g.setNode(2,  { label: "NP",        class: "type-NP" });
g.setNode(3,  { label: "DT",        class: "type-DT" });
g.setNode(4,  { label: "This",      class: "type-TK" });
g.setNode(5,  { label: "VP",        class: "type-VP" });
g.setNode(6,  { label: "VBZ",       class: "type-VBZ" });
g.setNode(7,  { label: "is",        class: "type-TK" });
g.setNode(8,  { label: "NP",        class: "type-NP" });
g.setNode(9,  { label: "DT",        class: "type-DT" });
g.setNode(10, { label: "an",        class: "type-TK" });
g.setNode(11, { label: "NN",        class: "type-NN" });
g.setNode(12, { label: "example",   class: "type-TK" });
g.setNode(13, { label: ".",         class: "type-." });
g.setNode(14, { label: "sentence",  class: "type-TK" });

g.nodes().forEach(function(v) {
  var node = g.node(v);
  // Round the corners of the nodes
  node.rx = node.ry = 5;
});

// Set up edges, no special attributes.
g.setEdge(3, 4);
g.setEdge(2, 3);
g.setEdge(1, 2);
g.setEdge(6, 7);
g.setEdge(5, 6);
g.setEdge(9, 10);
g.setEdge(8, 9);
g.setEdge(11,12);
g.setEdge(8, 11);
g.setEdge(5, 8);
g.setEdge(1, 5);
g.setEdge(13,14);
g.setEdge(1, 13);
g.setEdge(0, 1)

// Create the renderer
var render = new dagreD3.render();

// Set up an SVG group so that we can translate the final graph.
var svg = d3.select("svg"),
    svgGroup = svg.append("g");

// Run the renderer. This is what draws the final graph.
render(d3.select("svg g"), g);

// Center the graph
var xCenterOffset = (svg.attr("width") - g.graph().width) / 2;
svgGroup.attr("transform", "translate(" + xCenterOffset + ", 20)");
svg.attr("height", g.graph().height + 40);


// Convert to image

var canvas = document.getElementById("svg-canvas"), ctx = canvas.getContext("2d");

// draw to canvas...
canvas.toBlob(function(blob) {
    saveAs(blob, "prettyimage.png");
});

</script>

这是一个使用 FileSaver 在本地保存的工作示例:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");

var img=new Image()
img.onload=function(){
  ctx.fillStyle="red";
  ctx.drawImage(img,0,0);
  ctx.fillRect(100,100,50,30);
}
img.crossOrigin="anonymous";
img.src="https://dl.dropboxusercontent.com/u/139992952/Whosebug/ship.png";


$("#save").click(function(){
  canvas.toBlob(function(blob){ saveAs(blob,"temp4.png"); });
});


//////////////////////////////////////
// FileSaver scripts
//////////////////////////////////////

/* FileSaver.js
             * A saveAs() FileSaver implementation.
             * 2014-08-29
             *
             * By Eli Grey, http://eligrey.com
             * License: X11/MIT
             *   See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
             */

/*global self */
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */

/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */

var saveAs = saveAs
// IE 10+ (native saveAs)
|| (typeof navigator !== "undefined" &&
    navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
// Everyone else
|| (function(view) {
  "use strict";
  // IE <10 is explicitly unsupported
  if (typeof navigator !== "undefined" &&
      /MSIE [1-9]\./.test(navigator.userAgent)) {
    return;
  }
  var
  doc = view.document
  // only get URL when necessary in case Blob.js hasn't overridden it yet
  , get_URL = function() {
    return view.URL || view.webkitURL || view;
  }
  , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
  , can_use_save_link = "download" in save_link
  , click = function(node) {
    var event = doc.createEvent("MouseEvents");
    event.initMouseEvent(
      "click", true, false, view, 0, 0, 0, 0, 0
      , false, false, false, false, 0, null
    );
    node.dispatchEvent(event);
  }
  , webkit_req_fs = view.webkitRequestFileSystem
  , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
  , throw_outside = function(ex) {
    (view.setImmediate || view.setTimeout)(function() {
      throw ex;
    }, 0);
  }
  , force_saveable_type = "application/octet-stream"
  , fs_min_size = 0
  // See https://code.google.com/p/chromium/issues/detail?id=375297#c7 for
  // the reasoning behind the timeout and revocation flow
  , arbitrary_revoke_timeout = 10
  , revoke = function(file) {
    var revoker = function() {
      if (typeof file === "string") { // file is an object URL
        get_URL().revokeObjectURL(file);
      } else { // file is a File
        file.remove();
      }
    };
    if (view.chrome) {
      revoker();
    } else {
      setTimeout(revoker, arbitrary_revoke_timeout);
    }
  }
  , dispatch = function(filesaver, event_types, event) {
    event_types = [].concat(event_types);
    var i = event_types.length;
    while (i--) {
      var listener = filesaver["on" + event_types[i]];
      if (typeof listener === "function") {
        try {
          listener.call(filesaver, event || filesaver);
        } catch (ex) {
          throw_outside(ex);
        }
      }
    }
  }
  , FileSaver = function(blob, name) {
    // First try a.download, then web filesystem, then object URLs
    var
    filesaver = this
    , type = blob.type
    , blob_changed = false
    , object_url
    , target_view
    , dispatch_all = function() {
      dispatch(filesaver, "writestart progress write writeend".split(" "));
    }
    // on any filesys errors revert to saving with object URLs
    , fs_error = function() {
      // don't create more object URLs than needed
      if (blob_changed || !object_url) {
        object_url = get_URL().createObjectURL(blob);
      }
      if (target_view) {
        target_view.location.href = object_url;
      } else {
        var new_tab = view.open(object_url, "_blank");
        if (new_tab == undefined && typeof safari !== "undefined") {
          //Apple do not allow window.open, see http://bit.ly/1kZffRI
          view.location.href = object_url
        }
      }
      filesaver.readyState = filesaver.DONE;
      dispatch_all();
      revoke(object_url);
    }
    , abortable = function(func) {
      return function() {
        if (filesaver.readyState !== filesaver.DONE) {
          return func.apply(this, arguments);
        }
      };
    }
    , create_if_not_found = {create: true, exclusive: false}
    , slice
    ;
    filesaver.readyState = filesaver.INIT;
    if (!name) {
      name = "download";
    }
    if (can_use_save_link) {
      object_url = get_URL().createObjectURL(blob);
      save_link.href = object_url;
      save_link.download = name;
      click(save_link);
      filesaver.readyState = filesaver.DONE;
      dispatch_all();
      revoke(object_url);
      return;
    }
    // Object and web filesystem URLs have a problem saving in Google Chrome when
    // viewed in a tab, so I force save with application/octet-stream
    // http://code.google.com/p/chromium/issues/detail?id=91158
    // Update: Google errantly closed 91158, I submitted it again:
    // https://code.google.com/p/chromium/issues/detail?id=389642
    if (view.chrome && type && type !== force_saveable_type) {
      slice = blob.slice || blob.webkitSlice;
      blob = slice.call(blob, 0, blob.size, force_saveable_type);
      blob_changed = true;
    }
    // Since I can't be sure that the guessed media type will trigger a download
    // in WebKit, I append .download to the filename.
    // https://bugs.webkit.org/show_bug.cgi?id=65440
    if (webkit_req_fs && name !== "download") {
      name += ".download";
    }
    if (type === force_saveable_type || webkit_req_fs) {
      target_view = view;
    }
    if (!req_fs) {
      fs_error();
      return;
    }
    fs_min_size += blob.size;
    req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
      fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
        var save = function() {
          dir.getFile(name, create_if_not_found, abortable(function(file) {
            file.createWriter(abortable(function(writer) {
              writer.onwriteend = function(event) {
                target_view.location.href = file.toURL();
                filesaver.readyState = filesaver.DONE;
                dispatch(filesaver, "writeend", event);
                revoke(file);
              };
              writer.onerror = function() {
                var error = writer.error;
                if (error.code !== error.ABORT_ERR) {
                  fs_error();
                }
              };
              "writestart progress write abort".split(" ").forEach(function(event) {
                writer["on" + event] = filesaver["on" + event];
              });
              writer.write(blob);
              filesaver.abort = function() {
                writer.abort();
                filesaver.readyState = filesaver.DONE;
              };
              filesaver.readyState = filesaver.WRITING;
            }), fs_error);
          }), fs_error);
        };
        dir.getFile(name, {create: false}, abortable(function(file) {
          // delete file if it already exists
          file.remove();
          save();
        }), abortable(function(ex) {
          if (ex.code === ex.NOT_FOUND_ERR) {
            save();
          } else {
            fs_error();
          }
        }));
      }), fs_error);
    }), fs_error);
  }
  , FS_proto = FileSaver.prototype
  , saveAs = function(blob, name) {
    return new FileSaver(blob, name);
  }
  ;
  FS_proto.abort = function() {
    var filesaver = this;
    filesaver.readyState = filesaver.DONE;
    dispatch(filesaver, "abort");
  };
  FS_proto.readyState = FS_proto.INIT = 0;
  FS_proto.WRITING = 1;
  FS_proto.DONE = 2;

  FS_proto.error =
    FS_proto.onwritestart =
    FS_proto.onprogress =
    FS_proto.onwrite =
    FS_proto.onabort =
    FS_proto.onerror =
    FS_proto.onwriteend =
    null;

  return saveAs;
}(
  typeof self !== "undefined" && self
  || typeof window !== "undefined" && window
  || this.content
));
// `self` is undefined in Firefox for Android content script context
// while `this` is nsIContentFrameMessageManager
// with an attribute `content` that corresponds to the window

if (typeof module !== "undefined" && module !== null) {
  module.exports = saveAs;
} else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
  define([], function() {
    return saveAs;
  });
}

/* canvas-toBlob.js
             * A canvas.toBlob() implementation.
             * 2013-12-27
             * 
             * By Eli Grey, http://eligrey.com and Devin Samarin, https://github.com/eboyjr
             * License: X11/MIT
             *   See https://github.com/eligrey/canvas-toBlob.js/blob/master/LICENSE.md
             */

/*global self */
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
              plusplus: true */

/*! @source http://purl.eligrey.com/github/canvas-toBlob.js/blob/master/canvas-toBlob.js */

(function(view) {
  "use strict";
  var
  Uint8Array = view.Uint8Array
  , HTMLCanvasElement = view.HTMLCanvasElement
  , canvas_proto = HTMLCanvasElement && HTMLCanvasElement.prototype
  , is_base64_regex = /\s*;\s*base64\s*(?:;|$)/i
  , to_data_url = "toDataURL"
  , base64_ranks
  , decode_base64 = function(base64) {
    var
    len = base64.length
    , buffer = new Uint8Array(len / 4 * 3 | 0)
    , i = 0
    , outptr = 0
    , last = [0, 0]
    , state = 0
    , save = 0
    , rank
    , code
    , undef
    ;
    while (len--) {
      code = base64.charCodeAt(i++);
      rank = base64_ranks[code-43];
      if (rank !== 255 && rank !== undef) {
        last[1] = last[0];
        last[0] = code;
        save = (save << 6) | rank;
        state++;
        if (state === 4) {
          buffer[outptr++] = save >>> 16;
          if (last[1] !== 61 /* padding character */) {
            buffer[outptr++] = save >>> 8;
          }
          if (last[0] !== 61 /* padding character */) {
            buffer[outptr++] = save;
          }
          state = 0;
        }
      }
    }
    // 2/3 chance there's going to be some null bytes at the end, but that
    // doesn't really matter with most image formats.
    // If it somehow matters for you, truncate the buffer up outptr.
    return buffer;
  }
  ;
  if (Uint8Array) {
    base64_ranks = new Uint8Array([
      62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1
      , -1, -1,  0, -1, -1, -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9
      , 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25
      , -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35
      , 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
    ]);
  }
  if (HTMLCanvasElement && !canvas_proto.toBlob) {
    canvas_proto.toBlob = function(callback, type /*, ...args*/) {
      if (!type) {
        type = "image/png";
      } if (this.mozGetAsFile) {
        callback(this.mozGetAsFile("canvas", type));
        return;
      } if (this.msToBlob && /^\s*image\/png\s*(?:$|;)/i.test(type)) {
        callback(this.msToBlob());
        return;
      }

      var
      args = Array.prototype.slice.call(arguments, 1)
      , dataURI = this[to_data_url].apply(this, args)
      , header_end = dataURI.indexOf(",")
      , data = dataURI.substring(header_end + 1)
      , is_base64 = is_base64_regex.test(dataURI.substring(0, header_end))
      , blob
      ;
      if (Blob.fake) {
        // no reason to decode a data: URI that's just going to become a data URI again
        blob = new Blob
        if (is_base64) {
          blob.encoding = "base64";
        } else {
          blob.encoding = "URI";
        }
        blob.data = data;
        blob.size = data.length;
      } else if (Uint8Array) {
        if (is_base64) {
          blob = new Blob([decode_base64(data)], {type: type});
        } else {
          blob = new Blob([decodeURIComponent(data)], {type: type});
        }
      }
      callback(blob);
    };

    if (canvas_proto.toDataURLHD) {
      canvas_proto.toBlobHD = function() {
        to_data_url = "toDataURLHD";
        var blob = this.toBlob();
        to_data_url = "toDataURL";
        return blob;
      }
    } else {
      canvas_proto.toBlobHD = canvas_proto.toBlob;
    }
  }
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="save">Save</button><br>
<canvas id="canvas" width=300 height=300></canvas>