2013.08.28更新:
此次更新是修復上一次更新的遺留問題,即"有返回值的函數無法得到正確的返回值",比如getImageData,isPointInPath等,這個問題是因為為了實現鏈式語法,函數會總是返回this.其實要修復這個問題是很簡單的,就是判斷函數執行后是否有返回值,有的話就返回這個返回值,沒有就繼續返回this(大多數情況下都沒有).
不過由於這個判斷的原因,可能會對整體效率有那么一點點的影響;另外,在使用有返回值的函數后,后續就不能繼續鏈式語法了.
另外我把原來的用來放函數名的數組變成了一個字符串,因為這樣可以少寫很多引號.

var XtendCanvas = function () { var pro = 'save,restore,scale,rotate,translate,transform,createLinearGradient,createRadialGradient,getLineDash,clearRect,fillRect,beginPath,closePath,moveTo,lineTo,quadraticCurveTo,bezierCurveTo,arcTo,rect,arc,fill,stroke,clip,clearShadow,fillText,strokeText,strokeRect,drawImage,drawImageFromRect,putImageData,createPattern,createImageData,textBaseLine,strokeStyle,lineWidth,globalAlpha,fillStyle,font,shadowOffsetX,shadowOffsetY,shadowBlur,shadowColor,lineCap,lineJoin,miterLimit,getImageData,isPointInPath'.split(','); function fn (canvas) { this.context = canvas.getContext('2d'); } var old = document.createElement('CANVAS').getContext('2d'); for(var i = 1,p=pro[0];p;p=pro[i++]) { // console.log(i +' >> '+ p + ' >> ' + typeof CanvasRenderingContext2D.prototype[p]); fn.prototype[p] = function (p) { return (typeof old[p] === 'function') ? function () { var r = this.context[p].apply(this.context,arguments); return r === undefined ? this : r; } : function () { this.context[p] = Array.prototype.join.call(arguments); return this; }; }(p); } return function (canvas) { return new fn(canvas); }; }()
先來看一段正常的canvas畫圖語法:
ctx.arc(centerX,centerY,radius,0,PI*2,true); ctx.shadowColor = 'rgba(0,0,0,0.5)'; ctx.shadowBlur = "10"; ctx.fill(); ctx.beginPath(); ctx.shadowColor = 'rgba(0,0,0,0)'; ctx.moveTo(centerX-radius,centerY); ctx.lineTo(centerX-radius,centerY - 50); ctx.lineTo(centerX+radius,centerY - 50); ctx.lineTo(centerX+radius,centerY); // ctx.lineTo(centerX-radius,centerY); ctx.fill(); ctx.beginPath(); ctx.fillStyle = 'rgba(255,0,0,1)'; ctx.arc(centerX,centerY-50,radius,0,PI*2,true); ctx.fill();
我對canvas原生語法不爽的有兩點:1是每句前面都有寫ctx(即canvas的context2d對象),2是每個函數或屬性都要占一行,浪費空間。
我對jQuery的鏈式語法很欣賞,比如:
$('#div1').show(300).html(p).delay(3000).slideUp(300).remove();
所以,我也想用這種語法來進行canvas繪圖:
ctx.moveTo(500,0).lineTo(500,500).strokeStyle('#f00').stroke();
有個辦法就是模擬一個context2d對象,這個對象支持所有的原生context2d方法,但又支持鏈式。
不過,代碼不能太多,多了就沒人喜歡用了。
下面就是完整的代碼段,這個“類”我取名為XtendCanvas(又是以X開頭的喲):
// 讓canvas支持鏈式語法,來自十年燈 ~function () {var pro = ['save','restore', 'scale', 'rotate', 'translate', 'transform', 'createLinearGradient', 'createRadialGradient', 'getLineDash', 'clearRect', 'fillRect', 'beginPath', 'closePath', 'moveTo', 'lineTo', 'quadraticCurveTo', 'bezierCurveTo', 'arcTo', 'rect', 'arc', 'fill', 'stroke', 'clip', 'isPointInPath', 'measureText', 'clearShadow', 'fillText', 'strokeText', 'strokeRect', 'drawImage', 'drawImageFromRect', 'putImageData', 'createPattern', 'createImageData', 'getImageData', 'lineWidth','strokeStyle','globalAlpha','fillStyle','font','shadowOffsetX','shadowOffsetY','shadowBlur','shadowColor','lineCap','lineJoin','miterLimit']; function XtendCanvas (canvas) { var ctx = canvas.getContext('2d'), fn = function(){}, fnP = fn.prototype; for(var j = 0,p=pro[0];p;p=pro[j++]) { fn.prototype[p] = function (p) { return function () { var args = Array.prototype.slice.call(arguments); // console.log(args); if(typeof ctx[p] == 'function') { ctx[p].apply(ctx,args); } else { ctx[p] = args+''; } return fnP; }; }(p); } return new fn; }; window.XtendCanvas = XtendCanvas; }();
使用方法很簡單,給他傳一個canvas對象,他就會返回一個類似context2d的對象,你可以像普通的context2d一樣使用,但不同的是,他支持鏈式語法了:
var cvs = document.getElementById('cvs');
var ctx = XtendCanvas(cvs);
ctx.moveTo(500,0).lineTo(500,500).strokeStyle('#f00').stroke();
這樣一來你就可以把所有操作都放在一句話里,你也可以隨時中斷,做其他的事,然后繼續。
2013/1/5更新了下:
var XtendCanvas = function () { var pro = ['save','restore', 'scale', 'rotate', 'translate', 'transform', 'createLinearGradient', 'createRadialGradient', 'getLineDash', 'clearRect', 'fillRect', 'beginPath', 'closePath', 'moveTo', 'lineTo', 'quadraticCurveTo', 'bezierCurveTo', 'arcTo', 'rect', 'arc', 'fill', 'stroke', 'clip', 'isPointInPath', 'measureText', 'clearShadow', 'fillText', 'strokeText', 'strokeRect', 'drawImage', 'drawImageFromRect', 'putImageData', 'createPattern', 'createImageData', 'getImageData', 'lineWidth','strokeStyle','globalAlpha','fillStyle','font','shadowOffsetX','shadowOffsetY','shadowBlur','shadowColor','lineCap','lineJoin','miterLimit']; function fn (canvas) { this.context = canvas.getContext('2d'); } var fnP = fn.prototype, Slice = Array.prototype.slice; for(var j = 0,p=pro[0];p;p=pro[j++]) { fnP[p] = function (p) { return function () { var args = Slice.call(arguments), ctx = this.context; // console.log(args); if(typeof ctx[p] === 'function') { ctx[p].apply(ctx,args); } else { ctx[p] = args+''; } return this; }; }(p); } return function (canvas) { return new fn(canvas); }; }()
這個更新只是理了一下語法結構。
由於沒有經過極限測試,所以我也暫時不清楚此鏈式語法會不會在極端情況下造成效率問題。現在已經發現的問題是,用原生代碼與用鏈式語法代碼實現相同的效果時,鏈式語法的瀏覽器內存會在短時間內一直增長,但一會兒就達到峰值了不再增加;而原生代碼內存是緩慢增加,峰值與鏈式代碼差別不大。希望有條件的同學自行測試
-------------更新內容結束------------
這段代碼並不是對canvas的增強,只是單純的讓他支持鏈式語法了。但勝在代碼少,可以嵌入到任何JS庫中,在此我希望能得到你的一個“推薦”
代碼中肯定有值得改進的地方,大家可以自行完善。但——吃水不忘挖井人,希望大家記得我,最重要的是思路,對吧?下面就是思路:
大家可以看到,代碼中最長的部分,是那個保存方法名的數組pro,核心代碼反而很短。為什么我要建這么一個數組呢?
本來我也想直接從CanvasRenderingContext2D繼承所有原生方法,但每個瀏覽器下面遍歷這個CanvasRenderingContext2D,結果都不一致。如果我把他們直接繼承,那么當你想用chrome中的方法套在firefox里執行,就會報錯。
所以我只是把CanvasRenderingContext2D中的通用的,無異議的方法與屬性名提取了出來,沒辦法,只有建一個固定的數組——大家可以自行決定往里面添加你的方法。
方法與屬性提取出來了,接着就是把原生的方法加在我的新對象上。我建了一個叫fn的空函數,放置我的方法。
由於數組中的這些元素既有函數,也有屬性,所以我在循環中判斷了他是否是一個函數,如果是函數,就帶參數執行;不是函數——那么就肯定是屬性了,就把參數賦給這個屬性。
這樣大家在碰到設置canvas屬性的時候,就不用中斷鏈了,直接把屬性值當參數傳進去就行了,比如:
ctx.strokeStyle('#f00')
最后,關鍵的關鍵,就是返回fn,這招是從jQuery學來的,是支持鏈式語法的關鍵。
這段中用到了匿名函數,閉包,原型,以及我以前文章講過的奇怪的for循環。
說起來好像挺簡單的,不過我實在是想了很久,希望對大家有用。
在寫代碼的過程中,我發現chrome的做法很不錯,他有一串以set開頭的函數,如setStrokeColor,setLineCap等函數,給他們傳參數,就可以替代對應的strokeStyle和lineCap等屬性,也就是說,他的canvas里面就全是函數而沒有屬性了那樣的話我就不用判斷是函數還是是屬性了。但firefox里面沒有這些,所以我還是只能用前面的思路。
我也把那一串set函數給放出來吧:
var bak = ['setTransform','setAlpha', 'setCompositeOperation', 'setLineWidth', 'setLineCap', 'setLineJoin', 'setMiterLimit', 'setLineDash','setShadow','setStrokeColor','setFillColor'];
他們的用處一看就懂。你也可以選擇一些加入前面代碼的pro數組中。
最后,我很奇怪我的代碼怎么會沒有高亮了。。。如果你都看到最后了,那么還是給個推薦吧,讓我也虛榮一把