├── .vscode └── settings.json ├── index.js ├── .gitignore ├── src ├── types │ ├── TextItem.js │ ├── JSONItem.js │ ├── AnyItem.js │ ├── JSONPItem.js │ ├── AudioItem.js │ ├── VideoItem.js │ ├── ImageItem.js │ ├── XHRItem.js │ └── AbstractItem.js └── quickLoader.js ├── package.json ├── LICENSE ├── README.md └── dist └── quickLoader.js /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/dist": true 4 | } 5 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/quickLoader') 2 | 3 | require('./src/types/JSONPItem') 4 | require('./src/types/JSONItem') 5 | require('./src/types/TextItem') 6 | require('./src/types/AudioItem') 7 | require('./src/types/VideoItem') 8 | require('./src/types/AnyItem') 9 | require('./src/types/ImageItem') 10 | require('./src/types/XHRItem') 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.pyc 3 | *.pyo 4 | ======= 5 | # Compiled source # 6 | ################### 7 | *.com 8 | *.class 9 | *.dll 10 | *.exe 11 | *.o 12 | *.so 13 | .sass-cache 14 | 15 | 16 | # Packages # 17 | ############ 18 | # it's better to unpack these files and commit the raw source 19 | # git has its own built in compression methods 20 | *.7z 21 | *.dmg 22 | *.gz 23 | *.iso 24 | *.jar 25 | *.rar 26 | *.tar 27 | # Logs and databases # 28 | ###################### 29 | *.log 30 | #*.sql 31 | *.sqlite 32 | 33 | # OS generated files # 34 | ###################### 35 | .DS_Store 36 | .DS_Store? 37 | ._* 38 | .Spotlight-V100 39 | .Trashes 40 | ehthumbs.db 41 | Thumbs.db 42 | 43 | node_modules 44 | *.sublime-project 45 | *.sublime-workspace 46 | -------------------------------------------------------------------------------- /src/types/TextItem.js: -------------------------------------------------------------------------------- 1 | var XHRItem = require('./XHRItem') 2 | var quickLoader = require('../quickLoader') 3 | 4 | var undef 5 | 6 | function TextItem (url, cfg) { 7 | if (!url) return 8 | cfg.responseType = 'text' 9 | _super.constructor.apply(this, arguments) 10 | } 11 | 12 | module.exports = TextItem 13 | TextItem.type = 'text' 14 | TextItem.extensions = ['html', 'txt', 'svg'] 15 | quickLoader.register(TextItem) 16 | 17 | TextItem.retrieve = function () { 18 | return false 19 | } 20 | 21 | var _super = XHRItem.prototype 22 | var _p = TextItem.prototype = new XHRItem() 23 | _p.constructor = TextItem 24 | _p._onLoad = _onLoad 25 | 26 | function _onLoad () { 27 | if (!this.content) { 28 | this.content = this.xmlhttp.responseText; 29 | } 30 | _super._onLoad.apply(this, arguments) 31 | } 32 | -------------------------------------------------------------------------------- /src/types/JSONItem.js: -------------------------------------------------------------------------------- 1 | var TextItem = require('./TextItem') 2 | var quickLoader = require('../quickLoader') 3 | 4 | function JSONItem (url) { 5 | if (!url) return 6 | _super.constructor.apply(this, arguments) 7 | } 8 | 9 | module.exports = JSONItem 10 | JSONItem.type = 'json' 11 | JSONItem.extensions = ['json'] 12 | quickLoader.register(JSONItem) 13 | 14 | JSONItem.retrieve = function () { 15 | return false 16 | } 17 | 18 | var _super = TextItem.prototype 19 | var _p = JSONItem.prototype = new TextItem() 20 | _p.constructor = JSONItem 21 | _p._onLoad = _onLoad 22 | 23 | function _onLoad () { 24 | if (!this.content) { 25 | this.content = window.JSON && window.JSON.parse ? JSON.parse(this.xmlhttp.responseText.toString()) : eval(this.xmlhttp.responseText.toString()) 26 | } 27 | _super._onLoad.call(this) 28 | } 29 | -------------------------------------------------------------------------------- /src/types/AnyItem.js: -------------------------------------------------------------------------------- 1 | var AbstractItem = require('./AbstractItem') 2 | var quickLoader = require('../quickLoader') 3 | 4 | function AnyItem (url, cfg) { 5 | if (!url) return 6 | _super.constructor.call(this, url, cfg) 7 | 8 | if (!this.loadFunc && console) { 9 | console[console.error || console.log]('require loadFunc in the config object.') 10 | } 11 | } 12 | 13 | module.exports = AnyItem 14 | AnyItem.type = 'any' 15 | AnyItem.extensions = [] 16 | quickLoader.register(AnyItem) 17 | 18 | AnyItem.retrieve = function () { 19 | return false 20 | } 21 | 22 | var _super = AbstractItem.prototype 23 | var _p = AnyItem.prototype = new AbstractItem() 24 | _p.constructor = AnyItem 25 | 26 | _p.load = load 27 | 28 | function load () { 29 | var self = this 30 | 31 | this.loadFunc(this.url, function (content) { 32 | self.content = content 33 | _super._onLoad.call(self) 34 | }, this.loadingSignal) 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quick-loader", 3 | "version": "0.1.19", 4 | "description": "quick-loader is an asset loader that loads everything", 5 | "main": "index.js", 6 | "author": "Edan Kwan", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/edankwan/quick-loader.git" 11 | }, 12 | "keywords": [ 13 | "loader", 14 | "preload", 15 | "preloader", 16 | "asset", 17 | "assets" 18 | ], 19 | "bugs": { 20 | "url": "https://github.com/edankwan/quick-loader/issues" 21 | }, 22 | "homepage": "https://github.com/edankwan/quick-loader", 23 | "scripts": { 24 | "build": "browserify index.js --standalone quickLoader > ./dist/quickLoader.js" 25 | }, 26 | "devDependencies": { 27 | "browserify": "~17.0.0" 28 | }, 29 | "dependencies": { 30 | "computed-style": "~0.3.0", 31 | "min-signal": "~0.0.8", 32 | "mout": "~1.2.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2021 Edan Kwan 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/types/JSONPItem.js: -------------------------------------------------------------------------------- 1 | var AbstractItem = require('./AbstractItem') 2 | var quickLoader = require('../quickLoader') 3 | 4 | function __generateFuncName () { 5 | return '_jsonp' + new Date().getTime() + ~~(Math.random() * 100000000) 6 | } 7 | 8 | function JSONPItem (url) { 9 | if (!url) return 10 | _super.constructor.apply(this, arguments) 11 | } 12 | 13 | module.exports = JSONPItem 14 | JSONPItem.type = 'jsonp' 15 | JSONPItem.extensions = [] 16 | quickLoader.register(JSONPItem) 17 | 18 | JSONPItem.retrieve = function (target) { 19 | if ((typeof target === 'string') && (target.indexOf('=') > -1)) { 20 | return [target] 21 | } 22 | return false 23 | } 24 | 25 | var _super = AbstractItem.prototype 26 | var _p = JSONPItem.prototype = new AbstractItem() 27 | _p.constructor = JSONPItem 28 | _p.load = load 29 | 30 | function load (callback) { 31 | _super.load.apply(this, arguments) 32 | var self = this 33 | var lastEqualIndex = this.url.lastIndexOf('=') + 1 34 | var urlPrefix = this.url.substr(0, lastEqualIndex) 35 | var funcName = this.url.substr(lastEqualIndex) 36 | if (funcName.length === 0) { 37 | funcName = __generateFuncName() 38 | this.jsonpCallback = callback 39 | } else { 40 | this.jsonpCallback = this.jsonpCallback || window[funcName] 41 | } 42 | 43 | window[funcName] = function (data) { 44 | if (script.parentNode) script.parentNode.removeChild(script) 45 | self.content = data 46 | self._onLoad() 47 | } 48 | var script = document.createElement('script') 49 | script.type = 'text/javascript' 50 | script.src = urlPrefix + funcName 51 | document.getElementsByTagName('head')[0].appendChild(script) 52 | } 53 | -------------------------------------------------------------------------------- /src/types/AudioItem.js: -------------------------------------------------------------------------------- 1 | var AbstractItem = require('./AbstractItem') 2 | var quickLoader = require('../quickLoader') 3 | 4 | var undef 5 | 6 | function AudioItem (url, cfg) { 7 | if (!url) return 8 | this.loadThrough = !cfg || cfg.loadThrough === undef ? true : cfg.loadThrough 9 | _super.constructor.apply(this, arguments) 10 | try { 11 | this.content = this.content || new Audio() 12 | } catch (e) { 13 | this.content = this.content || document.createElement('audio') 14 | } 15 | if (this.crossOrigin) { 16 | this.content.crossOrigin = this.crossOrigin 17 | } 18 | } 19 | 20 | module.exports = AudioItem 21 | AudioItem.type = 'audio' 22 | AudioItem.extensions = ['mp3', 'ogg'] 23 | quickLoader.register(AudioItem) 24 | 25 | AudioItem.retrieve = function (target) { 26 | // TODO add dom audios support 27 | return false 28 | } 29 | 30 | var _super = AbstractItem.prototype 31 | var _p = AudioItem.prototype = new AbstractItem() 32 | _p.constructor = AudioItem 33 | _p.load = load 34 | _p._onLoad = _onLoad 35 | 36 | function load () { 37 | _super.load.apply(this, arguments) 38 | var self = this 39 | var audio = self.content 40 | audio.src = this.url 41 | if (this.loadThrough) { 42 | audio.addEventListener('canplaythrough', this.boundOnLoad, false) 43 | } else { 44 | audio.addEventListener('canplay', this.boundOnLoad, false) 45 | } 46 | audio.load() 47 | } 48 | 49 | function _onLoad () { 50 | this.content.removeEventListener('canplaythrough', this.boundOnLoad, false) 51 | this.content.removeEventListener('canplay', this.boundOnLoad, false) 52 | if (this.isLoaded) { 53 | return 54 | } 55 | _super._onLoad.call(this) 56 | } 57 | -------------------------------------------------------------------------------- /src/types/VideoItem.js: -------------------------------------------------------------------------------- 1 | var AbstractItem = require('./AbstractItem') 2 | var quickLoader = require('../quickLoader') 3 | 4 | var undef 5 | 6 | function VideoItem (url, cfg) { 7 | if (!url) { 8 | return 9 | } 10 | this.loadThrough = !cfg || cfg.loadThrough === undef ? true : cfg.loadThrough 11 | _super.constructor.apply(this, arguments) 12 | try { 13 | this.content = this.content || new Video() 14 | } catch (e) { 15 | this.content = this.content || document.createElement('video') 16 | } 17 | if (this.crossOrigin) { 18 | this.content.crossOrigin = this.crossOrigin 19 | } 20 | } 21 | 22 | module.exports = VideoItem 23 | VideoItem.type = 'video' 24 | VideoItem.extensions = ['mp4', 'webm', 'ogv'] 25 | quickLoader.register(VideoItem) 26 | 27 | VideoItem.retrieve = function (target) { 28 | // TODO add dom videos support 29 | return false 30 | } 31 | 32 | var _super = AbstractItem.prototype 33 | var _p = VideoItem.prototype = new AbstractItem() 34 | _p.constructor = VideoItem 35 | _p.load = load 36 | _p._onLoad = _onLoad 37 | 38 | function load () { 39 | _super.load.apply(this, arguments) 40 | var video = this.content 41 | video.preload = 'auto' 42 | video.src = this.url 43 | if (this.loadThrough) { 44 | video.addEventListener('canplaythrough', this.boundOnLoad, false) 45 | } else { 46 | video.addEventListener('canplay', this.boundOnLoad, false) 47 | } 48 | video.load() 49 | } 50 | 51 | function _onLoad () { 52 | this.content.removeEventListener('canplaythrough', this.boundOnLoad) 53 | this.content.removeEventListener('canplay', this.boundOnLoad) 54 | if (this.isLoaded) { 55 | return 56 | } 57 | _super._onLoad.call(this) 58 | } 59 | -------------------------------------------------------------------------------- /src/types/ImageItem.js: -------------------------------------------------------------------------------- 1 | var AbstractItem = require('./AbstractItem') 2 | var computedStyle = require('computed-style') 3 | var quickLoader = require('../quickLoader') 4 | 5 | function ImageItem (url, cfg) { 6 | if (!url) return 7 | _super.constructor.apply(this, arguments) 8 | this.content = this.content || new Image() 9 | if (this.crossOrigin) { 10 | this.content.crossOrigin = this.crossOrigin 11 | } 12 | } 13 | 14 | module.exports = ImageItem 15 | var _super = AbstractItem.prototype 16 | var _p = ImageItem.prototype = new AbstractItem() 17 | _p.constructor = ImageItem 18 | _p.load = load 19 | _p._onLoad = _onLoad 20 | 21 | ImageItem.retrieve = function (target) { 22 | if (target.nodeType && target.style) { 23 | var list = [] 24 | 25 | if ((target.nodeName.toLowerCase() === 'img') && (target.src.indexOf(';') < 0)) { 26 | list.push(target.src) 27 | } 28 | 29 | computedStyle(target, 'background-image').replace(/s?url\(\s*?['"]?([^;]*?)['"]?\s*?\)/g, function (a, b) { 30 | list.push(b) 31 | }) 32 | 33 | var i = list.length 34 | while (i--) { 35 | if (!_isNotData(list[i])) { 36 | list.splice(i, 1) 37 | } 38 | } 39 | return list.length ? list : false 40 | } else if (typeof target === 'string') { 41 | return [target] 42 | } else { 43 | return false 44 | } 45 | } 46 | 47 | ImageItem.type = 'image' 48 | ImageItem.extensions = ['jpg', 'gif', 'png'] 49 | quickLoader.register(ImageItem) 50 | 51 | function load () { 52 | _super.load.apply(this, arguments) 53 | var img = this.content 54 | img.onload = this.boundOnLoad 55 | img.src = this.url 56 | } 57 | 58 | function _onLoad () { 59 | delete this.content.onload 60 | this.width = this.content.width 61 | this.height = this.content.height 62 | _super._onLoad.call(this) 63 | } 64 | 65 | function _isNotData (url) { 66 | return url.indexOf('data:') !== 0 67 | } 68 | -------------------------------------------------------------------------------- /src/types/XHRItem.js: -------------------------------------------------------------------------------- 1 | var AbstractItem = require('./AbstractItem') 2 | var quickLoader = require('../quickLoader') 3 | 4 | var undef 5 | 6 | var IS_SUPPORT_XML_HTTP_REQUEST = !!window.XMLHttpRequest 7 | 8 | function XHRItem (url) { 9 | if (!url) return 10 | _super.constructor.apply(this, arguments) 11 | this.responseType = this.responseType || '' 12 | this.method = this.method || 'GET' 13 | } 14 | 15 | module.exports = XHRItem 16 | XHRItem.type = 'xhr' 17 | XHRItem.extensions = [] 18 | quickLoader.register(XHRItem) 19 | 20 | XHRItem.retrieve = function () { 21 | return false 22 | } 23 | 24 | var _super = AbstractItem.prototype 25 | var _p = XHRItem.prototype = new AbstractItem() 26 | _p.constructor = XHRItem 27 | _p.load = load 28 | _p._onXmlHttpChange = _onXmlHttpChange 29 | _p._onXmlHttpProgress = _onXmlHttpProgress 30 | _p._onLoad = _onLoad 31 | 32 | function load () { 33 | _super.load.apply(this, arguments) 34 | var self = this 35 | var xmlhttp 36 | 37 | if (IS_SUPPORT_XML_HTTP_REQUEST) { 38 | xmlhttp = this.xmlhttp = new XMLHttpRequest() 39 | } else { 40 | xmlhttp = this.xmlhttp = new ActiveXObject('Microsoft.XMLHTTP') 41 | } 42 | if (this.hasLoading) { 43 | xmlhttp.onprogress = function (evt) { 44 | self._onXmlHttpProgress(evt) 45 | } 46 | } 47 | xmlhttp.onreadystatechange = function () { 48 | self._onXmlHttpChange() 49 | } 50 | xmlhttp.open(this.method, this.url, true) 51 | this.xmlhttp.responseType = this.responseType 52 | 53 | if (IS_SUPPORT_XML_HTTP_REQUEST) { 54 | xmlhttp.send(null) 55 | } else { 56 | xmlhttp.send() 57 | } 58 | } 59 | 60 | function _onXmlHttpProgress (evt) { 61 | this.loadingSignal.dispatch(evt.loaded / evt.total) 62 | } 63 | 64 | function _onXmlHttpChange () { 65 | if (this.xmlhttp.readyState === 4) { 66 | if (this.xmlhttp.status === 200) { 67 | this._onLoad(this.xmlhttp) 68 | } 69 | } 70 | } 71 | 72 | function _onLoad () { 73 | if (!this.content) { 74 | this.content = this.xmlhttp.response 75 | } 76 | this.xmlhttp = undef 77 | _super._onLoad.call(this) 78 | } 79 | -------------------------------------------------------------------------------- /src/types/AbstractItem.js: -------------------------------------------------------------------------------- 1 | var MinSignal = require('min-signal') 2 | var quickLoader = require('../quickLoader') 3 | 4 | function AbstractItem (url, cfg) { 5 | if (!url) return 6 | this.url = url 7 | this.loadedWeight = 0 8 | this.weight = 1 9 | this.postPercent = 0 10 | for (var id in cfg) { 11 | this[id] = cfg[id] 12 | } 13 | 14 | if (!this.type) { 15 | this.type = this.constructor.type 16 | } 17 | 18 | if (this.hasLoading) { 19 | this.loadingSignal = new MinSignal() 20 | this.loadingSignal.add(_onLoading, this) 21 | if (this.onLoading) { 22 | this.loadingSignal.add(this.onLoading) 23 | } 24 | } 25 | 26 | if (this.onPost) { 27 | this.onPostLoadingSignal = new MinSignal() 28 | this.onPostLoadingSignal.add(this._onPostLoading, this) 29 | this.postWeightRatio = this.postWeightRatio || 0.1 30 | } else { 31 | this.postWeightRatio = 0 32 | } 33 | 34 | var self = this 35 | this.boundOnLoad = function () { 36 | self._onLoad() 37 | } 38 | this.onLoaded = new MinSignal() 39 | 40 | quickLoader.addedItems[url] = this 41 | } 42 | 43 | module.exports = AbstractItem 44 | var _p = AbstractItem.prototype 45 | _p.load = load 46 | _p._onLoad = _onLoad 47 | _p._onLoading = _onLoading 48 | _p._onPostLoading = _onPostLoading 49 | _p._onLoadComplete = _onLoadComplete 50 | _p.getCombinedPercent = getCombinedPercent 51 | _p.dispatch = dispatch 52 | 53 | AbstractItem.extensions = [] 54 | 55 | AbstractItem.retrieve = function () { 56 | return false 57 | } 58 | 59 | function load () { 60 | this.isStartLoaded = true 61 | } 62 | 63 | function _onLoad () { 64 | if (this.onPost) { 65 | this.onPost.call(this, this.content, this.onPostLoadingSignal) 66 | } else { 67 | this._onLoadComplete() 68 | } 69 | } 70 | 71 | function _onPostLoading (percent) { 72 | this.postPercent = percent 73 | if (this.hasLoading) { 74 | this.loadingSignal.dispatch(1) 75 | } 76 | if (percent === 1) { 77 | this._onLoadComplete() 78 | } 79 | } 80 | 81 | function _onLoadComplete () { 82 | this.isLoaded = true 83 | this.loadedWeight = this.weight 84 | quickLoader.loadedItems[this.url] = this 85 | this.onLoaded.dispatch(this.content) 86 | } 87 | 88 | function getCombinedPercent (percent) { 89 | return percent * (1 - this.postWeightRatio) + (this.postWeightRatio * this.postPercent) 90 | } 91 | 92 | function _onLoading (percent) { 93 | this.loadedWeight = this.weight * this.getCombinedPercent(percent) 94 | } 95 | 96 | function dispatch () { 97 | if (this.hasLoading) { 98 | this.loadingSignal.remove() 99 | } 100 | this.onLoaded.dispatch(this.content) 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # quick-loader 2 | 3 | quick-loader is an asset loader that loads everything. Say you have some third party libraries with their own loader modules, you can pipe the loading progress into quick-loader's loading pipeline. It also does basic loading for `image`, `json`, `jsonp`, `text`, `video` and `audio`. 4 | 5 | ## Usage 6 | 7 | ### Simple batch loading 8 | 9 | Add several assets to the loader and get the percent of the batch loading. 10 | 11 | ```js 12 | // assuming you are using CommonJS 13 | var quickLoader = require('quick-loader'); 14 | 15 | // load the asset with certain type 16 | quickLoader.add('1.jpg', {type: 'image'}); 17 | 18 | // or let it guess the type by the url extension 19 | quickLoader.add('2.jpg'); 20 | 21 | // you can also define the weight of the asset which is 1 by default 22 | quickLoader.add('3.jpg', {weight: 2}); 23 | 24 | quickLoader.start(function(percent){ 25 | 26 | // assuming the files are loaded in the same order as above 27 | // it will log 0.25, 0.5, 1.0 28 | console.log(percent); 29 | 30 | if(percent === 1) { 31 | init(); 32 | 33 | // the listener was removed at this point and it 34 | // will not have any stacked async issue so you can 35 | // load something else again. 36 | quickLoader.add(...); 37 | quickLoader.start(...); 38 | } 39 | 40 | }); 41 | 42 | ``` 43 | 44 | ### Individual asset callback 45 | You can add an onLoad callback to an individual asset. 46 | ```js 47 | quickLoader.add('data.json', { 48 | onLoad: function(data) { 49 | console.log(data); 50 | } 51 | }); 52 | quickLoader.add('img.jpg'); 53 | quickLoader.start(...); 54 | 55 | ``` 56 | 57 | ### Load a single item out of the quick-loader pipeline 58 | For all feature that works with Batch loading, it works with individual asset loading as well. Basically all you need is to change the `add()` into `load()` 59 | ```js 60 | quickLoader.load('data.json', { 61 | onLoad: function(data) { 62 | console.log(data); 63 | } 64 | }); 65 | 66 | ``` 67 | 68 | ### Initial content 69 | Sometimes when you add the loading query to the batch loader, you want to have access to the asset instance immediately. This feature only works with asset types: `image`, `video` and `audio` 70 | ```js 71 | var img = quickLoader.add('img.jpg').content; 72 | 73 | quickLoader.start(...); 74 | 75 | ``` 76 | 77 | ### No Cache 78 | Normally after an item is loaded, the content will be stored and if you fetch the same url, it will not download the content again. But you can set `noCache` to true in the item config and bypass this feature. It will remove the reference after the file is loaded. 79 | ```js 80 | quickLoader.load('img.jpg', { 81 | noCache: true 82 | }); 83 | 84 | ``` 85 | 86 | ### Working with third-party library loader like THREE.JS JSON Loader 87 | ```js 88 | quickLoader.load('mesh.json', { 89 | 90 | type: 'any', 91 | 92 | loadFunc: function(url, cb, loadingSignal) { 93 | 94 | var loader = new THREE.JSONLoader(); 95 | 96 | loader.load( url, function( geometry, material ) { 97 | var mesh = new THREE.Mesh( geometry, material); 98 | 99 | // tell quickLoader the item is loaded and 100 | // store the mesh instead as content 101 | cb(mesh); 102 | }); 103 | } 104 | }); 105 | 106 | ``` 107 | 108 | ### Postprocessing 109 | 110 | You can do postprocessing work with `onPost` 111 | 112 | ```js 113 | quickLoader.load('data.json', { 114 | type: 'json', 115 | postWeightRatio: 0.2 // [0-1] default 0.1, the weight ratio to the actual weight 116 | onPost: function(content, postLoadingSignal) { 117 | content.foo = 'bar'; 118 | postLoadingSignal.dispatch(1); // complete 119 | } 120 | }); 121 | ``` 122 | 123 | ### Individual asset preloading 124 | 125 | You can also add listener to the individual asset. This feature only works with asset types `json`, `text` and `any`. 126 | ```js 127 | quickLoader.add('data.json', { 128 | type: 'json', 129 | weight: 5, 130 | hasLoading: true, 131 | onLoading: function(percent){ 132 | console.log(percent); 133 | } 134 | }); 135 | ``` 136 | 137 | ### Individual asset preloading with third-party library 138 | This following example is to show you when you are using `any` asset type, you can do whatever you want. 139 | ```js 140 | quickLoader.add('a_fake_loader', { 141 | type: 'any', 142 | weight: 50, 143 | hasLoading: true, 144 | onLoading: function(percent) { 145 | console.log('loading: ' + ~~(percent * 100) + '%'); 146 | }, 147 | onLoad: function(content) { 148 | // some content here 149 | console.log('loaded: ' + content); 150 | }, 151 | loadFunc: function(url, cb, loadingSignal) { 152 | var count = 0; 153 | var interval = setInterval(function(){ 154 | count++; 155 | loadingSignal.dispatch(count / 10); 156 | if(count == 10) { 157 | clearInterval(interval); 158 | cb('some content here'); 159 | } 160 | }, 100); 161 | } 162 | }); 163 | ``` 164 | 165 | ### Add a chunk of assets 166 | ```js 167 | quickLoader.addChunk(['1.jpg', '2.jpg', '3.jpg'], 'image'); 168 | 169 | // let quick-loader to guess the types 170 | quickLoader.addChunk(['1.jpg', '2.txt', '3.json']); 171 | ``` 172 | 173 | ### Add DOM Images(experimental) 174 | It adds all images through img tag and background images. 175 | ```js 176 | quickLoader.addChunk(document.body.querySelectorAll('*')); 177 | ``` 178 | 179 | ### Multi-batch Loader instances 180 | For some reason if you want to have 2 loaders, you can create a new one like this: 181 | ```js 182 | var quickLoader = require('quick-loader'); 183 | 184 | var batchLoader = quickLoader.create(); 185 | batchLoader.add(...); 186 | 187 | ``` 188 | 189 | ### Cross Origin 190 | ```js 191 | var quickLoader = require('quick-loader'); 192 | 193 | // set for everything cross-origin within a domain 194 | quickLoader.setCrossOrigin('http://mydomain/', 'anonymous') 195 | 196 | // set cross-origin for individual load item 197 | quickLoader.add('http://anotherdomain/image.jpg', { 198 | crossOrigin: 'anonymous' 199 | }) 200 | 201 | ``` 202 | 203 | 204 | ## Installation 205 | Download the standalone version **[HERE](https://raw.githubusercontent.com/edankwan/quick-loader/master/dist/quickLoader.js)** 206 | 207 | `npm install quick-loader` 208 | 209 | 210 | ## License 211 | MIT License -------------------------------------------------------------------------------- /src/quickLoader.js: -------------------------------------------------------------------------------- 1 | var MinSignal = require('min-signal') 2 | 3 | var undef 4 | 5 | function QuickLoader () { 6 | this.isLoading = false 7 | this.totalWeight = 0 8 | this.loadedWeight = 0 9 | this.itemUrls = {} 10 | this.itemList = [] 11 | this.loadingSignal = new MinSignal() 12 | this.crossOriginMap = {} 13 | this.queue = [] 14 | this.activeItems = [] 15 | this.maxActiveItems = 4 16 | } 17 | 18 | var _p = QuickLoader.prototype 19 | _p.addChunk = addChunk 20 | _p.setCrossOrigin = setCrossOrigin 21 | _p.add = add 22 | _p.load = load 23 | _p.start = start 24 | _p.loadNext = loadNext 25 | _p._createItem = _createItem 26 | _p._onLoading = _onLoading 27 | 28 | _p.VERSION = '0.1.17' 29 | _p.register = register 30 | _p.retrieveAll = retrieveAll 31 | _p.retrieve = retrieve 32 | _p.testExtensions = testExtensions 33 | _p.create = create 34 | _p.check = check 35 | 36 | var addedItems = _p.addedItems = {} 37 | var loadedItems = _p.loadedItems = {} 38 | 39 | var ITEM_CLASS_LIST = _p.ITEM_CLASS_LIST = [] 40 | var ITEM_CLASSES = _p.ITEM_CLASSES = {} 41 | 42 | var quickLoader = module.exports = create() 43 | 44 | function setCrossOrigin (domain, value) { 45 | this.crossOriginMap[domain] = value 46 | } 47 | 48 | function addChunk (target, type) { 49 | var i, j, len, itemsLength, retrievedTypeObj 50 | var retrievedTypeObjList = retrieveAll(target, type) 51 | for (i = 0, len = retrievedTypeObjList.length; i < len; i++) { 52 | retrievedTypeObj = retrievedTypeObjList[i] 53 | for (j = 0, itemsLength = retrievedTypeObj.items.length; j < itemsLength; j++) { 54 | this.add(retrievedTypeObj.items[j], {type: retrievedTypeObj.type}) 55 | } 56 | } 57 | return retrievedTypeObjList 58 | } 59 | 60 | function add (url, cfg) { 61 | var item = addedItems[url] 62 | if (!item) { 63 | item = this._createItem(url, (cfg && cfg.type) ? cfg.type : retrieve(url).type, cfg) 64 | } 65 | 66 | if (cfg && cfg.onLoad) item.onLoaded.addOnce(cfg.onLoad) 67 | 68 | if (!this.itemUrls[url]) { 69 | this.itemUrls[url] = item 70 | this.itemList.push(item) 71 | this.totalWeight += item.weight 72 | } 73 | 74 | return item 75 | } 76 | 77 | function load (url, cfg) { 78 | var item = addedItems[url] 79 | if (!item) { 80 | item = this._createItem(url, (cfg && cfg.type) ? cfg.type : retrieve(url).type, cfg) 81 | } 82 | 83 | if (cfg && cfg.onLoad) item.onLoaded.addOnce(cfg.onLoad) 84 | 85 | if (loadedItems[url]) { 86 | item.dispatch() 87 | } else { 88 | if (!item.isStartLoaded) { 89 | item.load() 90 | } 91 | } 92 | 93 | return item 94 | } 95 | 96 | function start (onLoading) { 97 | if (onLoading) this.loadingSignal.add(onLoading) 98 | this.isLoading = true 99 | var len = this.itemList.length 100 | if (len) { 101 | var itemList = this.itemList.splice(0, this.itemList.length) 102 | var item 103 | for (var url in this.itemUrls) { 104 | delete this.itemUrls[url]; 105 | } 106 | for (var i = 0; i < len; i++) { 107 | item = itemList[i] 108 | var isAlreadyLoaded = !!loadedItems[item.url] 109 | item.onLoaded.addOnce(_onItemLoad, this, -1024, item, itemList, isAlreadyLoaded) 110 | if (item.hasLoading) { 111 | item.loadingSignal.add(_onLoading, this, -1024, item, itemList, undef) 112 | } 113 | 114 | if (isAlreadyLoaded) { 115 | item.dispatch(_onItemLoad) 116 | } else { 117 | if (!item.isStartLoaded) { 118 | this.queue.push(item) 119 | } 120 | } 121 | } 122 | if (this.queue.length) { 123 | this.loadNext() 124 | } 125 | } else { 126 | _onItemLoad.call(this, undef, this.itemList) 127 | } 128 | } 129 | 130 | function loadNext () { 131 | if (this.queue.length && (this.activeItems.length < this.maxActiveItems)) { 132 | var item = this.queue.shift() 133 | this.activeItems.push(item) 134 | this.loadNext() 135 | item.load() 136 | } 137 | } 138 | 139 | function _onLoading (item, itemList, loadingSignal, itemPercent, percent) { 140 | // leave the onLoading triggered by the _onItemLoad() to prevent stacked call. 141 | if (item && !item.isLoaded && (item.getCombinedPercent(itemPercent) === 1)) return 142 | if (percent === undef) { 143 | this.loadedWeight = _getLoadedWeight(itemList) 144 | percent = this.loadedWeight / this.totalWeight 145 | } 146 | 147 | loadingSignal = loadingSignal || this.loadingSignal 148 | loadingSignal.dispatch(percent, item) 149 | } 150 | 151 | function _getLoadedWeight (itemList) { 152 | var loadedWeight = 0 153 | for (var i = 0, len = itemList.length; i < len; i++) { 154 | loadedWeight += itemList[i].loadedWeight 155 | } 156 | return loadedWeight 157 | } 158 | 159 | function _onItemLoad (item, itemList, isAlreadyLoaded) { 160 | this.loadedWeight = _getLoadedWeight(itemList) 161 | 162 | if (!isAlreadyLoaded) { 163 | var activeItems = this.activeItems 164 | var i = activeItems.length 165 | while (i--) { 166 | if(activeItems[i] === item) { 167 | activeItems.splice(i, 1) 168 | break 169 | } 170 | } 171 | } 172 | 173 | var loadingSignal = this.loadingSignal 174 | if (this.loadedWeight === this.totalWeight) { 175 | this.isLoading = false 176 | this.loadedWeight = 0 177 | this.totalWeight = 0 178 | this.loadingSignal = new MinSignal() 179 | this._onLoading(item, itemList, loadingSignal, 1, 1) 180 | if (item && item.noCache) _removeItemCache(item) 181 | } else { 182 | this._onLoading(item, itemList, loadingSignal, 1, this.loadedWeight / this.totalWeight) 183 | if (item && item.noCache) _removeItemCache(item) 184 | if (!isAlreadyLoaded) { 185 | this.loadNext() 186 | } 187 | } 188 | } 189 | 190 | function _removeItemCache (item) { 191 | var url = item.url 192 | item.content = undef 193 | addedItems[url] = undef 194 | loadedItems[url] = undef 195 | } 196 | 197 | function _createItem (url, type, cfg) { 198 | cfg = cfg || {} 199 | if (!cfg.crossOrigin) { 200 | for (var domain in this.crossOriginMap) { 201 | if (url.indexOf(domain) === 0) { 202 | cfg.crossOrigin = this.crossOriginMap[domain] 203 | break 204 | } 205 | } 206 | } 207 | return new ITEM_CLASSES[type](url, cfg) 208 | } 209 | 210 | function register (ItemClass) { 211 | if (!ITEM_CLASSES[ItemClass.type]) { 212 | ITEM_CLASS_LIST.push(ItemClass) 213 | ITEM_CLASSES[ItemClass.type] = ItemClass 214 | } 215 | } 216 | 217 | function retrieveAll (target, type) { 218 | var i, retrievedTypeObj 219 | var len = target.length 220 | var retrievedTypeObjList = [] 221 | if (len && (typeof target !== 'string')) { 222 | for (i = 0; i < len; i++) { 223 | retrievedTypeObj = retrieve(target[i], type) 224 | if (retrievedTypeObj) { 225 | retrievedTypeObjList = retrievedTypeObjList.concat(retrievedTypeObj) 226 | } 227 | } 228 | } else { 229 | retrievedTypeObj = retrieve(target, type) 230 | if (retrievedTypeObj) { 231 | retrievedTypeObjList = retrievedTypeObjList.concat(retrievedTypeObj) 232 | } 233 | } 234 | return retrievedTypeObjList 235 | } 236 | 237 | function retrieve (target, type) { 238 | var i, len, items, ItemClass, guessedType 239 | if (type) { 240 | ItemClass = ITEM_CLASSES[type] 241 | items = ItemClass.retrieve(target) 242 | } else { 243 | for (i = 0, len = ITEM_CLASS_LIST.length; i < len; i++) { 244 | ItemClass = ITEM_CLASS_LIST[i] 245 | guessedType = ItemClass.type 246 | 247 | if (typeof target === 'string') { 248 | if (testExtensions(target, ItemClass)) { 249 | items = [target] 250 | break 251 | } 252 | } else { 253 | items = ItemClass.retrieve(target) 254 | if (items && items.length && (typeof items[0] === 'string') && testExtensions(items[0], ItemClass)) { 255 | break 256 | } 257 | } 258 | items = undef 259 | guessedType = undef 260 | } 261 | } 262 | if (items) { 263 | return { 264 | type: type || guessedType, 265 | items: items 266 | } 267 | } 268 | return 269 | } 270 | 271 | function testExtensions (url, ItemClass) { 272 | if (!url) return 273 | var urlExtension = _getExtension(url) 274 | var extensions = ItemClass.extensions 275 | var i = extensions.length 276 | while (i--) { 277 | if (urlExtension === extensions[i]) { 278 | return true 279 | } 280 | } 281 | return false 282 | } 283 | 284 | function _getExtension (url) { 285 | return url.split('.').pop().split(/#|\?/)[0] 286 | } 287 | 288 | function create () { 289 | return new QuickLoader() 290 | } 291 | 292 | function check () { 293 | var addedUrl = [] 294 | var notLoadedUrl = [] 295 | for (var url in addedItems) { 296 | addedUrl.push(url) 297 | if (!loadedItems[url]) { 298 | notLoadedUrl.push(addedItems[url]) 299 | } 300 | } 301 | console.log({ 302 | added: addedUrl, 303 | notLoaded: notLoadedUrl 304 | }) 305 | } 306 | -------------------------------------------------------------------------------- /dist/quickLoader.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.quickLoader = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i b ? -1 : 0; 66 | }); 67 | } 68 | 69 | /** 70 | * Adding callback to the signal 71 | * @param {Function} the callback function 72 | * @param {object} the context of the callback function 73 | * @param {number} priority in the dispatch call. The higher priority it is, the eariler it will be dispatched. 74 | * @param {any...} additional argument prefix 75 | */ 76 | function add (fn, context, priority, args) { 77 | 78 | if(!fn) { 79 | throw ERROR_MESSAGE_MISSING_CALLBACK; 80 | } 81 | 82 | priority = priority || 0; 83 | var listeners = this._listeners; 84 | var listener, realFn, sliceIndex; 85 | var i = listeners.length; 86 | while(i--) { 87 | listener = listeners[i]; 88 | if(listener.f === fn && listener.c === context) { 89 | return false; 90 | } 91 | } 92 | if(typeof priority === 'function') { 93 | realFn = priority; 94 | priority = args; 95 | sliceIndex = 4; 96 | } 97 | listeners.unshift({f: fn, c: context, p: priority, r: realFn || fn, a: _slice.call(arguments, sliceIndex || 3), j: 0}); 98 | _sort(listeners); 99 | } 100 | 101 | /** 102 | * Adding callback to the signal but it will only trigger once 103 | * @param {Function} the callback function 104 | * @param {object} the context of the callback function 105 | * @param {number} priority in the dispatch call. The higher priority it is, the eariler it will be dispatched. 106 | * @param {any...} additional argument prefix 107 | */ 108 | function addOnce (fn, context, priority, args) { 109 | 110 | if(!fn) { 111 | throw ERROR_MESSAGE_MISSING_CALLBACK; 112 | } 113 | 114 | var self = this; 115 | var realFn = function() { 116 | self.remove.call(self, fn, context); 117 | return fn.apply(context, _slice.call(arguments, 0)); 118 | }; 119 | args = _slice.call(arguments, 0); 120 | if(args.length === 1) { 121 | args.push(undef); 122 | } 123 | args.splice(2, 0, realFn); 124 | add.apply(self, args); 125 | } 126 | 127 | /** 128 | * Remove callback from the signal 129 | * @param {Function} the callback function 130 | * @param {object} the context of the callback function 131 | * @return {boolean} return true if there is any callback was removed 132 | */ 133 | function remove (fn, context) { 134 | if(!fn) { 135 | this._listeners.length = 0; 136 | return true; 137 | } 138 | var listeners = this._listeners; 139 | var listener; 140 | var i = listeners.length; 141 | while(i--) { 142 | listener = listeners[i]; 143 | if(listener.f === fn && (!context || (listener.c === context))) { 144 | listener.j = 0; 145 | listeners.splice(i, 1); 146 | return true; 147 | } 148 | } 149 | return false; 150 | } 151 | 152 | 153 | /** 154 | * Dispatch the callback 155 | * @param {any...} additional argument suffix 156 | */ 157 | function dispatch(args) { 158 | args = _slice.call(arguments, 0); 159 | this.dispatchCount++; 160 | var dispatchCount = this.dispatchCount; 161 | var listeners = this._listeners; 162 | var listener, context, stoppedListener; 163 | var i = listeners.length; 164 | while(i--) { 165 | listener = listeners[i]; 166 | if(listener && (listener.j < dispatchCount)) { 167 | listener.j = dispatchCount; 168 | if(listener.r.apply(listener.c, listener.a.concat(args)) === false) { 169 | stoppedListener = listener; 170 | break; 171 | } 172 | } 173 | } 174 | listeners = this._listeners; 175 | i = listeners.length; 176 | while(i--) { 177 | listeners[i].j = 0; 178 | } 179 | return stoppedListener; 180 | } 181 | 182 | if (typeof module !== 'undefined') { 183 | module.exports = MinSignal; 184 | } 185 | 186 | }()); 187 | 188 | },{}],4:[function(require,module,exports){ 189 | var MinSignal = require('min-signal') 190 | 191 | var undef 192 | 193 | function QuickLoader () { 194 | this.isLoading = false 195 | this.totalWeight = 0 196 | this.loadedWeight = 0 197 | this.itemUrls = {} 198 | this.itemList = [] 199 | this.loadingSignal = new MinSignal() 200 | this.crossOriginMap = {} 201 | this.queue = [] 202 | this.activeItems = [] 203 | this.maxActiveItems = 4 204 | } 205 | 206 | var _p = QuickLoader.prototype 207 | _p.addChunk = addChunk 208 | _p.setCrossOrigin = setCrossOrigin 209 | _p.add = add 210 | _p.load = load 211 | _p.start = start 212 | _p.loadNext = loadNext 213 | _p._createItem = _createItem 214 | _p._onLoading = _onLoading 215 | 216 | _p.VERSION = '0.1.17' 217 | _p.register = register 218 | _p.retrieveAll = retrieveAll 219 | _p.retrieve = retrieve 220 | _p.testExtensions = testExtensions 221 | _p.create = create 222 | _p.check = check 223 | 224 | var addedItems = _p.addedItems = {} 225 | var loadedItems = _p.loadedItems = {} 226 | 227 | var ITEM_CLASS_LIST = _p.ITEM_CLASS_LIST = [] 228 | var ITEM_CLASSES = _p.ITEM_CLASSES = {} 229 | 230 | var quickLoader = module.exports = create() 231 | 232 | function setCrossOrigin (domain, value) { 233 | this.crossOriginMap[domain] = value 234 | } 235 | 236 | function addChunk (target, type) { 237 | var i, j, len, itemsLength, retrievedTypeObj 238 | var retrievedTypeObjList = retrieveAll(target, type) 239 | for (i = 0, len = retrievedTypeObjList.length; i < len; i++) { 240 | retrievedTypeObj = retrievedTypeObjList[i] 241 | for (j = 0, itemsLength = retrievedTypeObj.items.length; j < itemsLength; j++) { 242 | this.add(retrievedTypeObj.items[j], {type: retrievedTypeObj.type}) 243 | } 244 | } 245 | return retrievedTypeObjList 246 | } 247 | 248 | function add (url, cfg) { 249 | var item = addedItems[url] 250 | if (!item) { 251 | item = this._createItem(url, (cfg && cfg.type) ? cfg.type : retrieve(url).type, cfg) 252 | } 253 | 254 | if (cfg && cfg.onLoad) item.onLoaded.addOnce(cfg.onLoad) 255 | 256 | if (!this.itemUrls[url]) { 257 | this.itemUrls[url] = item 258 | this.itemList.push(item) 259 | this.totalWeight += item.weight 260 | } 261 | 262 | return item 263 | } 264 | 265 | function load (url, cfg) { 266 | var item = addedItems[url] 267 | if (!item) { 268 | item = this._createItem(url, (cfg && cfg.type) ? cfg.type : retrieve(url).type, cfg) 269 | } 270 | 271 | if (cfg && cfg.onLoad) item.onLoaded.addOnce(cfg.onLoad) 272 | 273 | if (loadedItems[url]) { 274 | item.dispatch() 275 | } else { 276 | if (!item.isStartLoaded) { 277 | item.load() 278 | } 279 | } 280 | 281 | return item 282 | } 283 | 284 | function start (onLoading) { 285 | if (onLoading) this.loadingSignal.add(onLoading) 286 | this.isLoading = true 287 | var len = this.itemList.length 288 | if (len) { 289 | var itemList = this.itemList.splice(0, this.itemList.length) 290 | var item 291 | for (var url in this.itemUrls) { 292 | delete this.itemUrls[url]; 293 | } 294 | for (var i = 0; i < len; i++) { 295 | item = itemList[i] 296 | var isAlreadyLoaded = !!loadedItems[item.url] 297 | item.onLoaded.addOnce(_onItemLoad, this, -1024, item, itemList, isAlreadyLoaded) 298 | if (item.hasLoading) { 299 | item.loadingSignal.add(_onLoading, this, -1024, item, itemList, undef) 300 | } 301 | 302 | if (isAlreadyLoaded) { 303 | item.dispatch(_onItemLoad) 304 | } else { 305 | if (!item.isStartLoaded) { 306 | this.queue.push(item) 307 | } 308 | } 309 | } 310 | if (this.queue.length) { 311 | this.loadNext() 312 | } 313 | } else { 314 | _onItemLoad.call(this, undef, this.itemList) 315 | } 316 | } 317 | 318 | function loadNext () { 319 | if (this.queue.length && (this.activeItems.length < this.maxActiveItems)) { 320 | var item = this.queue.shift() 321 | this.activeItems.push(item) 322 | this.loadNext() 323 | item.load() 324 | } 325 | } 326 | 327 | function _onLoading (item, itemList, loadingSignal, itemPercent, percent) { 328 | // leave the onLoading triggered by the _onItemLoad() to prevent stacked call. 329 | if (item && !item.isLoaded && (item.getCombinedPercent(itemPercent) === 1)) return 330 | if (percent === undef) { 331 | this.loadedWeight = _getLoadedWeight(itemList) 332 | percent = this.loadedWeight / this.totalWeight 333 | } 334 | 335 | loadingSignal = loadingSignal || this.loadingSignal 336 | loadingSignal.dispatch(percent, item) 337 | } 338 | 339 | function _getLoadedWeight (itemList) { 340 | var loadedWeight = 0 341 | for (var i = 0, len = itemList.length; i < len; i++) { 342 | loadedWeight += itemList[i].loadedWeight 343 | } 344 | return loadedWeight 345 | } 346 | 347 | function _onItemLoad (item, itemList, isAlreadyLoaded) { 348 | this.loadedWeight = _getLoadedWeight(itemList) 349 | 350 | if (!isAlreadyLoaded) { 351 | var activeItems = this.activeItems 352 | var i = activeItems.length 353 | while (i--) { 354 | if(activeItems[i] === item) { 355 | activeItems.splice(i, 1) 356 | break 357 | } 358 | } 359 | } 360 | 361 | var loadingSignal = this.loadingSignal 362 | if (this.loadedWeight === this.totalWeight) { 363 | this.isLoading = false 364 | this.loadedWeight = 0 365 | this.totalWeight = 0 366 | this.loadingSignal = new MinSignal() 367 | this._onLoading(item, itemList, loadingSignal, 1, 1) 368 | if (item && item.noCache) _removeItemCache(item) 369 | } else { 370 | this._onLoading(item, itemList, loadingSignal, 1, this.loadedWeight / this.totalWeight) 371 | if (item && item.noCache) _removeItemCache(item) 372 | if (!isAlreadyLoaded) { 373 | this.loadNext() 374 | } 375 | } 376 | } 377 | 378 | function _removeItemCache (item) { 379 | var url = item.url 380 | item.content = undef 381 | addedItems[url] = undef 382 | loadedItems[url] = undef 383 | } 384 | 385 | function _createItem (url, type, cfg) { 386 | cfg = cfg || {} 387 | if (!cfg.crossOrigin) { 388 | for (var domain in this.crossOriginMap) { 389 | if (url.indexOf(domain) === 0) { 390 | cfg.crossOrigin = this.crossOriginMap[domain] 391 | break 392 | } 393 | } 394 | } 395 | return new ITEM_CLASSES[type](url, cfg) 396 | } 397 | 398 | function register (ItemClass) { 399 | if (!ITEM_CLASSES[ItemClass.type]) { 400 | ITEM_CLASS_LIST.push(ItemClass) 401 | ITEM_CLASSES[ItemClass.type] = ItemClass 402 | } 403 | } 404 | 405 | function retrieveAll (target, type) { 406 | var i, retrievedTypeObj 407 | var len = target.length 408 | var retrievedTypeObjList = [] 409 | if (len && (typeof target !== 'string')) { 410 | for (i = 0; i < len; i++) { 411 | retrievedTypeObj = retrieve(target[i], type) 412 | if (retrievedTypeObj) { 413 | retrievedTypeObjList = retrievedTypeObjList.concat(retrievedTypeObj) 414 | } 415 | } 416 | } else { 417 | retrievedTypeObj = retrieve(target, type) 418 | if (retrievedTypeObj) { 419 | retrievedTypeObjList = retrievedTypeObjList.concat(retrievedTypeObj) 420 | } 421 | } 422 | return retrievedTypeObjList 423 | } 424 | 425 | function retrieve (target, type) { 426 | var i, len, items, ItemClass, guessedType 427 | if (type) { 428 | ItemClass = ITEM_CLASSES[type] 429 | items = ItemClass.retrieve(target) 430 | } else { 431 | for (i = 0, len = ITEM_CLASS_LIST.length; i < len; i++) { 432 | ItemClass = ITEM_CLASS_LIST[i] 433 | guessedType = ItemClass.type 434 | 435 | if (typeof target === 'string') { 436 | if (testExtensions(target, ItemClass)) { 437 | items = [target] 438 | break 439 | } 440 | } else { 441 | items = ItemClass.retrieve(target) 442 | if (items && items.length && (typeof items[0] === 'string') && testExtensions(items[0], ItemClass)) { 443 | break 444 | } 445 | } 446 | items = undef 447 | guessedType = undef 448 | } 449 | } 450 | if (items) { 451 | return { 452 | type: type || guessedType, 453 | items: items 454 | } 455 | } 456 | return 457 | } 458 | 459 | function testExtensions (url, ItemClass) { 460 | if (!url) return 461 | var urlExtension = _getExtension(url) 462 | var extensions = ItemClass.extensions 463 | var i = extensions.length 464 | while (i--) { 465 | if (urlExtension === extensions[i]) { 466 | return true 467 | } 468 | } 469 | return false 470 | } 471 | 472 | function _getExtension (url) { 473 | return url.split('.').pop().split(/#|\?/)[0] 474 | } 475 | 476 | function create () { 477 | return new QuickLoader() 478 | } 479 | 480 | function check () { 481 | var addedUrl = [] 482 | var notLoadedUrl = [] 483 | for (var url in addedItems) { 484 | addedUrl.push(url) 485 | if (!loadedItems[url]) { 486 | notLoadedUrl.push(addedItems[url]) 487 | } 488 | } 489 | console.log({ 490 | added: addedUrl, 491 | notLoaded: notLoadedUrl 492 | }) 493 | } 494 | 495 | },{"min-signal":3}],5:[function(require,module,exports){ 496 | var MinSignal = require('min-signal') 497 | var quickLoader = require('../quickLoader') 498 | 499 | function AbstractItem (url, cfg) { 500 | if (!url) return 501 | this.url = url 502 | this.loadedWeight = 0 503 | this.weight = 1 504 | this.postPercent = 0 505 | for (var id in cfg) { 506 | this[id] = cfg[id] 507 | } 508 | 509 | if (!this.type) { 510 | this.type = this.constructor.type 511 | } 512 | 513 | if (this.hasLoading) { 514 | this.loadingSignal = new MinSignal() 515 | this.loadingSignal.add(_onLoading, this) 516 | if (this.onLoading) { 517 | this.loadingSignal.add(this.onLoading) 518 | } 519 | } 520 | 521 | if (this.onPost) { 522 | this.onPostLoadingSignal = new MinSignal() 523 | this.onPostLoadingSignal.add(this._onPostLoading, this) 524 | this.postWeightRatio = this.postWeightRatio || 0.1 525 | } else { 526 | this.postWeightRatio = 0 527 | } 528 | 529 | var self = this 530 | this.boundOnLoad = function () { 531 | self._onLoad() 532 | } 533 | this.onLoaded = new MinSignal() 534 | 535 | quickLoader.addedItems[url] = this 536 | } 537 | 538 | module.exports = AbstractItem 539 | var _p = AbstractItem.prototype 540 | _p.load = load 541 | _p._onLoad = _onLoad 542 | _p._onLoading = _onLoading 543 | _p._onPostLoading = _onPostLoading 544 | _p._onLoadComplete = _onLoadComplete 545 | _p.getCombinedPercent = getCombinedPercent 546 | _p.dispatch = dispatch 547 | 548 | AbstractItem.extensions = [] 549 | 550 | AbstractItem.retrieve = function () { 551 | return false 552 | } 553 | 554 | function load () { 555 | this.isStartLoaded = true 556 | } 557 | 558 | function _onLoad () { 559 | if (this.onPost) { 560 | this.onPost.call(this, this.content, this.onPostLoadingSignal) 561 | } else { 562 | this._onLoadComplete() 563 | } 564 | } 565 | 566 | function _onPostLoading (percent) { 567 | this.postPercent = percent 568 | if (this.hasLoading) { 569 | this.loadingSignal.dispatch(1) 570 | } 571 | if (percent === 1) { 572 | this._onLoadComplete() 573 | } 574 | } 575 | 576 | function _onLoadComplete () { 577 | this.isLoaded = true 578 | this.loadedWeight = this.weight 579 | quickLoader.loadedItems[this.url] = this 580 | this.onLoaded.dispatch(this.content) 581 | } 582 | 583 | function getCombinedPercent (percent) { 584 | return percent * (1 - this.postWeightRatio) + (this.postWeightRatio * this.postPercent) 585 | } 586 | 587 | function _onLoading (percent) { 588 | this.loadedWeight = this.weight * this.getCombinedPercent(percent) 589 | } 590 | 591 | function dispatch () { 592 | if (this.hasLoading) { 593 | this.loadingSignal.remove() 594 | } 595 | this.onLoaded.dispatch(this.content) 596 | } 597 | 598 | },{"../quickLoader":4,"min-signal":3}],6:[function(require,module,exports){ 599 | var AbstractItem = require('./AbstractItem') 600 | var quickLoader = require('../quickLoader') 601 | 602 | function AnyItem (url, cfg) { 603 | if (!url) return 604 | _super.constructor.call(this, url, cfg) 605 | 606 | if (!this.loadFunc && console) { 607 | console[console.error || console.log]('require loadFunc in the config object.') 608 | } 609 | } 610 | 611 | module.exports = AnyItem 612 | AnyItem.type = 'any' 613 | AnyItem.extensions = [] 614 | quickLoader.register(AnyItem) 615 | 616 | AnyItem.retrieve = function () { 617 | return false 618 | } 619 | 620 | var _super = AbstractItem.prototype 621 | var _p = AnyItem.prototype = new AbstractItem() 622 | _p.constructor = AnyItem 623 | 624 | _p.load = load 625 | 626 | function load () { 627 | var self = this 628 | 629 | this.loadFunc(this.url, function (content) { 630 | self.content = content 631 | _super._onLoad.call(self) 632 | }, this.loadingSignal) 633 | } 634 | 635 | },{"../quickLoader":4,"./AbstractItem":5}],7:[function(require,module,exports){ 636 | var AbstractItem = require('./AbstractItem') 637 | var quickLoader = require('../quickLoader') 638 | 639 | var undef 640 | 641 | function AudioItem (url, cfg) { 642 | if (!url) return 643 | this.loadThrough = !cfg || cfg.loadThrough === undef ? true : cfg.loadThrough 644 | _super.constructor.apply(this, arguments) 645 | try { 646 | this.content = this.content || new Audio() 647 | } catch (e) { 648 | this.content = this.content || document.createElement('audio') 649 | } 650 | if (this.crossOrigin) { 651 | this.content.crossOrigin = this.crossOrigin 652 | } 653 | } 654 | 655 | module.exports = AudioItem 656 | AudioItem.type = 'audio' 657 | AudioItem.extensions = ['mp3', 'ogg'] 658 | quickLoader.register(AudioItem) 659 | 660 | AudioItem.retrieve = function (target) { 661 | // TODO add dom audios support 662 | return false 663 | } 664 | 665 | var _super = AbstractItem.prototype 666 | var _p = AudioItem.prototype = new AbstractItem() 667 | _p.constructor = AudioItem 668 | _p.load = load 669 | _p._onLoad = _onLoad 670 | 671 | function load () { 672 | _super.load.apply(this, arguments) 673 | var self = this 674 | var audio = self.content 675 | audio.src = this.url 676 | if (this.loadThrough) { 677 | audio.addEventListener('canplaythrough', this.boundOnLoad, false) 678 | } else { 679 | audio.addEventListener('canplay', this.boundOnLoad, false) 680 | } 681 | audio.load() 682 | } 683 | 684 | function _onLoad () { 685 | this.content.removeEventListener('canplaythrough', this.boundOnLoad, false) 686 | this.content.removeEventListener('canplay', this.boundOnLoad, false) 687 | if (this.isLoaded) { 688 | return 689 | } 690 | _super._onLoad.call(this) 691 | } 692 | 693 | },{"../quickLoader":4,"./AbstractItem":5}],8:[function(require,module,exports){ 694 | var AbstractItem = require('./AbstractItem') 695 | var computedStyle = require('computed-style') 696 | var quickLoader = require('../quickLoader') 697 | 698 | function ImageItem (url, cfg) { 699 | if (!url) return 700 | _super.constructor.apply(this, arguments) 701 | this.content = this.content || new Image() 702 | if (this.crossOrigin) { 703 | this.content.crossOrigin = this.crossOrigin 704 | } 705 | } 706 | 707 | module.exports = ImageItem 708 | var _super = AbstractItem.prototype 709 | var _p = ImageItem.prototype = new AbstractItem() 710 | _p.constructor = ImageItem 711 | _p.load = load 712 | _p._onLoad = _onLoad 713 | 714 | ImageItem.retrieve = function (target) { 715 | if (target.nodeType && target.style) { 716 | var list = [] 717 | 718 | if ((target.nodeName.toLowerCase() === 'img') && (target.src.indexOf(';') < 0)) { 719 | list.push(target.src) 720 | } 721 | 722 | computedStyle(target, 'background-image').replace(/s?url\(\s*?['"]?([^;]*?)['"]?\s*?\)/g, function (a, b) { 723 | list.push(b) 724 | }) 725 | 726 | var i = list.length 727 | while (i--) { 728 | if (!_isNotData(list[i])) { 729 | list.splice(i, 1) 730 | } 731 | } 732 | return list.length ? list : false 733 | } else if (typeof target === 'string') { 734 | return [target] 735 | } else { 736 | return false 737 | } 738 | } 739 | 740 | ImageItem.type = 'image' 741 | ImageItem.extensions = ['jpg', 'gif', 'png'] 742 | quickLoader.register(ImageItem) 743 | 744 | function load () { 745 | _super.load.apply(this, arguments) 746 | var img = this.content 747 | img.onload = this.boundOnLoad 748 | img.src = this.url 749 | } 750 | 751 | function _onLoad () { 752 | delete this.content.onload 753 | this.width = this.content.width 754 | this.height = this.content.height 755 | _super._onLoad.call(this) 756 | } 757 | 758 | function _isNotData (url) { 759 | return url.indexOf('data:') !== 0 760 | } 761 | 762 | },{"../quickLoader":4,"./AbstractItem":5,"computed-style":2}],9:[function(require,module,exports){ 763 | var TextItem = require('./TextItem') 764 | var quickLoader = require('../quickLoader') 765 | 766 | function JSONItem (url) { 767 | if (!url) return 768 | _super.constructor.apply(this, arguments) 769 | } 770 | 771 | module.exports = JSONItem 772 | JSONItem.type = 'json' 773 | JSONItem.extensions = ['json'] 774 | quickLoader.register(JSONItem) 775 | 776 | JSONItem.retrieve = function () { 777 | return false 778 | } 779 | 780 | var _super = TextItem.prototype 781 | var _p = JSONItem.prototype = new TextItem() 782 | _p.constructor = JSONItem 783 | _p._onLoad = _onLoad 784 | 785 | function _onLoad () { 786 | if (!this.content) { 787 | this.content = window.JSON && window.JSON.parse ? JSON.parse(this.xmlhttp.responseText.toString()) : eval(this.xmlhttp.responseText.toString()) 788 | } 789 | _super._onLoad.call(this) 790 | } 791 | 792 | },{"../quickLoader":4,"./TextItem":11}],10:[function(require,module,exports){ 793 | var AbstractItem = require('./AbstractItem') 794 | var quickLoader = require('../quickLoader') 795 | 796 | function __generateFuncName () { 797 | return '_jsonp' + new Date().getTime() + ~~(Math.random() * 100000000) 798 | } 799 | 800 | function JSONPItem (url) { 801 | if (!url) return 802 | _super.constructor.apply(this, arguments) 803 | } 804 | 805 | module.exports = JSONPItem 806 | JSONPItem.type = 'jsonp' 807 | JSONPItem.extensions = [] 808 | quickLoader.register(JSONPItem) 809 | 810 | JSONPItem.retrieve = function (target) { 811 | if ((typeof target === 'string') && (target.indexOf('=') > -1)) { 812 | return [target] 813 | } 814 | return false 815 | } 816 | 817 | var _super = AbstractItem.prototype 818 | var _p = JSONPItem.prototype = new AbstractItem() 819 | _p.constructor = JSONPItem 820 | _p.load = load 821 | 822 | function load (callback) { 823 | _super.load.apply(this, arguments) 824 | var self = this 825 | var lastEqualIndex = this.url.lastIndexOf('=') + 1 826 | var urlPrefix = this.url.substr(0, lastEqualIndex) 827 | var funcName = this.url.substr(lastEqualIndex) 828 | if (funcName.length === 0) { 829 | funcName = __generateFuncName() 830 | this.jsonpCallback = callback 831 | } else { 832 | this.jsonpCallback = this.jsonpCallback || window[funcName] 833 | } 834 | 835 | window[funcName] = function (data) { 836 | if (script.parentNode) script.parentNode.removeChild(script) 837 | self.content = data 838 | self._onLoad() 839 | } 840 | var script = document.createElement('script') 841 | script.type = 'text/javascript' 842 | script.src = urlPrefix + funcName 843 | document.getElementsByTagName('head')[0].appendChild(script) 844 | } 845 | 846 | },{"../quickLoader":4,"./AbstractItem":5}],11:[function(require,module,exports){ 847 | var XHRItem = require('./XHRItem') 848 | var quickLoader = require('../quickLoader') 849 | 850 | var undef 851 | 852 | function TextItem (url, cfg) { 853 | if (!url) return 854 | cfg.responseType = 'text' 855 | _super.constructor.apply(this, arguments) 856 | } 857 | 858 | module.exports = TextItem 859 | TextItem.type = 'text' 860 | TextItem.extensions = ['html', 'txt', 'svg'] 861 | quickLoader.register(TextItem) 862 | 863 | TextItem.retrieve = function () { 864 | return false 865 | } 866 | 867 | var _super = XHRItem.prototype 868 | var _p = TextItem.prototype = new XHRItem() 869 | _p.constructor = TextItem 870 | _p._onLoad = _onLoad 871 | 872 | function _onLoad () { 873 | if (!this.content) { 874 | this.content = this.xmlhttp.responseText; 875 | } 876 | _super._onLoad.apply(this, arguments) 877 | } 878 | 879 | },{"../quickLoader":4,"./XHRItem":13}],12:[function(require,module,exports){ 880 | var AbstractItem = require('./AbstractItem') 881 | var quickLoader = require('../quickLoader') 882 | 883 | var undef 884 | 885 | function VideoItem (url, cfg) { 886 | if (!url) { 887 | return 888 | } 889 | this.loadThrough = !cfg || cfg.loadThrough === undef ? true : cfg.loadThrough 890 | _super.constructor.apply(this, arguments) 891 | try { 892 | this.content = this.content || new Video() 893 | } catch (e) { 894 | this.content = this.content || document.createElement('video') 895 | } 896 | if (this.crossOrigin) { 897 | this.content.crossOrigin = this.crossOrigin 898 | } 899 | } 900 | 901 | module.exports = VideoItem 902 | VideoItem.type = 'video' 903 | VideoItem.extensions = ['mp4', 'webm', 'ogv'] 904 | quickLoader.register(VideoItem) 905 | 906 | VideoItem.retrieve = function (target) { 907 | // TODO add dom videos support 908 | return false 909 | } 910 | 911 | var _super = AbstractItem.prototype 912 | var _p = VideoItem.prototype = new AbstractItem() 913 | _p.constructor = VideoItem 914 | _p.load = load 915 | _p._onLoad = _onLoad 916 | 917 | function load () { 918 | _super.load.apply(this, arguments) 919 | var video = this.content 920 | video.preload = 'auto' 921 | video.src = this.url 922 | if (this.loadThrough) { 923 | video.addEventListener('canplaythrough', this.boundOnLoad, false) 924 | } else { 925 | video.addEventListener('canplay', this.boundOnLoad, false) 926 | } 927 | video.load() 928 | } 929 | 930 | function _onLoad () { 931 | this.content.removeEventListener('canplaythrough', this.boundOnLoad) 932 | this.content.removeEventListener('canplay', this.boundOnLoad) 933 | if (this.isLoaded) { 934 | return 935 | } 936 | _super._onLoad.call(this) 937 | } 938 | 939 | },{"../quickLoader":4,"./AbstractItem":5}],13:[function(require,module,exports){ 940 | var AbstractItem = require('./AbstractItem') 941 | var quickLoader = require('../quickLoader') 942 | 943 | var undef 944 | 945 | var IS_SUPPORT_XML_HTTP_REQUEST = !!window.XMLHttpRequest 946 | 947 | function XHRItem (url) { 948 | if (!url) return 949 | _super.constructor.apply(this, arguments) 950 | this.responseType = this.responseType || '' 951 | this.method = this.method || 'GET' 952 | } 953 | 954 | module.exports = XHRItem 955 | XHRItem.type = 'xhr' 956 | XHRItem.extensions = [] 957 | quickLoader.register(XHRItem) 958 | 959 | XHRItem.retrieve = function () { 960 | return false 961 | } 962 | 963 | var _super = AbstractItem.prototype 964 | var _p = XHRItem.prototype = new AbstractItem() 965 | _p.constructor = XHRItem 966 | _p.load = load 967 | _p._onXmlHttpChange = _onXmlHttpChange 968 | _p._onXmlHttpProgress = _onXmlHttpProgress 969 | _p._onLoad = _onLoad 970 | 971 | function load () { 972 | _super.load.apply(this, arguments) 973 | var self = this 974 | var xmlhttp 975 | 976 | if (IS_SUPPORT_XML_HTTP_REQUEST) { 977 | xmlhttp = this.xmlhttp = new XMLHttpRequest() 978 | } else { 979 | xmlhttp = this.xmlhttp = new ActiveXObject('Microsoft.XMLHTTP') 980 | } 981 | if (this.hasLoading) { 982 | xmlhttp.onprogress = function (evt) { 983 | self._onXmlHttpProgress(evt) 984 | } 985 | } 986 | xmlhttp.onreadystatechange = function () { 987 | self._onXmlHttpChange() 988 | } 989 | xmlhttp.open(this.method, this.url, true) 990 | this.xmlhttp.responseType = this.responseType 991 | 992 | if (IS_SUPPORT_XML_HTTP_REQUEST) { 993 | xmlhttp.send(null) 994 | } else { 995 | xmlhttp.send() 996 | } 997 | } 998 | 999 | function _onXmlHttpProgress (evt) { 1000 | this.loadingSignal.dispatch(evt.loaded / evt.total) 1001 | } 1002 | 1003 | function _onXmlHttpChange () { 1004 | if (this.xmlhttp.readyState === 4) { 1005 | if (this.xmlhttp.status === 200) { 1006 | this._onLoad(this.xmlhttp) 1007 | } 1008 | } 1009 | } 1010 | 1011 | function _onLoad () { 1012 | if (!this.content) { 1013 | this.content = this.xmlhttp.response 1014 | } 1015 | this.xmlhttp = undef 1016 | _super._onLoad.call(this) 1017 | } 1018 | 1019 | },{"../quickLoader":4,"./AbstractItem":5}]},{},[1])(1) 1020 | }); 1021 | --------------------------------------------------------------------------------