命令模式
/** * Created by shangkuikui on 2017/4/26. */ var tween = { linear: function (t, b, c, d) { return c * t / d + b; }, easeIn: function (t, b, c, d) { return c * ( t /= d ) * t + b; }, strongEaseIn: function (t, b, c, d) { return c * ( t /= d ) * t * t * t * t + b; }, strongEaseOut: function (t, b, c, d) { return c * ( ( t = t / d - 1) * t * t * t * t + 1 ) + b; }, sineaseIn: function (t, b, c, d) { return c * ( t /= d) * t * t + b; }, sineaseOut: function (t, b, c, d) { return c * ( ( t = t / d - 1) * t * t + 1 ) + b; } }; var Animate = function (dom) { this.dom = dom; // 进行运动的dom 节点 this.startTime = 0; // 动画开始时间 this.startPos = 0; // 动画开始时,dom 节点的位置,即dom 的初始位置 this.endPos = 0; // 动画结束时,dom 节点的位置,即dom 的目标位置 this.propertyName = null; // dom 节点需要被改变的css 属性名 this.easing = null; // 缓动算法 this.duration = null; // 动画持续时间 }; Animate.prototype.start = function (propertyName, endPos, duration, easing, cb) { this.startTime = +new Date; // 动画启动时间 this.startPos = this.dom.getBoundingClientRect()[propertyName]; // dom 节点初始位置 this.propertyName = propertyName; // dom 节点需要被改变的CSS 属性名 this.endPos = endPos; // dom 节点目标位置 this.duration = duration; // 动画持续事件 this.easing = tween[easing]; // 缓动算法 var self = this; var timeId = setInterval(function () { // 启动定时器,开始执行动画 if (self.step() === false) { // 如果动画已结束,则清除定时器 clearInterval(timeId); Event.trigger('finish'); if(cb && typeof cb === "function" ){ cb(); } } }, 19); }; Animate.prototype.step = function () { var t = +new Date; // 取得当前时间 if (t >= this.startTime + this.duration) { // (1) this.update(this.endPos); // 更新小球的CSS 属性值 return false; } var pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration); // pos 为小球当前位置 this.update(pos); // 更新小球的CSS 属性值 }; Animate.prototype.update = function( pos ){ this.dom.style[ this.propertyName ] = pos + 'px'; };
var Event = (function () { var global = this, Event, _default = 'default'; Event = function () { var _listen, _trigger, _remove, _slice = Array.prototype.slice, _shift = Array.prototype.shift, _unshift = Array.prototype.unshift, namespaceCache = {}, _create, find, each = function (ary, fn) { var ret; for (var i = 0, l = ary.length; i < l; i++) { var n = ary[i]; ret = fn.call(n, i, n); } return ret; }; _listen = function (key, fn, cache) { if (!cache[key]) { cache[key] = []; } cache[key].push(fn); }; _remove = function (key, cache, fn) { if (cache[key]) { if (fn) { for (var i = cache[key].length; i >= 0; i--) { if (cache[key][i] === fn) { cache[key].splice(i, 1); } } } else { cache[key] = []; } } }; _trigger = function () { var cache = _shift.call(arguments), key = _shift.call(arguments), args = arguments, _self = this, ret, stack = cache[key]; if (!stack || !stack.length) { return; } return each(stack, function () { return this.apply(_self, args); }); }; _create = function (namespace) { var namespace = namespace || _default; var cache = {}, offlineStack = [], // 离线事件 ret = { listen: function (key, fn, last) { _listen(key, fn, cache); if (offlineStack === null) { return; } if (last === 'last') { offlineStack.length && offlineStack.pop()(); } else { each(offlineStack, function () { this(); }); } offlineStack = null; }, one: function (key, fn, last) { _remove(key, cache); this.listen(key, fn, last); }, remove: function (key, fn) { _remove(key, cache, fn); }, trigger: function () { var fn, args, _self = this; _unshift.call(arguments, cache); args = arguments; fn = function () { return _trigger.apply(_self, args); }; if (offlineStack) { return offlineStack.push(fn); } return fn(); } }; return namespace ? ( namespaceCache[namespace] ? namespaceCache[namespace] : namespaceCache[namespace] = ret ) : ret; }; return { create: _create, one: function (key, fn, last) { var event = this.create(); event.one(key, fn, last); }, remove: function (key, fn) { var event = this.create(); event.remove(key, fn); }, listen: function (key, fn, last) { var event = this.create(); event.listen(key, fn, last); }, trigger: function () { var event = this.create(); event.trigger.apply(this, arguments); } }; }(); return Event; })();
以上是文章中用到的两个js文件,event.js 和 animate.js
1普通青年版(初级命令模式)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <button id="bt1">按钮1</button> <button id="bt2">按钮2</button> <button id="bt3">按钮3</button> <div style="position:absolute;background:blue" id="div">我是div</div> </body> <script src="animate.js"></script> <script> const bt1 = document.querySelector('#bt1'); const bt2 = document.querySelector('#bt2'); const bt3 = document.querySelector('#bt3'); const func= { fn1:function () { var div = document.getElementById( 'div' ); var animate = new Animate( div ); animate.start( 'left', 500, 1000, 'sineaseIn' ); console.log('fn1') }, fn2:function () { console.log('fn2') }, fn3:function () { console.log('fn3') } }; let Acommond = function (recevier) { return{ execute () { recevier.fn1() } } } let Bcommond = function (recevier) { return{ execute () { recevier.fn2() } } } let acommond = new Acommond(func); let bcommond = new Bcommond(func); const setCommand= function (bt,commond) { bt.onclick= function () { commond.execute(); } } setCommand(bt1,acommond); setCommand(bt2,bcommond); </script> </html>
2文艺青年版(+撤销功能)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <button id="moveBtn">移动</button> <button id="cancelBtn">撤销</button> <input type="text" id="pos"> <div id="ball" style="position:absolute;background:#000;width:50px;height:50px"></div> </body> <script src="animate.js"></script> <script> const ball = document.getElementById( 'ball' ); const pos = document.getElementById( 'pos' ); const moveBtn = document.getElementById( 'moveBtn' ); const cancelBtn = document.getElementById( 'cancelBtn' ); /*moveBtn.onclick = function(){ var animate = new Animate( ball ); animate.start( 'left', pos.value, 1000, 'strongEaseOut' ); };*/ const setCommand= function (tar,commond) { tar.onclick= function () { commond.execute(); } } const MoveCommand = function( receiver, pos ){ this.receiver = receiver; this.pos = pos; this.oldpos = null; }; MoveCommand.prototype.execute = function(){ //let that = this; this.receiver.start( 'left', this.pos, 1000, 'strongEaseOut' ); this.oldPos = this.receiver.dom.getBoundingClientRect()[ this.receiver.propertyName ]; }; MoveCommand.prototype.undo = function(){ this.receiver.start( 'left', this.oldPos, 1000, 'strongEaseOut' ); // 回到小球移动前记录的位置 }; //setCommand(moveBtn,moveCommand); let moveCommand; moveBtn.onclick =function () { const animate = new Animate(ball); moveCommand = new MoveCommand(animate , pos.value); moveCommand.execute(); } cancelBtn.onclick =function () { moveCommand.undo(); } </script> </html>
3文艺青年豪华版(+回放(队列功能)模式,使用回调方式通知)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <button id="moveBtn">移动</button> <button id="cancelBtn">撤销</button> <button id="reply">播放</button> <input type="text" id="pos"> <div id="ball" style="position:absolute;background:#000;width:50px;height:50px"></div> </body> <script src="animate.js"></script> <script> const ball = document.getElementById('ball'); const pos = document.getElementById('pos'); const moveBtn = document.getElementById('moveBtn'); const cancelBtn = document.getElementById('cancelBtn'); const replyBtn = document.getElementById('reply'); const MoveCommand = function (receiver, pos) { this.receiver = receiver; this.pos = pos; this.oldpos = null; }; MoveCommand.prototype.execute = function (cb) { //let that = this; this.receiver.start('left', this.pos, 1000, 'strongEaseOut', cb); this.oldPos = this.receiver.dom.getBoundingClientRect()[this.receiver.propertyName]; }; let commandStack = []; // 保存命令的堆栈 MoveCommand.prototype.undo = function () { this.receiver.start('left', this.oldPos, 1000, 'strongEaseOut'); // 回到小球移动前记录的位置 }; let moveCommand; moveBtn.onclick = function () { const animate = new Animate(ball); moveCommand = new MoveCommand(animate, pos.value); moveCommand.execute(); commandStack.push(moveCommand); //console.log(commandStack); } cancelBtn.onclick = function () { moveCommand.undo(); } //目前作为演示 只是做了三个函数回放的演示 replyBtn.onclick = function () { let commond; commond = commandStack.shift(); commond.execute(function () { commond = commandStack.shift(); commond.execute(function () { commond = commandStack.shift(); commond.execute(function () { }) }) }); } </script> </html>
4文艺青年豪华加长版(+回放(队列功能)模式,使用广播方式通知)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <button id="moveBtn">移动</button> <button id="cancelBtn">撤销</button> <button id="reply">播放</button> <input type="text" id="pos"> <div id="ball" style="position:absolute;background:#000;width:50px;height:50px"></div> </body> <script src="Event.js"></script> <script src="animate.js"></script> <script> const ball = document.getElementById('ball'); const pos = document.getElementById('pos'); const moveBtn = document.getElementById('moveBtn'); const cancelBtn = document.getElementById('cancelBtn'); const replyBtn = document.getElementById('reply'); const MoveCommand = function (receiver, pos) { this.receiver = receiver; this.pos = pos; this.oldpos = null; }; MoveCommand.prototype.execute = function (cb) { //let that = this; this.receiver.start('left', this.pos, 1000, 'strongEaseOut', cb); this.oldPos = this.receiver.dom.getBoundingClientRect()[this.receiver.propertyName]; }; let commandStack = []; // 保存命令的堆栈 let historyStack = []; // 保存命令的历史堆栈 MoveCommand.prototype.undo = function () { this.receiver.start('left', this.oldPos, 1000, 'strongEaseOut'); // 回到小球移动前记录的位置 }; let moveCommand; moveBtn.onclick = function () { const animate = new Animate(ball); moveCommand = new MoveCommand(animate, pos.value); commandStack.push(moveCommand); historyStack.push(moveCommand); } cancelBtn.onclick = function () { moveCommand.undo(); } replyBtn.onclick = function () { historyloop(historyStack); } /** * 用于时刻从命令队列里取出命令执行 * @param Stack 命令队列 */ function eventloop(Stack) { let continued = true; setInterval(function () { if (continued) { let tempcommond = Stack.shift(); tempcommond && tempcommond.execute(); tempcommond && (continued = false); } }, 10); Event.listen('finish', function () { continued = true; console.log('finish') }) }; eventloop(commandStack); /** * 用于播放历史命令队列 * @param Stack 命令队列 */ function historyloop(Stack) { let continued = true; let timer = setInterval(function () { if (continued) { let tempcommond = Stack.shift(); tempcommond && tempcommond.execute(); tempcommond && (continued = false); if(!tempcommond){ clearInterval(timer); } } }, 100); Event.listen('finish', function () { continued = true; console.log('finish') }) } </script> </html>