├── README.md ├── requestAnimationFrame.js ├── timer-es5.js └── timer.js /README.md: -------------------------------------------------------------------------------- 1 | # timer 2 | 一个基于 RAF 的时间管理工具,它的APIs 如下: 3 | 4 | | name | type | syntax | detail | 5 | | :-- | :-- | :-- | :-- | 6 | | setTimeout | Function | let setTimeoutID = timer.setTimeout(fun, delay[, id]) | 替代原生setTimeout,第三个参数表示指定一个有意义的setTimeoutID | 7 | | clearTimeout | Function | timer.clearTimeout(setTimeoutID1, setTimeoutID2, ...) | 清除timer.setTimeout | 8 | | setInterval | Function | let setIntervalID = timer.setInterval(fun, delay[, id]) | 替代原生setInterval,第三个参数表示指定一个有意义的setIntervalID | 9 | | clearInterval | Function | timer.clearInterval(setIntervalID1, setIntervalID2, ...) | 清除timer.clearInterval | 10 | | delete | Function | timer.delete(id1, id2, ...) | 相当于clearTimeout & clearInterval | 11 | | pause | Function | timer.pause(setTimeoutID/setIntervalID) | 暂停指定ID的计时,如果没指定ID表示暂停所有计时 | 12 | | resume | Function | timer.resume(setTimeoutID/setIntervalID) | 恢复指定ID的计时,如果没指定ID表示恢复所有计时 | 13 | | play | Function | timer.play(setTimeoutID/setIntervalID) | 同 resume | 14 | | pauseAll | Function | timer.pauseAll() | 暂停所有计时 | 15 | | playAll | Function | timer.playAll() | 恢复所有计时 | 16 | | clean | Function | timer.clean() | 清空所有计时 | 17 | | set | Function | timer.set(id, {fn, delay}) | 重置timer的回调函数与delay | 18 | | reset | Function | timer.reset(id) | 调用reset后,指定ID的计时会被置零 | 19 | | resetAll | Function | timer.resetAll() | 调用resetAll后,所有计时会被置零 | 20 | | useRAF | Boolean | timer.useRAF = true / false | true 表示启用自身RAF,false 反之。与第三方ticker结合时,timer 会自动切换 | 21 | 22 | `timer-es5.js` 只是 [https://github.com/leeenx/es6-utils/blob/master/modules/timer.js](https://github.com/leeenx/es6-utils/blob/master/modules/timer.js) 的 ES5 版本。 23 | 24 | 如果不支持 RAF 的浏览器,需要自己添加一个 RAF 的 poly-fill。像我直接使用以下代码: 25 | 26 | ```javascript 27 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 28 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating 29 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel 30 | // MIT license 31 | (function() { 32 | var lastTime = 0; 33 | var vendors = ['ms', 'moz', 'webkit', 'o']; 34 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 35 | window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; 36 | window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']; 37 | } 38 | if (!window.requestAnimationFrame) window.requestAnimationFrame = function(callback, element) { 39 | var currTime = new Date().getTime(); 40 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 41 | var id = window.setTimeout(function() { 42 | callback(currTime + timeToCall); 43 | }, timeToCall); 44 | lastTime = currTime + timeToCall; 45 | return id; 46 | }; 47 | if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function(id) { 48 | clearTimeout(id); 49 | }; 50 | }()); 51 | ``` 52 | 53 | 54 | -------------------------------------------------------------------------------- /requestAnimationFrame.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 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel 4 | // MIT license 5 | (function() { 6 | var lastTime = 0; 7 | var vendors = ['ms', 'moz', 'webkit', 'o']; 8 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 9 | window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; 10 | window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']; 11 | } 12 | if (!window.requestAnimationFrame) window.requestAnimationFrame = function(callback, element) { 13 | var currTime = new Date().getTime(); 14 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 15 | var id = window.setTimeout(function() { 16 | callback(currTime + timeToCall); 17 | }, timeToCall); 18 | lastTime = currTime + timeToCall; 19 | return id; 20 | }; 21 | if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function(id) { 22 | clearTimeout(id); 23 | }; 24 | }()); 25 | -------------------------------------------------------------------------------- /timer-es5.js: -------------------------------------------------------------------------------- 1 | function Timer() { 2 | // 构造函数 3 | this.constructor = function() { 4 | // 暂停状态 - 这是个公共状态,由外部 Ticker 决定。 5 | this.paused = true; 6 | 7 | // 队列 8 | this.queue = new this.Map(); 9 | 10 | // 正在使用 timer 的 RAF 11 | this.usingRAF = false; 12 | 13 | // useRAF 触发器 14 | Object.defineProperty(this, "useRAF", { 15 | set: function(value) { 16 | this.usingRAF = value; 17 | value ? Timer.RAF.enable() : Timer.RAF.disable(); 18 | } 19 | }); 20 | } 21 | 22 | var that = this; 23 | 24 | // 创建一个 Symbol 25 | this.Symbol = function() { 26 | return "Symbol_" + new Date().getTime() + "_" + this.Symbol.id++; 27 | } 28 | 29 | // 添加一个 id 30 | this.Symbol.id = 0; 31 | 32 | // 创建一个 Map 33 | this.Map = function() { 34 | var map = {}; 35 | 36 | this.set = function (key, value) { 37 | map[key] = value; 38 | return true; 39 | } 40 | 41 | this.get = function(key) { 42 | if(map[key] !== undefined) return map[key]; 43 | } 44 | 45 | this.delete = function(key) { 46 | if(map[key] !== undefined) { 47 | delete map[key]; 48 | return true; 49 | } 50 | } 51 | 52 | this.forEach = function(fn) { 53 | for(var key in map) { 54 | fn(map[key], key); 55 | } 56 | } 57 | this.map = map; 58 | } 59 | 60 | // setTimeout 的实现 61 | this.setTimeout = function(fn, delay, id) { 62 | id = id || this.Symbol("timeoutID"); 63 | // 存入队列 64 | this.queue.set(id, { 65 | fn: fn, 66 | type: 0, 67 | paused: 0, 68 | elapsed: 0, 69 | delay: delay 70 | }); 71 | return id; 72 | } 73 | 74 | // clearTimeout 75 | this.clearTimeout = function(id) { 76 | return this.delete(id); 77 | } 78 | 79 | // setInterval 的实现 80 | this.setInterval = function(fn, delay, id) { 81 | id = id || this.Symbol("intervalID"); 82 | // 存入队列 83 | this.queue.set(id, { 84 | fn: fn, 85 | type: 1, 86 | paused: 0, 87 | elapsed: 0, 88 | delay: delay 89 | }); 90 | return id; 91 | } 92 | 93 | // clearInterval 94 | this.clearInterval = function(id) { 95 | return this.delete(id); 96 | } 97 | 98 | // 修改指定id的 delay/fn 99 | this.set = function(id, config) { 100 | config = config || {}; 101 | var item = this.queue.get(id) || {}; 102 | for(var key in config) { 103 | item[key] = config[key]; 104 | } 105 | return true; 106 | } 107 | 108 | // 删除 queue 上的成员 109 | this.delete = function(id) { 110 | return this.queue.delete(id); 111 | } 112 | 113 | // 暂停指定id 114 | this.pause = function(id) { 115 | id === undefined ? this.pauseAll() : ((this.queue.get(id) || {}).paused = 1); 116 | return true; 117 | } 118 | 119 | // 恢复指定id 120 | this.resume = function(id) { 121 | return this.play(id); 122 | } 123 | 124 | // 播放指定id 125 | this.play = function(id) { 126 | id === undefined ? this.playAll() : ((this.queue.get(id) || {}).paused = 0); 127 | return true; 128 | } 129 | 130 | // 清空timer 131 | this.clean = function() { 132 | this.queue = new this.Map(); 133 | return true; 134 | } 135 | 136 | // 暂停全部 id 137 | this.pauseAll = function() { 138 | this.queue.forEach(function(item) {item.paused = 1}); 139 | return true; 140 | } 141 | 142 | // 播放全部 id 143 | this.playAll = function() { 144 | this.queue.forEach(function(item) {item.paused = 0}); 145 | return true; 146 | } 147 | 148 | // 重置 elapsed 为 0 149 | this.reset = function(id) { 150 | id === undefined ? this.resetAll() : ((this.queue.get(id) || {}).elapsed = 0); 151 | } 152 | 153 | // 重置所有的 elapsed 为 0 154 | this.resetAll = function() { 155 | this.queue.forEach(function(item) {item.elapsed = 0}); 156 | } 157 | 158 | // tick 159 | this.tick = function(delta) { 160 | this.paused || this.updateQueue(delta); 161 | } 162 | 163 | // 更新 map 队列 164 | this.updateQueue = function(delta) { 165 | this.queue.forEach(function(item, id) { 166 | if(item.paused === 1) return; 167 | item.elapsed += delta; 168 | if(item.elapsed >= item.delay) { 169 | item.fn(); 170 | item.type === 0 ? that.delete(id) : (item.elapsed = 0); 171 | } 172 | }); 173 | } 174 | 175 | // 状态更新 176 | this.update = function() { 177 | // 第一次调用 update 时主动停用原生接口 178 | this.useRAF = false; 179 | 180 | // 下面是真正的 update 181 | this.update = function(delta) { 182 | if(this.usingRAF) return; 183 | this.tick(delta); 184 | } 185 | } 186 | 187 | // 操行构造函数 188 | this.constructor(); 189 | } 190 | 191 | function AnimationFrame() { 192 | this.constructor = function() { 193 | this.time = 0; 194 | this.auto = this.auto.bind(this); 195 | } 196 | this.auto = function(elapsed) { 197 | timer.tick(elapsed - this.time); 198 | this.time = elapsed; 199 | this.id = requestAnimationFrame(this.auto); 200 | } 201 | this.enable = function() { 202 | timer.paused = false; 203 | this.id = requestAnimationFrame(this.auto); 204 | } 205 | this.disable = function() { 206 | cancelAnimationFrame(this.id); 207 | } 208 | // 执行构造函数 209 | this.constructor(); 210 | } 211 | 212 | // 原生RAF 213 | Timer.RAF = new AnimationFrame(); 214 | 215 | // 对外接口 216 | var timer = new Timer(); 217 | 218 | // 默认使用原生 RAF 219 | timer.useRAF = true; 220 | -------------------------------------------------------------------------------- /timer.js: -------------------------------------------------------------------------------- 1 | /* 2 | author: leeenx 3 | @ timer 对象 4 | @ 提供 7 个API如下: 5 | @ timer.setTimeout(fun, delay[, id]) 6 | @ timer.clearTimeout(id) 7 | @ timer.setInterval(fun, delay[, id]) 8 | @ timer.clearInterval(id) 9 | @ timer.delete(id) 删除对应id的timeout/interval 10 | @ timer.pause(id) 暂停对应id的timeout/interval 11 | @ timer.resume(id) 恢复对应id的timeout/interval 12 | @ timer.clean() 清空所有 timeout & interval 13 | */ 14 | 15 | class AnimationFrame { 16 | auto = (elapsed, timer) => { 17 | timer.tick(elapsed - timer.stamp) 18 | timer.stamp = elapsed 19 | timer.id = requestAnimationFrame( 20 | elapsed => this.auto(elapsed, timer) 21 | ) 22 | } 23 | enable (timer) { 24 | timer.paused = false 25 | timer.stamp = 0 26 | timer.id = requestAnimationFrame( 27 | elapsed => this.auto(elapsed, timer) 28 | ) 29 | } 30 | disable (timer) { 31 | cancelAnimationFrame(timer.id) 32 | } 33 | } 34 | 35 | // 原生RAF 36 | const RAF = new AnimationFrame() 37 | 38 | export class Timer { 39 | // 构造函数 40 | constructor () { 41 | // 暂停状态 - 这是个公共状态,由外部 Ticker 决定 42 | this.paused = true 43 | 44 | // 队列 45 | this.queue = new Map() 46 | 47 | // 开启 RAF 48 | RAF.enable(this) 49 | } 50 | 51 | // setTimeout 的实现 52 | setTimeout (fn, delay, id = Symbol('timeoutID')) { 53 | // 存入队列 54 | this.queue.set(id, { 55 | fn, 56 | type: 0, 57 | paused: 0, 58 | elapsed: 0, 59 | delay 60 | }) 61 | return id 62 | } 63 | 64 | // clearTimeout 65 | clearTimeout (...ids) { 66 | return this.delete(...ids) 67 | } 68 | 69 | // setInterval 的实现 70 | setInterval (fn, delay, id = Symbol('intervalID')) { 71 | // 存入队列 72 | this.queue.set(id, { 73 | fn, 74 | type: 1, 75 | paused: 0, 76 | elapsed: 0, 77 | delay 78 | }) 79 | return id 80 | } 81 | 82 | // clearInterval 83 | clearInterval (...ids) { 84 | return this.delete(...ids) 85 | } 86 | 87 | // 修改指定id的 delay/fn 88 | set (id, config = {}) { 89 | const item = this.queue.get(id) || {} 90 | for (const key in config) { 91 | item[key] = config[key] 92 | } 93 | return true 94 | } 95 | 96 | // 删除 queue 上的成员 97 | delete (...ids) { 98 | return ids.every(id => this.queue.delete(id)) 99 | } 100 | 101 | // 暂停指定id 102 | pause (id) { 103 | if (!id) { 104 | this.pauseAll() 105 | } else { 106 | const item = this.queue.get(id) 107 | if (item) item.paused = 1 108 | } 109 | return true 110 | } 111 | 112 | // 恢复指定id 113 | resume (id) { 114 | return this.play(id) 115 | } 116 | 117 | // 播放指定id 118 | play (id) { 119 | if (!id) { 120 | this.playAll() 121 | } else { 122 | const item = this.queue.get(id) 123 | if (item) item.paused = 0 124 | } 125 | return true 126 | } 127 | 128 | // 清空timer 129 | clean () { 130 | this.queue = new Map() 131 | return true 132 | } 133 | 134 | // 暂停全部 id 135 | pauseAll () { 136 | this.queue.forEach( 137 | item => (item.paused = 1) 138 | ) 139 | return true 140 | } 141 | 142 | // 播放全部 id 143 | playAll () { 144 | this.queue.forEach( 145 | item => (item.paused = 0) 146 | ) 147 | return true 148 | } 149 | 150 | // 重置 elapsed 为 0 151 | reset (id) { 152 | if (!id) { 153 | this.resetAll() 154 | } else { 155 | const item = this.queue.get(id) 156 | if (item) item.elapsed = 0 157 | } 158 | } 159 | 160 | // 重置所有的 elapsed 为 0 161 | resetAll () { 162 | this.queue.forEach( 163 | item => (item.elapsed = 0) 164 | ) 165 | } 166 | 167 | // tick 168 | tick (delta) { 169 | this.paused || this.updateQueue(delta) 170 | } 171 | 172 | // 更新 map 队列 173 | updateQueue (delta) { 174 | this.queue.forEach((item, id) => { 175 | if (item.paused === 1) return 176 | item.elapsed += delta 177 | if (item.elapsed >= item.delay) { 178 | item.fn() 179 | item.type === 0 ? this.delete(id) : (item.elapsed = 0) 180 | } 181 | }) 182 | } 183 | } 184 | 185 | // 对外接口 186 | const timer = new Timer() 187 | 188 | // 导出timer 189 | export default timer 190 | --------------------------------------------------------------------------------