├── .babelrc ├── .gitignore ├── .jscsrc ├── .travis.yml ├── LICENSE ├── build ├── leopard.js ├── leopard.js.map ├── leopard.min.js └── leopard.min.js.map ├── demo ├── image.html ├── index.html ├── leopard1.jpg ├── leopard2.jpg ├── leopard3.jpg └── starvation.html ├── gulpfile.js ├── package.json ├── readme.md ├── src ├── congestion.js ├── emitter.js ├── index.js ├── now.js ├── rAF.js └── schedule.js └── test ├── congestion.js └── helpers └── browser-env.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # Test related files 36 | .nyc_output/ 37 | # Coveralls 38 | .coveralls.yml 39 | .vscode/ -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | preset: 'google', 3 | esnext: true, 4 | "requireSemicolons": false, 5 | "disallowSemicolons": true 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - stable 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Howard, Chang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build/leopard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; 4 | 5 | var Emitter = function Emitter() { 6 | this.uid = 0; 7 | this.handlers = []; 8 | for (var i = 0; i < 1000; i++) { 9 | this.handlers.push([]); 10 | } 11 | }; 12 | Emitter.prototype.on = function (level, callback) { 13 | this.handlers[level].push({ 14 | id: this.uid++, 15 | action: callback 16 | }); 17 | }; 18 | Emitter.prototype.emit = function (level) { 19 | for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 20 | args[_key - 1] = arguments[_key]; 21 | } 22 | 23 | this.handlers[level].forEach(function (handler) { 24 | if (typeof handler.action === 'function') handler.action.apply(handler, args); 25 | }); 26 | }; 27 | Emitter.prototype.once = function (level, callback) { 28 | var _this = this; 29 | 30 | var id = this.uid; 31 | this.on(level, function () { 32 | callback.apply(undefined, arguments); 33 | var handler = _this.handlers[level].find(function (handler) { 34 | return handler.id === id; 35 | }); 36 | _this.handlers[level].splice(_this.handlers[level].indexOf(handler), 1); 37 | // delete handler 38 | }); 39 | }; 40 | var singletonEmitter = new Emitter() 41 | 42 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 43 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating 44 | 45 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel 46 | 47 | // MIT license 48 | 49 | ;(function () { 50 | var lastTime = 0; 51 | var vendors = ['ms', 'moz', 'webkit', 'o']; 52 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 53 | window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; 54 | window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']; 55 | } 56 | 57 | if (!window.requestAnimationFrame) window.requestAnimationFrame = function (callback, element) { 58 | var currTime = new Date().getTime(); 59 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 60 | var id = window.setTimeout(function () { 61 | callback(currTime + timeToCall); 62 | }, timeToCall); 63 | lastTime = currTime + timeToCall; 64 | return id; 65 | }; 66 | /* istanbul ignore if */ 67 | if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function (id) { 68 | clearTimeout(id); 69 | }; 70 | })(); 71 | 72 | // @license http://opensource.org/licenses/MIT 73 | // copyright Paul Irish 2015 74 | 75 | // Date.now() is supported everywhere except IE8. For IE8 we use the Date.now polyfill 76 | // github.com/Financial-Times/polyfill-service/blob/master/polyfills/Date.now/polyfill.js 77 | // as Safari 6 doesn't have support for NavigationTiming, we use a Date.now() timestamp for relative values 78 | 79 | // if you want values similar to what you'd get with real perf.now, place this towards the head of the page 80 | // but in reality, you're just getting the delta between now() calls, so it's not terribly important where it's placed 81 | 82 | (function () { 83 | 84 | if ('performance' in window == false) { 85 | window.performance = {}; 86 | } 87 | /* istanbul ignore next */ 88 | Date.now = Date.now || function () { 89 | // thanks IE8 90 | return new Date().getTime(); 91 | }; 92 | 93 | if ('now' in window.performance == false) { 94 | 95 | var nowOffset = Date.now(); 96 | /* istanbul ignore next */ 97 | if (window.performance.timing && window.performance.timing.navigationStart) { 98 | nowOffset = window.performance.timing.navigationStart; 99 | } 100 | 101 | window.performance.now = function now() { 102 | return Date.now() - nowOffset; 103 | }; 104 | } 105 | })(); 106 | 107 | var queue = []; 108 | var counter = 0; 109 | var levels = 1000; 110 | 111 | for (var i = 0; i < levels; i++) { 112 | queue.push([]); 113 | }function run(count) { 114 | for (var i = 0; i < queue.length; i++) { 115 | if (count < 1) break; 116 | var level = queue[i]; 117 | while (level.length) { 118 | if (count < 1) break; 119 | counter--; 120 | // the bigger of level, the less emergent to complete 121 | // So we deduce more for higher level (lower priority) actions 122 | count--; 123 | var callback = level.shift(); 124 | if (callback && typeof callback === 'function') callback(); 125 | if (!level.length) { 126 | singletonEmitter.emit(i); 127 | } 128 | } 129 | /* istanbul ignore if */ 130 | if (i === queue.length - 1 && counter === 0) { 131 | return false; 132 | } 133 | } 134 | return true; 135 | } 136 | function enqueue(priority, callback, times) { 137 | if (!times) { 138 | queue[priority].push(callback); 139 | counter++; 140 | } else { 141 | while (times--) { 142 | queue[priority].push(callback); 143 | counter++; 144 | } 145 | } 146 | } 147 | function flush() { 148 | for (var _i = 0; _i < levels; _i++) { 149 | queue[_i].length = 0; 150 | }counter = 0; 151 | } 152 | 153 | var perFrame = 16; 154 | 155 | // options 156 | var expectedFrame = perFrame; 157 | var limit = 1000; 158 | var count = limit; 159 | var strategy = 'normal'; 160 | var perf = 2; 161 | var autoStop = false; 162 | 163 | var isRunning = false; 164 | var accelerate = 1; // for slow start 165 | 166 | var scriptStart; 167 | var scriptEnd; 168 | var styleStart; 169 | var styleEnd; 170 | 171 | var styleDuration; 172 | var scriptDuration; 173 | 174 | function frame(frameStart) { 175 | if (!isRunning) return; 176 | // calculate metrix 177 | styleEnd = frameStart; 178 | styleDuration = styleStart ? styleEnd - styleStart : expectedFrame; 179 | scriptDuration = scriptEnd - scriptStart; 180 | 181 | var inc = true; 182 | var dec = true; 183 | // calculate limit 184 | if (strategy === 'batch') { 185 | // will try to batch up all update 186 | inc = scriptDuration < expectedFrame + 1; 187 | dec = scriptDuration >= expectedFrame + 1; 188 | } else { 189 | inc = styleDuration >= expectedFrame && styleDuration < expectedFrame + 1 && styleDuration !== 0; 190 | dec = styleDuration >= expectedFrame + 1; 191 | } 192 | if (inc) { 193 | accelerate = accelerate * perf; 194 | count += accelerate; 195 | } else if (dec) { 196 | accelerate = 1; 197 | count = Math.floor(count / 2); 198 | /* istanbul ignore next */ 199 | } else if (styleDuration === 0) { 200 | // This is a skipped frame 201 | } 202 | if (count < 1) count = 1; 203 | scriptStart = window.performance.now(); 204 | var continueRun = run(count); 205 | if (!continueRun && autoStop) return; 206 | scriptEnd = window.performance.now(); 207 | styleStart = frameStart; 208 | 209 | window.requestAnimationFrame(frame); 210 | if (window && window.requestIdleCallback) { 211 | // For browsers which support requestIdleCallback 212 | /* istanbul ignore next */ 213 | window.requestIdleCallback(function (deadline) { 214 | if (deadline.timeRemaining() > 0) { 215 | var ratio = deadline.timeRemaining() / perFrame; 216 | run(Math.floor(count * ratio)); 217 | } 218 | }); 219 | } 220 | } 221 | 222 | function stop() { 223 | accelerate = 1; // for slow start 224 | count = limit; 225 | isRunning = false; 226 | flush(); 227 | } 228 | function start() { 229 | var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 230 | 231 | if (!isRunning) window.requestAnimationFrame(frame); 232 | options.limit && (limit = count = options.limit); 233 | options.expectedFrame && (expectedFrame = options.expectedFrame); 234 | options.strategy && (strategy = options.strategy); 235 | options.perf && (perf = options.perf); 236 | options.autoStop && (autoStop = options.autoStop); 237 | scriptStart = null; 238 | scriptEnd = null; 239 | styleStart = null; 240 | styleEnd = null; 241 | isRunning = true; 242 | } 243 | function put(priority, callback, times) { 244 | enqueue(priority, callback, times); 245 | } 246 | function getCount() { 247 | return count; 248 | } 249 | 250 | var Leopard = { 251 | on: singletonEmitter.on.bind(singletonEmitter), 252 | once: singletonEmitter.once.bind(singletonEmitter), 253 | start: start, 254 | stop: stop, 255 | put: put, 256 | get limit() { 257 | return getCount(); 258 | } 259 | }; 260 | 261 | if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') module.exports = Leopard;else if (typeof define === 'function' && typeof define.amd !== 'undefined') define(function () { 262 | return Leopard; 263 | });else window.Leopard = Leopard; -------------------------------------------------------------------------------- /build/leopard.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["rAF.js","emitter.js","control.js","schedule.js","index.js"],"names":["queue","counter","emitter"],"mappings":"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","file":"leopard.js","sourcesContent":["// http://paulirish.com/2011/requestanimationframe-for-smart-animating/\n// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating\n\n// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel\n\n// MIT license\n\n(function() {\n var lastTime = 0\n var vendors = ['ms', 'moz', 'webkit', 'o']\n for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {\n window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']\n window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||\n window[vendors[x] + 'CancelRequestAnimationFrame']\n }\n\n if (!window.requestAnimationFrame)\n window.requestAnimationFrame = function(callback, element) {\n var currTime = new Date().getTime()\n var timeToCall = Math.max(0, 16 - (currTime - lastTime))\n var id = window.setTimeout(function() { callback(currTime + timeToCall); },\n timeToCall)\n lastTime = currTime + timeToCall\n return id\n }\n\n if (!window.cancelAnimationFrame)\n window.cancelAnimationFrame = function(id) {\n clearTimeout(id)\n }\n}())\n","var Emitter = function() {\n this.uid = 0\n this.handlers = []\n // FIX 1000\n for (let i = 0; i < 1000; i ++) this.handlers.push([])\n}\nEmitter.prototype.on = function(level, callback) {\n this.handlers[level].push({\n id: this.uid ++,\n action: callback\n })\n}\nEmitter.prototype.emit = function(level, ...args) {\n this.handlers[level].forEach(handler => {\n if (typeof(handler.action) === 'function')\n handler.action(...args)\n })\n}\nEmitter.prototype.once = function(level, callback) {\n var id = this.uid\n this.on(level, (...args) => {\n callback(...args)\n var handler = this.handlers[level].find(handler => handler.id === id)\n this.handlers[level].splice(this.handlers[level].indexOf(handler), 1)\n // delete handler\n })\n}\nvar singletonEmitter = new Emitter()\nexport default singletonEmitter\n","import emitter from './emitter'\nvar queue = []\nvar counter = 0\nvar levels = 1000\n\nfor (let i = 0; i < levels; i ++) queue.push([])\n\nexport function run(count) {\n for (var i = 0; i < queue.length; i ++) {\n if (count < 1) break\n var level = queue[i]\n while (level.length) {\n if (count < 1) break\n counter --\n // the bigger of level, the less emergent to complete\n // So we deduce more for higher level (lower priority) actions\n count = count - i * i\n var callback = level.shift()\n if (callback && typeof callback === 'function') callback()\n if (!level.length) emitter.emit(i)\n }\n if (i === queue.length - 1 && counter === 0)\n return false\n return true\n }\n}\n","import { run } from './control'\nvar perFrame = 16\nvar limit = 10\nvar balance = limit\nvar goal = perFrame\nvar isRunning = false\nvar accelerate = 1 // for slow start\n\nvar scriptStart\nvar scriptEnd\nvar styleStart\nvar styleEnd\n\nvar styleDuration\nvar scriptDuration\n\n\nfunction frame(frameStart) {\n if (!isRunning) return\n\n // calculate metrix\n styleEnd = frameStart\n styleDuration = styleStart ? (styleEnd - styleStart) : goal\n scriptDuration = scriptEnd - scriptStart\n scriptStart = now()\n console.log(limit)\n console.log(styleDuration);\n\n // calculate limit\n if (goal <= styleDuration && styleDuration < goal + 1 &&\n styleDuration !== 0) {\n accelerate = accelerate * 2\n limit += accelerate\n } else if (styleDuration >= goal + 1) {\n accelerate = 1\n limit = Math.floor(limit / 2)\n } else if (styleDuration === 0) {\n // This is a skipped frame\n }\n if (limit < 1)\n limit = 1\n if (!run(limit)) // stop\n stop()\n scriptEnd = now()\n styleStart = frameStart\n \n requestAnimationFrame(frame)\n if (window && window.requestIdleCallback) {\n // For browsers which support requestIdleCallback\n requestIdleCallback(function(deadline) {\n if (deadline.timeRemaining() > 0) {\n var ratio = deadline.timeRemaining() / perFrame\n run(Math.floor(limit * ratio))\n }\n })\n }\n}\n\n\nfunction now() {\n return performance.now() || Date.now()\n}\n\nexport function stop() {\n console.log('stop');\n accelerate = 1 // for slow start\n multiplier = 2\n isRunning = false\n}\nexport function start() {\n scriptStart = null\n scriptEnd = null\n styleStart = null\n styleEnd = null\n isRunning = true\n requestAnimationFrame(frame)\n\n}\nexport function put(priority, callback, times) {\n if (!times) {\n queue[priority].push(callback)\n counter ++\n } else {\n while (times--) {\n queue[priority].push(callback)\n counter ++\n }\n }\n if (!isRunning)\n start()\n}\n","import _ from './rAF'\nimport {\n start,\n stop,\n put\n} from './schedule'\nimport emitter from './emitter'\nvar Leopard = {\n on: emitter.on.bind(emitter),\n once: emitter.once.bind(emitter),\n start,\n stop,\n put\n}\n\nif (typeof exports === 'object')\n module.exports = Leopard\nelse if (typeof define === 'function' && typeof define.amd !== 'undefined')\n define(function() { return Leopard })\nelse\n window.Leopard = Leopard\n"],"sourceRoot":"/source/"} -------------------------------------------------------------------------------- /build/leopard.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function run(t){for(var e=0;et);e++){for(var n=queue[e];n.length&&!(1>t);){counter--,t--;var i=n.shift();i&&"function"==typeof i&&i(),n.length||singletonEmitter.emit(e)}if(e===queue.length-1&&0===counter)return!1}return!0}function enqueue(t,e,n){if(n)for(;n--;)queue[t].push(e),counter++;else queue[t].push(e),counter++}function flush(){for(var t=0;levels>t;t++)queue[t].length=0;counter=0}function frame(t){if(isRunning){styleEnd=t,styleDuration=styleStart?styleEnd-styleStart:expectedFrame,scriptDuration=scriptEnd-scriptStart;var e=!0,n=!0;"batch"===strategy?(e=expectedFrame+1>scriptDuration,n=scriptDuration>=expectedFrame+1):(e=styleDuration>=expectedFrame&&expectedFrame+1>styleDuration&&0!==styleDuration,n=styleDuration>=expectedFrame+1),e?(accelerate*=perf,count+=accelerate):n&&(accelerate=1,count=Math.floor(count/2)),1>count&&(count=1),scriptStart=window.performance.now();var i=run(count);!i&&autoStop||(scriptEnd=window.performance.now(),styleStart=t,window.requestAnimationFrame(frame),window&&window.requestIdleCallback&&window.requestIdleCallback(function(t){if(t.timeRemaining()>0){var e=t.timeRemaining()/perFrame;run(Math.floor(count*e))}}))}}function stop(){accelerate=1,count=limit,isRunning=!1,flush()}function start(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];isRunning||window.requestAnimationFrame(frame),t.limit&&(limit=count=t.limit),t.expectedFrame&&(expectedFrame=t.expectedFrame),t.strategy&&(strategy=t.strategy),t.perf&&(perf=t.perf),t.autoStop&&(autoStop=t.autoStop),scriptStart=null,scriptEnd=null,styleStart=null,styleEnd=null,isRunning=!0}function put(t,e,n){enqueue(t,e,n)}function getCount(){return count}var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol?"symbol":typeof t},Emitter=function(){this.uid=0,this.handlers=[];for(var t=0;1e3>t;t++)this.handlers.push([])};Emitter.prototype.on=function(t,e){this.handlers[t].push({id:this.uid++,action:e})},Emitter.prototype.emit=function(t){for(var e=arguments.length,n=Array(e>1?e-1:0),i=1;e>i;i++)n[i-1]=arguments[i];this.handlers[t].forEach(function(t){"function"==typeof t.action&&t.action.apply(t,n)})},Emitter.prototype.once=function(t,e){var n=this,i=this.uid;this.on(t,function(){e.apply(void 0,arguments);var r=n.handlers[t].find(function(t){return t.id===i});n.handlers[t].splice(n.handlers[t].indexOf(r),1)})};var singletonEmitter=new Emitter;!function(){for(var t=0,e=["ms","moz","webkit","o"],n=0;ni;i++)queue.push([]);var perFrame=16,expectedFrame=perFrame,limit=1e3,count=limit,strategy="normal",perf=2,autoStop=!1,isRunning=!1,accelerate=1,scriptStart,scriptEnd,styleStart,styleEnd,styleDuration,scriptDuration,Leopard={on:singletonEmitter.on.bind(singletonEmitter),once:singletonEmitter.once.bind(singletonEmitter),start:start,stop:stop,put:put,get limit(){return getCount()}};"object"===("undefined"==typeof exports?"undefined":_typeof(exports))?module.exports=Leopard:"function"==typeof define&&"undefined"!=typeof define.amd?define(function(){return Leopard}):window.Leopard=Leopard; 2 | //# sourceMappingURL=leopard.min.js.map 3 | -------------------------------------------------------------------------------- /build/leopard.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["leopard.js","schedule.js","congestion.js","emitter.js","rAF.js","now.js","index.js"],"names":["run","count","i","queue","length","level","counter","callback","shift","emitter","emit","enqueue","priority","times","push","flush","_i","levels","frame","frameStart","isRunning","styleEnd","styleDuration","styleStart","expectedFrame","scriptDuration","scriptEnd","scriptStart","inc","dec","strategy","accelerate","perf","Math","floor","window","performance","now","continueRun","autoStop","requestAnimationFrame","requestIdleCallback","deadline","timeRemaining","ratio","perFrame","stop","limit","start","options","arguments","undefined","put","getCount","_typeof","Symbol","iterator","obj","constructor","Emitter","this","uid","handlers","prototype","on","id","action","_len","args","Array","_key","forEach","handler","apply","once","_this","find","splice","indexOf","singletonEmitter","lastTime","vendors","x","cancelAnimationFrame","element","currTime","Date","getTime","timeToCall","max","setTimeout","clearTimeout","nowOffset","timing","navigationStart","Leopard","bind","exports","module","define","amd"],"mappings":"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","file":"leopard.min.js","sourcesContent":["'use strict';\n\nvar _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol ? \"symbol\" : typeof obj; };\n\nvar Emitter = function Emitter() {\n this.uid = 0;\n this.handlers = [];\n for (var i = 0; i < 1000; i++) {\n this.handlers.push([]);\n }\n};\nEmitter.prototype.on = function (level, callback) {\n this.handlers[level].push({\n id: this.uid++,\n action: callback\n });\n};\nEmitter.prototype.emit = function (level) {\n for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n args[_key - 1] = arguments[_key];\n }\n\n this.handlers[level].forEach(function (handler) {\n if (typeof handler.action === 'function') handler.action.apply(handler, args);\n });\n};\nEmitter.prototype.once = function (level, callback) {\n var _this = this;\n\n var id = this.uid;\n this.on(level, function () {\n callback.apply(undefined, arguments);\n var handler = _this.handlers[level].find(function (handler) {\n return handler.id === id;\n });\n _this.handlers[level].splice(_this.handlers[level].indexOf(handler), 1);\n // delete handler\n });\n};\nvar singletonEmitter = new Emitter()\n\n// http://paulirish.com/2011/requestanimationframe-for-smart-animating/\n// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating\n\n// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel\n\n// MIT license\n\n;(function () {\n var lastTime = 0;\n var vendors = ['ms', 'moz', 'webkit', 'o'];\n for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {\n window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];\n window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];\n }\n\n if (!window.requestAnimationFrame) window.requestAnimationFrame = function (callback, element) {\n var currTime = new Date().getTime();\n var timeToCall = Math.max(0, 16 - (currTime - lastTime));\n var id = window.setTimeout(function () {\n callback(currTime + timeToCall);\n }, timeToCall);\n lastTime = currTime + timeToCall;\n return id;\n };\n /* istanbul ignore if */\n if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function (id) {\n clearTimeout(id);\n };\n})();\n\n// @license http://opensource.org/licenses/MIT\n// copyright Paul Irish 2015\n\n// Date.now() is supported everywhere except IE8. For IE8 we use the Date.now polyfill\n// github.com/Financial-Times/polyfill-service/blob/master/polyfills/Date.now/polyfill.js\n// as Safari 6 doesn't have support for NavigationTiming, we use a Date.now() timestamp for relative values\n\n// if you want values similar to what you'd get with real perf.now, place this towards the head of the page\n// but in reality, you're just getting the delta between now() calls, so it's not terribly important where it's placed\n\n(function () {\n\n if ('performance' in window == false) {\n window.performance = {};\n }\n /* istanbul ignore next */\n Date.now = Date.now || function () {\n // thanks IE8\n return new Date().getTime();\n };\n\n if ('now' in window.performance == false) {\n\n var nowOffset = Date.now();\n /* istanbul ignore next */\n if (window.performance.timing && window.performance.timing.navigationStart) {\n nowOffset = window.performance.timing.navigationStart;\n }\n\n window.performance.now = function now() {\n return Date.now() - nowOffset;\n };\n }\n})();\n\nvar queue = [];\nvar counter = 0;\nvar levels = 1000;\n\nfor (var i = 0; i < levels; i++) {\n queue.push([]);\n}function run(count) {\n for (var i = 0; i < queue.length; i++) {\n if (count < 1) break;\n var level = queue[i];\n while (level.length) {\n if (count < 1) break;\n counter--;\n // the bigger of level, the less emergent to complete\n // So we deduce more for higher level (lower priority) actions\n count--;\n var callback = level.shift();\n if (callback && typeof callback === 'function') callback();\n if (!level.length) {\n singletonEmitter.emit(i);\n }\n }\n /* istanbul ignore if */\n if (i === queue.length - 1 && counter === 0) {\n return false;\n }\n }\n return true;\n}\nfunction enqueue(priority, callback, times) {\n if (!times) {\n queue[priority].push(callback);\n counter++;\n } else {\n while (times--) {\n queue[priority].push(callback);\n counter++;\n }\n }\n}\nfunction flush() {\n for (var _i = 0; _i < levels; _i++) {\n queue[_i].length = 0;\n }counter = 0;\n}\n\nvar perFrame = 16;\n\n// options\nvar expectedFrame = perFrame;\nvar limit = 1000;\nvar count = limit;\nvar strategy = 'normal';\nvar perf = 2;\nvar autoStop = false;\n\nvar isRunning = false;\nvar accelerate = 1; // for slow start\n\nvar scriptStart;\nvar scriptEnd;\nvar styleStart;\nvar styleEnd;\n\nvar styleDuration;\nvar scriptDuration;\n\nfunction frame(frameStart) {\n if (!isRunning) return;\n // calculate metrix\n styleEnd = frameStart;\n styleDuration = styleStart ? styleEnd - styleStart : expectedFrame;\n scriptDuration = scriptEnd - scriptStart;\n\n var inc = true;\n var dec = true;\n // calculate limit\n if (strategy === 'batch') {\n // will try to batch up all update\n inc = scriptDuration < expectedFrame + 1;\n dec = scriptDuration >= expectedFrame + 1;\n } else {\n inc = styleDuration >= expectedFrame && styleDuration < expectedFrame + 1 && styleDuration !== 0;\n dec = styleDuration >= expectedFrame + 1;\n }\n if (inc) {\n accelerate = accelerate * perf;\n count += accelerate;\n } else if (dec) {\n accelerate = 1;\n count = Math.floor(count / 2);\n /* istanbul ignore next */\n } else if (styleDuration === 0) {\n // This is a skipped frame\n }\n if (count < 1) count = 1;\n scriptStart = window.performance.now();\n var continueRun = run(count);\n if (!continueRun && autoStop) return;\n scriptEnd = window.performance.now();\n styleStart = frameStart;\n\n window.requestAnimationFrame(frame);\n if (window && window.requestIdleCallback) {\n // For browsers which support requestIdleCallback\n /* istanbul ignore next */\n window.requestIdleCallback(function (deadline) {\n if (deadline.timeRemaining() > 0) {\n var ratio = deadline.timeRemaining() / perFrame;\n run(Math.floor(count * ratio));\n }\n });\n }\n}\n\nfunction stop() {\n accelerate = 1; // for slow start\n count = limit;\n isRunning = false;\n flush();\n}\nfunction start() {\n var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];\n\n if (!isRunning) window.requestAnimationFrame(frame);\n options.limit && (limit = count = options.limit);\n options.expectedFrame && (expectedFrame = options.expectedFrame);\n options.strategy && (strategy = options.strategy);\n options.perf && (perf = options.perf);\n options.autoStop && (autoStop = options.autoStop);\n scriptStart = null;\n scriptEnd = null;\n styleStart = null;\n styleEnd = null;\n isRunning = true;\n}\nfunction put(priority, callback, times) {\n enqueue(priority, callback, times);\n}\nfunction getCount() {\n return count;\n}\n\nvar Leopard = {\n on: singletonEmitter.on.bind(singletonEmitter),\n once: singletonEmitter.once.bind(singletonEmitter),\n start: start,\n stop: stop,\n put: put,\n get limit() {\n return getCount();\n }\n};\n\nif ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') module.exports = Leopard;else if (typeof define === 'function' && typeof define.amd !== 'undefined') define(function () {\n return Leopard;\n});else window.Leopard = Leopard;","import emitter from './emitter'\n\nvar queue = []\nvar counter = 0\nvar levels = 1000\n\nfor (let i = 0; i < levels; i ++) queue.push([])\n\nexport function run(count) {\n for (var i = 0; i < queue.length; i ++) {\n if (count < 1) break\n var level = queue[i]\n while (level.length) {\n if (count < 1) break\n counter --\n // the bigger of level, the less emergent to complete\n // So we deduce more for higher level (lower priority) actions\n count --\n var callback = level.shift()\n if (callback && typeof callback === 'function') callback()\n if (!level.length) {\n emitter.emit(i)\n }\n }\n /* istanbul ignore if */\n if (i === queue.length - 1 && counter === 0) {\n return false\n }\n }\n return true\n}\nexport function enqueue(priority, callback, times) {\n if (!times) {\n queue[priority].push(callback)\n counter ++\n } else {\n while (times--) {\n queue[priority].push(callback)\n counter ++\n }\n }\n}\nexport function flush() {\n for (let i = 0; i < levels; i ++) queue[i].length = 0\n counter = 0\n}\n","import _ from './rAF'\nimport __ from './now'\nimport { run, enqueue, flush } from './schedule'\n\n\nconst perFrame = 16\n\n// options\nvar expectedFrame = perFrame\nvar limit = 1000\nvar count = limit\nvar strategy = 'normal'\nvar perf = 2\nvar autoStop = false\n\nvar isRunning = false\nvar accelerate = 1 // for slow start\n\nvar scriptStart\nvar scriptEnd\nvar styleStart\nvar styleEnd\n\nvar styleDuration\nvar scriptDuration\n\nfunction frame(frameStart) {\n if (!isRunning) return\n // calculate metrix\n styleEnd = frameStart\n styleDuration = styleStart ? (styleEnd - styleStart) : expectedFrame\n scriptDuration = scriptEnd - scriptStart\n\n var inc = true\n var dec = true\n // calculate limit\n if (strategy === 'batch') {\n // will try to batch up all update\n inc =\n (scriptDuration < expectedFrame + 1)\n dec =\n (scriptDuration >= expectedFrame + 1)\n } else {\n inc =\n (styleDuration >= expectedFrame) &&\n (styleDuration < expectedFrame + 1) &&\n (styleDuration !== 0)\n dec =\n (styleDuration >= expectedFrame + 1)\n }\n if (inc) {\n accelerate = accelerate * perf\n count += accelerate\n } else if (dec) {\n accelerate = 1\n count = Math.floor(count / 2)\n /* istanbul ignore next */\n } else if (styleDuration === 0) {\n // This is a skipped frame\n }\n if (count < 1)\n count = 1\n scriptStart = window.performance.now()\n var continueRun = run(count)\n if (!continueRun && autoStop)\n return\n scriptEnd = window.performance.now()\n styleStart = frameStart\n\n window.requestAnimationFrame(frame)\n if (window && window.requestIdleCallback) {\n // For browsers which support requestIdleCallback\n /* istanbul ignore next */\n window.requestIdleCallback(function(deadline) {\n if (deadline.timeRemaining() > 0) {\n var ratio = deadline.timeRemaining() / perFrame\n run(Math.floor(count * ratio))\n }\n })\n }\n}\n\nexport function stop() {\n accelerate = 1 // for slow start\n count = limit\n isRunning = false\n flush()\n}\nexport function start(options = {}) {\n if (!isRunning)\n window.requestAnimationFrame(frame)\n options.limit && (limit = count = options.limit)\n options.expectedFrame && (expectedFrame = options.expectedFrame)\n options.strategy && (strategy = options.strategy)\n options.perf && (perf = options.perf)\n options.autoStop && (autoStop = options.autoStop)\n scriptStart = null\n scriptEnd = null\n styleStart = null\n styleEnd = null\n isRunning = true\n}\nexport function put(priority, callback, times) {\n enqueue(priority, callback, times)\n}\nexport function getCount() {\n return count\n}\n","var Emitter = function() {\n this.uid = 0\n this.handlers = []\n for (let i = 0; i < 1000; i ++) this.handlers.push([])\n}\nEmitter.prototype.on = function(level, callback) {\n this.handlers[level].push({\n id: this.uid ++,\n action: callback\n })\n}\nEmitter.prototype.emit = function(level, ...args) {\n this.handlers[level].forEach(handler => {\n if (typeof(handler.action) === 'function')\n handler.action(...args)\n })\n}\nEmitter.prototype.once = function(level, callback) {\n var id = this.uid\n this.on(level, (...args) => {\n callback(...args)\n var handler = this.handlers[level].find(handler => handler.id === id)\n this.handlers[level].splice(this.handlers[level].indexOf(handler), 1)\n // delete handler\n })\n}\nvar singletonEmitter = new Emitter()\nexport default singletonEmitter\n","// http://paulirish.com/2011/requestanimationframe-for-smart-animating/\n// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating\n\n// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel\n\n// MIT license\n\n;(function() {\n var lastTime = 0\n var vendors = ['ms', 'moz', 'webkit', 'o']\n for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {\n window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']\n window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||\n window[vendors[x] + 'CancelRequestAnimationFrame']\n }\n\n if (!window.requestAnimationFrame)\n window.requestAnimationFrame = function(callback, element) {\n var currTime = new Date().getTime()\n var timeToCall = Math.max(0, 16 - (currTime - lastTime))\n var id = window.setTimeout(function() { callback(currTime + timeToCall); },\n timeToCall)\n lastTime = currTime + timeToCall\n return id\n }\n /* istanbul ignore if */\n if (!window.cancelAnimationFrame)\n window.cancelAnimationFrame = function(id) {\n clearTimeout(id)\n }\n}())\n","// @license http://opensource.org/licenses/MIT\n// copyright Paul Irish 2015\n\n// Date.now() is supported everywhere except IE8. For IE8 we use the Date.now polyfill\n// github.com/Financial-Times/polyfill-service/blob/master/polyfills/Date.now/polyfill.js\n// as Safari 6 doesn't have support for NavigationTiming, we use a Date.now() timestamp for relative values\n\n// if you want values similar to what you'd get with real perf.now, place this towards the head of the page\n// but in reality, you're just getting the delta between now() calls, so it's not terribly important where it's placed\n\n;(function() {\n\n if ('performance' in window == false) {\n window.performance = {}\n }\n /* istanbul ignore next */\n Date.now = (Date.now || function() { // thanks IE8\n return new Date().getTime()\n })\n\n if ('now' in window.performance == false) {\n\n var nowOffset = Date.now()\n /* istanbul ignore next */\n if (window.performance.timing && window.performance.timing.navigationStart) {\n nowOffset = window.performance.timing.navigationStart\n }\n\n window.performance.now = function now() {\n return Date.now() - nowOffset\n }\n }\n\n})()\n","import emitter from './emitter'\n\nimport {\n start,\n stop,\n put,\n getCount\n} from './congestion'\n\n\nvar Leopard = {\n on: emitter.on.bind(emitter),\n once: emitter.once.bind(emitter),\n start,\n stop,\n put,\n get limit() {\n return getCount()\n }\n}\n\nif (typeof exports === 'object')\n module.exports = Leopard\nelse if (typeof define === 'function' && typeof define.amd !== 'undefined')\n define(function() { return Leopard })\nelse\n window.Leopard = Leopard\n"],"sourceRoot":"/source/"} -------------------------------------------------------------------------------- /demo/image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 33 | 34 | 35 |
36 |

37 |

Click any leopard images to see the effect.

38 | Image processing is a expensive calculation. 39 | The example demonstrate how Leopard improves the responsiveness to user ineraction. 40 |

41 |
42 | 43 | 44 |
45 |
46 | 47 | 48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 | 56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 48 | 49 | 50 | 51 | 52 |
53 |

Leopard

54 |

Scheduling tasks

55 |

56 | The example demonstrate a situation that we sometimes need to display a list with dynamics inserted items. 57 | With Leopard, we can schedule the tasks with different priority. Run the tasks with high priority first, make user interaction really smooth. 58 |

59 |
60 |
61 |
62 | 63 |
64 |
65 |
66 | 67 |
68 |
69 |
70 | 71 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /demo/leopard1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenghaoc/leopard/54a88078664f990337d518e3c213718b4b807f99/demo/leopard1.jpg -------------------------------------------------------------------------------- /demo/leopard2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenghaoc/leopard/54a88078664f990337d518e3c213718b4b807f99/demo/leopard2.jpg -------------------------------------------------------------------------------- /demo/leopard3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenghaoc/leopard/54a88078664f990337d518e3c213718b4b807f99/demo/leopard3.jpg -------------------------------------------------------------------------------- /demo/starvation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 43 | 44 | 45 | 46 | 47 |
48 |

Work in progress

49 |

Scheduling tasks with different priority

50 | The example demonstrate that tasks with high priority (such as feedback of user action) will always execute smoothly, even there're many tasks in the scheduler. But tasks with low priority (such as data analysis) may face the situation of starvation (never execute) if there're always some tasks have some higher priority. 51 |
52 |
53 |
54 | Without Leopard 55 | Leopard normal strategy 56 | Leopard batch strategy 57 |
Notice the fps when using and not using Leopard.
58 |
59 |
60 |
61 |
62 |
63 | 65 |
66 |
67 | 68 | 69 |
70 |
71 |
72 |
73 |
1~50
74 |
75 |
76 | 77 |
78 | 79 | 80 | 81 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | var sourcemaps = require('gulp-sourcemaps') 3 | var rollup = require('gulp-rollup') 4 | var babel = require('gulp-babel') 5 | var rename = require('gulp-rename') 6 | var util = require('gulp-util') 7 | var jscs = require('gulp-jscs') 8 | var watch = require('gulp-watch') 9 | var plumber = require('gulp-plumber') 10 | var print = require('gulp-print') 11 | var uglify = require('gulp-uglify'); 12 | var nodeResolve = require('rollup-plugin-node-resolve') 13 | var commonjs = require('rollup-plugin-commonjs') 14 | 15 | gulp.task('compile', () => { 16 | watch('./src/*.js', compile) 17 | return compile() 18 | }) 19 | 20 | function compile() { 21 | return gulp.src('./src/index.js') 22 | .pipe(print(function(filepath) { 23 | return 'built: ' + filepath 24 | })) 25 | .pipe(plumber({ 26 | errorHandler: function(err) { 27 | console.log(err.message) 28 | this.emit('end') 29 | } 30 | })) 31 | .pipe(rollup({ 32 | sourceMap: true 33 | })) 34 | .pipe(babel()) 35 | .on('error', util.log) 36 | .pipe(rename('leopard.js')) 37 | .pipe(gulp.dest('./build')) 38 | .pipe(uglify()) 39 | .pipe(rename('leopard.min.js')) 40 | .pipe(sourcemaps.write('.')) 41 | .pipe(gulp.dest('./build')) 42 | } 43 | 44 | gulp.task('default', ['compile']) 45 | gulp.task('lint', function() { 46 | return gulp.src('src/**') 47 | .pipe(jscs({fix: true, configPath: '.jscsrc'})) 48 | .pipe(gulp.dest('src/')) 49 | }) 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leopard.js", 3 | "version": "0.2.1", 4 | "description": "60 fps pages made easy.", 5 | "main": "build/leopard.js", 6 | "devDependencies": { 7 | "ava": "^0.12.0", 8 | "babel-plugin-external-helpers": "^6.5.0", 9 | "babel-preset-es2015": "^6.6.0", 10 | "babel-preset-es2015-rollup": "^1.1.1", 11 | "babel-register": "^6.6.5", 12 | "babelify": "^7.2.0", 13 | "bower": "~1.4.1", 14 | "browserify": "^13.0.0", 15 | "coveralls": "^2.11.9", 16 | "gulp": "^3.9.1", 17 | "gulp-babel": "^6.1.2", 18 | "gulp-jscs": "^3.0.2", 19 | "gulp-plumber": "^1.1.0", 20 | "gulp-print": "^2.0.1", 21 | "gulp-rename": "^1.2.2", 22 | "gulp-rollup": "^1.8.0", 23 | "gulp-sourcemaps": "^1.6.0", 24 | "gulp-uglify": "^1.5.3", 25 | "gulp-util": "^3.0.7", 26 | "gulp-watch": "^4.3.5", 27 | "jsdom": "^8.1.0", 28 | "nyc": "^6.1.1", 29 | "rollup-plugin-commonjs": "^2.2.1", 30 | "rollup-plugin-includepaths": "^0.1.2", 31 | "rollup-plugin-node-resolve": "^1.5.0", 32 | "vinyl-buffer": "^1.0.0", 33 | "vinyl-source-stream": "^1.1.0", 34 | "watchify": "^3.7.0" 35 | }, 36 | "scripts": { 37 | "test": "nyc ava --serial test/*.js" 38 | }, 39 | "ava": { 40 | "require": [ 41 | "./test/helpers/browser-env.js" 42 | ] 43 | }, 44 | "author": "changbenny", 45 | "license": "MIT" 46 | } 47 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # [Leopard](http://changbenny.github.io/leopard/) 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | > 60 FPS pages made easy. 13 | 14 | Performant, heuristic scheduler for building user interface (Just 4 kb gzipped). 15 | 16 | [Leopard](http://changbenny.github.io/leopard/) eliminates jank from websites by scheduling page interactions automatically. For pages with heavy DOM manipulation, Leopard will batch update related manipulation. For pages with heavy JavaScript calculation, Leopard will schedule the calculation for avoiding jank. 17 | 18 | ## Install 19 | 20 | ```sh 21 | npm install leopard.js 22 | ``` 23 | 24 | ```html 25 | 26 | ``` 27 | 28 | ## Examples 29 | 30 | - [Lists with large amount of items](http://changbenny.github.io/leopard/demo/) 31 | - [Image processing](http://changbenny.github.io/leopard/demo/image.html) 32 | 33 | ## Usage 34 | 35 | #### Schedule user actions with high priority 36 | 37 | ```javascript 38 | target.addEventListener('click', function(e) { 39 | Leopard.put(1, function() { 40 | // feedback of user actions 41 | }) 42 | }) 43 | Leopard.put(10, function() { 44 | // sync the data, upload analysis data, etc. 45 | }) 46 | Leopard.start() 47 | ``` 48 | 49 | #### Listen events 50 | 51 | ```javascript 52 | Leopard.put(2, function() { 53 | console.log('priority 2: task') 54 | }) 55 | Leopard.put(1, function() { 56 | console.log('priority 1: first task') 57 | }) 58 | Leopard.put(1, function() { 59 | console.log('priority 1: second task') 60 | }) 61 | Leopard.on(1, function () { 62 | console.log('priority 1: complete') 63 | }) 64 | Leopard.start() 65 | 66 | ``` 67 | 68 | Output: 69 | 70 | ```javascript 71 | priority 1: first task 72 | priority 1: second task 73 | priority 1: complete 74 | priority 2: task 75 | ``` 76 | 77 | 78 | 79 | ## API 80 | 81 | #### `Leopard.put(priority, callback)` 82 | 83 | `priority` Integer between 0 to 1000, lower value, higher priority. 84 | 85 | Enqueue a task with specific priority to the Scheduler. 86 | 87 | #### `Leopard.start([options])` 88 | 89 | Start Leopard, the [options](#options) is for optional advanced setting. 90 | 91 | #### `Leopard.stop()` 92 | 93 | Stop Leopard and flush the unfinished tasks in the scheduler queue. 94 | 95 | ### Events 96 | 97 | When all tasks in one priority queue finish, Leopard will fire an event. Your can listen to the specific priority queue. 98 | 99 | #### `Leopard.on(priority, callback)` 100 | 101 | #### `Leopard.once(priority, callback)` 102 | 103 | ### Options 104 | 105 | | Name | Type | Usage | Default | 106 | | -------- | ------- | ---------------------------------------- | -------- | 107 | | limit | Integer | The limit of actions can be executed per frame. Leopard will adjust this value based on the browser performance. If each actions in scheduler queue costs many resources, then decrease this option. | 1000 | 108 | | strategy | String | The batching strategy. For pages with heavy DOM manipulation (a lot of page re-layout, re-paint), you should set the option to `batch`. Leopard will batch update and avoid too many page re-paint. Otherwise, keep this option as `normal`, Leopard will schedule those operation for avoiding jank. | 'normal' | 109 | | perf | Float | Tune the performance. Bigger the number, better perfmance , but lower FPS. | 2.0 | 110 | | autoStop | Boolean | automatically stop if there's no tasks in scheduler queue. | false | 111 | 112 | ## Concept and Implementation 113 | 114 | The algorithm for calculating maximum actions per frames is inspired by *TCP congestion control*. Take a look of [`src/congestion.js` ](https://github.com/changbenny/leopard/blob/master/src/congestion.js) for detailed implementation. 115 | 116 | The scheduler for scheduling the prioritised actions is based on *Fixed priority none-preemptive scheduling*. Take a look of [`src/schedule.js`](https://github.com/changbenny/leopard/blob/master/src/schedule.js) for detailed implementation. You specify the priority of tasks by yourself. Be careful about number of tasks you assign to the scheduler. Too many tasks in a scheduler queue will cause [Starvation](https://en.wikipedia.org/wiki/Starvation_(computer_science)). 117 | 118 | 119 | 120 | ## Browser Support 121 | 122 | | Chrome | Firefox | IE | Safari | 123 | | :----- | :------ | :--- | :----- | 124 | | latest | latest | 9+ | latest | 125 | 126 | ## License 127 | 128 | MIT 129 | 130 | 131 | -------------------------------------------------------------------------------- /src/congestion.js: -------------------------------------------------------------------------------- 1 | import { run, enqueue, flush } from './schedule' 2 | // Cause side effects 3 | import './rAF' 4 | import './now' 5 | // 6 | 7 | const perFrame = 16 8 | 9 | // options 10 | var expectedFrame = perFrame 11 | var limit = 1000 12 | var count = limit 13 | var strategy = 'normal' 14 | var perf = 2 15 | var autoStop = false 16 | 17 | var isRunning = false 18 | var accelerate = 1 // for slow start 19 | 20 | var scriptStart 21 | var scriptEnd 22 | var styleStart 23 | var styleEnd 24 | 25 | var styleDuration 26 | var scriptDuration 27 | 28 | /* 29 | * The main entry point: 30 | * We execute frame via requestAnimationFrame, based on previous execution we can 31 | * calculate how many works we can do for this frame. 32 | * Therefore, Leopard is suitable for some consistant operations, such as rendering update. 33 | */ 34 | 35 | function frame(frameStart) { 36 | if (!isRunning) return 37 | // calculate metrix 38 | styleEnd = frameStart 39 | styleDuration = styleStart ? (styleEnd - styleStart) : expectedFrame 40 | scriptDuration = scriptEnd - scriptStart 41 | 42 | var inc = true 43 | var dec = true 44 | // calculate limit 45 | if (strategy === 'batch') { 46 | // will try to batch up all update 47 | inc = 48 | (scriptDuration < expectedFrame + 1) 49 | dec = 50 | (scriptDuration >= expectedFrame + 1) 51 | } else { 52 | inc = 53 | (styleDuration >= expectedFrame) && 54 | (styleDuration < expectedFrame + 1) && 55 | (styleDuration !== 0) 56 | dec = 57 | (styleDuration >= expectedFrame + 1) 58 | } 59 | if (inc) { 60 | accelerate = accelerate * perf 61 | count += accelerate 62 | } else if (dec) { 63 | accelerate = 1 64 | count = Math.floor(count / 2) 65 | /* istanbul ignore next */ 66 | } else if (styleDuration === 0) { 67 | // This is a skipped frame 68 | } 69 | if (count < 1) 70 | count = 1 71 | scriptStart = window.performance.now() 72 | var continueRun = run(count) 73 | if (!continueRun && autoStop) 74 | return 75 | scriptEnd = window.performance.now() 76 | styleStart = frameStart 77 | 78 | window.requestAnimationFrame(frame) 79 | if (window && window.requestIdleCallback) { 80 | // For browsers which support requestIdleCallback 81 | /* istanbul ignore next */ 82 | window.requestIdleCallback(function(deadline) { 83 | if (deadline.timeRemaining() > 0) { 84 | var ratio = deadline.timeRemaining() / perFrame 85 | run(Math.floor(count * ratio)) 86 | } 87 | }) 88 | } 89 | } 90 | 91 | /* 92 | * Public API: 93 | * start, stop, put 94 | * 95 | */ 96 | 97 | export function stop() { 98 | accelerate = 1 // for slow start 99 | count = limit 100 | isRunning = false 101 | flush() 102 | } 103 | export function start(options = {}) { 104 | if (!isRunning) 105 | window.requestAnimationFrame(frame) 106 | options.limit && (limit = count = options.limit) 107 | options.expectedFrame && (expectedFrame = options.expectedFrame) 108 | options.strategy && (strategy = options.strategy) 109 | options.perf && (perf = options.perf) 110 | options.autoStop && (autoStop = options.autoStop) 111 | scriptStart = null 112 | scriptEnd = null 113 | styleStart = null 114 | styleEnd = null 115 | isRunning = true 116 | } 117 | export function put(priority, callback, times) { 118 | enqueue(priority, callback, times) 119 | } 120 | -------------------------------------------------------------------------------- /src/emitter.js: -------------------------------------------------------------------------------- 1 | var Emitter = function() { 2 | this.uid = 0 3 | this.handlers = [] 4 | // 1000 is specific for the level of scheduler 5 | for (let i = 0; i < 1000; i ++) this.handlers.push([]) 6 | } 7 | Emitter.prototype.on = function(level, callback) { 8 | this.handlers[level].push({ 9 | id: this.uid ++, 10 | action: callback 11 | }) 12 | } 13 | Emitter.prototype.emit = function(level, ...args) { 14 | this.handlers[level].forEach(handler => { 15 | if (typeof(handler.action) === 'function') 16 | handler.action(...args) 17 | }) 18 | } 19 | Emitter.prototype.once = function(level, callback) { 20 | var id = this.uid 21 | this.on(level, (...args) => { 22 | callback(...args) 23 | var handler = this.handlers[level].find(handler => handler.id === id) 24 | this.handlers[level].splice(this.handlers[level].indexOf(handler), 1) 25 | // delete handler 26 | }) 27 | } 28 | var singletonEmitter = new Emitter() 29 | export default singletonEmitter 30 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import emitter from './emitter' 2 | 3 | import { 4 | start, 5 | stop, 6 | put, 7 | getCount 8 | } from './congestion' 9 | 10 | 11 | var Leopard = { 12 | on: emitter.on.bind(emitter), 13 | once: emitter.once.bind(emitter), 14 | start, 15 | stop, 16 | put 17 | } 18 | 19 | if (typeof exports === 'object') 20 | module.exports = Leopard 21 | else if (typeof define === 'function' && typeof define.amd !== 'undefined') 22 | define(function() { return Leopard }) 23 | else 24 | window.Leopard = Leopard 25 | -------------------------------------------------------------------------------- /src/now.js: -------------------------------------------------------------------------------- 1 | // @license http://opensource.org/licenses/MIT 2 | // copyright Paul Irish 2015 3 | 4 | // Date.now() is supported everywhere except IE8. For IE8 we use the Date.now polyfill 5 | // github.com/Financial-Times/polyfill-service/blob/master/polyfills/Date.now/polyfill.js 6 | // as Safari 6 doesn't have support for NavigationTiming, we use a Date.now() timestamp for relative values 7 | 8 | // if you want values similar to what you'd get with real perf.now, place this towards the head of the page 9 | // but in reality, you're just getting the delta between now() calls, so it's not terribly important where it's placed 10 | 11 | ;(function() { 12 | 13 | if ('performance' in window == false) { 14 | window.performance = {} 15 | } 16 | /* istanbul ignore next */ 17 | Date.now = (Date.now || function() { // thanks IE8 18 | return new Date().getTime() 19 | }) 20 | 21 | if ('now' in window.performance == false) { 22 | 23 | var nowOffset = Date.now() 24 | /* istanbul ignore next */ 25 | if (window.performance.timing && window.performance.timing.navigationStart) { 26 | nowOffset = window.performance.timing.navigationStart 27 | } 28 | 29 | window.performance.now = function now() { 30 | return Date.now() - nowOffset 31 | } 32 | } 33 | 34 | })() 35 | -------------------------------------------------------------------------------- /src/rAF.js: -------------------------------------------------------------------------------- 1 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 2 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating 3 | 4 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel 5 | 6 | // MIT license 7 | 8 | ;(function() { 9 | var lastTime = 0 10 | var vendors = ['ms', 'moz', 'webkit', 'o'] 11 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 12 | window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'] 13 | window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || 14 | window[vendors[x] + 'CancelRequestAnimationFrame'] 15 | } 16 | 17 | if (!window.requestAnimationFrame) 18 | window.requestAnimationFrame = function(callback, element) { 19 | var currTime = new Date().getTime() 20 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)) 21 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 22 | timeToCall) 23 | lastTime = currTime + timeToCall 24 | return id 25 | } 26 | /* istanbul ignore if */ 27 | if (!window.cancelAnimationFrame) 28 | window.cancelAnimationFrame = function(id) { 29 | clearTimeout(id) 30 | } 31 | }()) 32 | -------------------------------------------------------------------------------- /src/schedule.js: -------------------------------------------------------------------------------- 1 | import emitter from './emitter' 2 | 3 | var queue = [] 4 | var counter = 0 5 | var levels = 1000 6 | 7 | for (let i = 0; i < levels; i ++) queue.push([]) 8 | 9 | export function run(count) { 10 | for (var i = 0; i < queue.length; i ++) { 11 | if (count < 1) break 12 | var level = queue[i] 13 | while (level.length) { 14 | if (count < 1) break 15 | counter -- 16 | // the bigger of level, the less emergent to complete 17 | // So we deduce more for higher level (lower priority) actions 18 | count -- 19 | var callback = level.shift() 20 | if (callback && typeof callback === 'function') callback() 21 | if (!level.length) { 22 | emitter.emit(i) 23 | } 24 | } 25 | /* istanbul ignore if */ 26 | if (i === queue.length - 1 && counter === 0) { 27 | return false 28 | } 29 | } 30 | return true 31 | } 32 | export function enqueue(priority, callback, times) { 33 | if (!times) { 34 | queue[priority].push(callback) 35 | counter ++ 36 | } else { 37 | while (times--) { 38 | queue[priority].push(callback) 39 | counter ++ 40 | } 41 | } 42 | } 43 | export function flush() { 44 | for (let i = 0; i < levels; i ++) queue[i].length = 0 45 | counter = 0 46 | } 47 | -------------------------------------------------------------------------------- /test/congestion.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import 'babel-register' 3 | import { 4 | start, 5 | stop, 6 | put, 7 | getCount 8 | } from '../src/congestion' 9 | import emitter from '../src/emitter' 10 | 11 | test.beforeEach(t => { 12 | 13 | }) 14 | test.afterEach(t => { 15 | stop() 16 | }) 17 | 18 | test('start(options)', t => { 19 | start({ 20 | limit: 100 21 | }) 22 | t.is(getCount(), 100) 23 | }) 24 | 25 | test.cb('put() and start(): multiple times', t => { 26 | var results = [] 27 | put(2, function() { 28 | results.push(1) 29 | }, 200) 30 | 31 | start() 32 | t.is(results.length, 0) 33 | setTimeout(() => { 34 | t.true(results.length <= 200) 35 | t.end() 36 | }, 16) 37 | }) 38 | 39 | test.cb('put() and start(): single time', t => { 40 | var results = [] 41 | put(1, function() { 42 | results.push(1) 43 | }) 44 | 45 | start() 46 | t.is(results.length, 0) 47 | setTimeout(() => { 48 | t.is(results.length, 1) 49 | t.end() 50 | }, 16) 51 | }) 52 | 53 | test.cb('put() and start(): limit < 1', t => { 54 | var results = [] 55 | put(1, function() { 56 | results.push(1) 57 | }) 58 | 59 | start({ 60 | limit: -100 61 | }) 62 | t.is(results.length, 0) 63 | setTimeout(() => { 64 | t.is(results.length, 1) 65 | t.end() 66 | }, 16) 67 | }) 68 | 69 | test.cb('put() and start(): multiple multiple times', t => { 70 | var results = [] 71 | put(3, function() { 72 | results.push(1) 73 | }, 20000) 74 | 75 | start() 76 | t.is(results.length, 0) 77 | setTimeout(() => { 78 | t.true(results.length < 200) 79 | t.end() 80 | }, 16) 81 | }) 82 | 83 | test.cb('put() and start(): long frame with script strategy', t => { 84 | var results = [] 85 | put(1, function() { 86 | var times = 1000000 87 | while (times --) 88 | results.push(1) 89 | }, 2) 90 | 91 | // force the limit to become 1 92 | start({ 93 | limit: -10000, 94 | strategy: 'script' 95 | }) 96 | t.is(results.length, 0) 97 | setTimeout(() => { 98 | t.true(results.length <= 2000000) 99 | t.end() 100 | }, 16) 101 | }) 102 | 103 | test.cb('put() and start(): long frame with batch strategy, limit will decrease if janky', t => { 104 | var results = [] 105 | put(1, function() { 106 | var times = 1000000 107 | while (times --) 108 | results.push(1) 109 | }, 10) 110 | 111 | // force the limit to become 1 112 | start({ 113 | limit: -10000, 114 | strategy: 'batch' 115 | }) 116 | t.is(results.length, 0) 117 | setTimeout(() => { 118 | t.true(results.length <= 10000000) 119 | t.end() 120 | }, 100) 121 | }) 122 | 123 | test.cb('put() and start(): auto stop', t => { 124 | var results = [] 125 | put(1, function() { 126 | var times = 10 127 | while (times --) 128 | results.push(1) 129 | }) 130 | 131 | // force the limit to become 1 132 | start({ 133 | autoStop: true 134 | }) 135 | t.is(results.length, 0) 136 | setTimeout(() => { 137 | put(1, function() { 138 | var times = 10 139 | while (times --) 140 | results.push(1) 141 | }) 142 | }, 200) 143 | setTimeout(() => { 144 | t.is(results.length, 10) 145 | t.end() 146 | }, 400) 147 | }) 148 | 149 | test.cb('emitter once()', t => { 150 | var results = [] 151 | put(500, function() { 152 | results.push(1) 153 | }) 154 | emitter.once(500, function() { 155 | results.push(1) 156 | }) 157 | start() 158 | setTimeout(() => { 159 | t.is(results.length, 2) 160 | t.end() 161 | }, 50) 162 | }) 163 | -------------------------------------------------------------------------------- /test/helpers/browser-env.js: -------------------------------------------------------------------------------- 1 | global.document = require('jsdom').jsdom(''); 2 | global.window = document.defaultView; 3 | global.navigator = window.navigator; 4 | --------------------------------------------------------------------------------