preloader.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars
  2. const loadXHR = function (resolve, reject, file, tracker, attempts) {
  3. const xhr = new XMLHttpRequest();
  4. tracker[file] = {
  5. total: 0,
  6. loaded: 0,
  7. final: false,
  8. };
  9. xhr.onerror = function () {
  10. if (attempts <= 1) {
  11. reject(new Error(`Failed loading file '${file}'`));
  12. } else {
  13. setTimeout(function () {
  14. loadXHR(resolve, reject, file, tracker, attempts - 1);
  15. }, 1000);
  16. }
  17. };
  18. xhr.onabort = function () {
  19. tracker[file].final = true;
  20. reject(new Error(`Loading file '${file}' was aborted.`));
  21. };
  22. xhr.onloadstart = function (ev) {
  23. tracker[file].total = ev.total;
  24. tracker[file].loaded = ev.loaded;
  25. };
  26. xhr.onprogress = function (ev) {
  27. tracker[file].loaded = ev.loaded;
  28. tracker[file].total = ev.total;
  29. };
  30. xhr.onload = function () {
  31. if (xhr.status >= 400) {
  32. if (xhr.status < 500 || attempts <= 1) {
  33. reject(new Error(`Failed loading file '${file}': ${xhr.statusText}`));
  34. xhr.abort();
  35. } else {
  36. setTimeout(function () {
  37. loadXHR(resolve, reject, file, tracker, attempts - 1);
  38. }, 1000);
  39. }
  40. } else {
  41. tracker[file].final = true;
  42. resolve(xhr);
  43. }
  44. };
  45. // Make request.
  46. xhr.open('GET', file);
  47. if (!file.endsWith('.js')) {
  48. xhr.responseType = 'arraybuffer';
  49. }
  50. xhr.send();
  51. };
  52. const DOWNLOAD_ATTEMPTS_MAX = 4;
  53. const loadingFiles = {};
  54. const lastProgress = { loaded: 0, total: 0 };
  55. let progressFunc = null;
  56. const animateProgress = function () {
  57. let loaded = 0;
  58. let total = 0;
  59. let totalIsValid = true;
  60. let progressIsFinal = true;
  61. Object.keys(loadingFiles).forEach(function (file) {
  62. const stat = loadingFiles[file];
  63. if (!stat.final) {
  64. progressIsFinal = false;
  65. }
  66. if (!totalIsValid || stat.total === 0) {
  67. totalIsValid = false;
  68. total = 0;
  69. } else {
  70. total += stat.total;
  71. }
  72. loaded += stat.loaded;
  73. });
  74. if (loaded !== lastProgress.loaded || total !== lastProgress.total) {
  75. lastProgress.loaded = loaded;
  76. lastProgress.total = total;
  77. if (typeof progressFunc === 'function') {
  78. progressFunc(loaded, total);
  79. }
  80. }
  81. if (!progressIsFinal) {
  82. requestAnimationFrame(animateProgress);
  83. }
  84. };
  85. this.animateProgress = animateProgress;
  86. this.setProgressFunc = function (callback) {
  87. progressFunc = callback;
  88. };
  89. this.loadPromise = function (file) {
  90. return new Promise(function (resolve, reject) {
  91. loadXHR(resolve, reject, file, loadingFiles, DOWNLOAD_ATTEMPTS_MAX);
  92. });
  93. };
  94. this.preloadedFiles = [];
  95. this.preload = function (pathOrBuffer, destPath) {
  96. let buffer = null;
  97. if (typeof pathOrBuffer === 'string') {
  98. const me = this;
  99. return this.loadPromise(pathOrBuffer).then(function (xhr) {
  100. me.preloadedFiles.push({
  101. path: destPath || pathOrBuffer,
  102. buffer: xhr.response,
  103. });
  104. return Promise.resolve();
  105. });
  106. } else if (pathOrBuffer instanceof ArrayBuffer) {
  107. buffer = new Uint8Array(pathOrBuffer);
  108. } else if (ArrayBuffer.isView(pathOrBuffer)) {
  109. buffer = new Uint8Array(pathOrBuffer.buffer);
  110. }
  111. if (buffer) {
  112. this.preloadedFiles.push({
  113. path: destPath,
  114. buffer: pathOrBuffer,
  115. });
  116. return Promise.resolve();
  117. }
  118. return Promise.reject(new Error('Invalid object for preloading'));
  119. };
  120. };