本課題是我今年畢業設計的課題,現在我邊做邊跟大家分享,希望能通過“canvas矢量圖形渲染器”讓大家對canvas元素和其中的性能優化有更深的理解。
1.首先說說這個矢量渲染器是什么。
canvas元素封裝了很對對圖形繪制的接口,但是他跟flex相比最大的區別是我們通過fill() 或是 stroke()方法繪制的圖形是一張像素圖片,當放大或是縮小的時候會出現模糊等各種狀況。所以直接調用canvasAPI來繪制矢量圖形非常不合適。
這樣我們就需要設計一渲染器,里面封裝了各種圖形的繪制接口,並通過調用渲染器的繪制方法按照我們想發來繪制每一個矢量元素。
每一個矢量元素都是一個繼承自矢量圖形基類的對象,比如可以使點,線,面等。每一個圖形有自己的大小和位置信息,我們依次把每個圖形送入渲染器,渲染器結合自己當前的屬性(縮放百分比,中心點等)就可以計算出每一個圖形需要繪制的真實像素位置,之后再調用canvas的底層API實現圖形的繪制。
基本的類圖結構如下(在設計的過程當中可能會增加一些新的功能類):
一些點、線、面的基本矢量元素繼承自Geometry類,Geometry定義了矢量圖形的形狀信息。同時也是Vector類的一個屬性,Vector類里面還包括矢量元素的其他信息(如id、添加時間等)。
Layer類表示了當前圖層的一些基本信息,例如聲明了一個圖層(大小是400px * 400px),同時我在坐標為(0,0)的點放置了一個半徑為50px的圓,當前的縮放為100%,視圖中心點也是(0,0)則我們會得到下面的這樣一張圖片:
注:Layer類所表示的屬性:外側的方框代表當前的視圖范圍(viewBounds),坐標(0, 0)點則代表視圖的中心點(center),zoom的值代表當前的縮放百分比。
Vector類所表示的屬性: 擁有一個Geometry屬性表示半徑為70像素的圓,且擁有一個Style屬性表示填充的顏色為橙色。
下面說說Layer類,Vector類和Canvas類是如何協調工作的:
Canvas類也就是渲染器類,是本課題的核心,其中定義了各種繪制Geometry圖形的方法。但是我們該如何調用他進行繪制呢?
1.我們聲明一個Vector類的實例V1(他表示一個矢量圖形,其形狀信息保存在Geometry屬性當中)。
2.我們的Layer類必然有一個接口,用於接收由Vector聲明的實例V1(比如addVectors方法),當我們把所有的矢量圖形都接收到Layer當中后,我們就想要在瀏覽器當中看到我們所創建的的矢量圖形。
3.之后我們就應該請渲染器類出場了,他接收了Layer的所有矢量圖形的引用、當前的縮放級別、當前的視圖范圍和當前的中心點,之后渲染器通過一大堆的計算就神奇的把Geometry中的屬性變成了CanvasAPI中可以調用的數據。
注:就拿上個例子來說圓的中心點是(0,0),半徑是70像素,通過渲染器的一系列計算最后我們會調用“context.arc(200, 200, 70, Math.PI * 2, true); context.fill()”這兩句代碼。(200,200)這兩個位置便是渲染器所要計算的繪制中心點,而70這個半徑參數是通過70 * zoom(當前為100%)得到的。當然我們不能單單考慮這么簡單的一種情況,后面會跟大家介紹這些Geometry對應的點到底是如何計算的。
2.從構建基本的幾何基類、點開始。
1.geometry類。
這個類是一個幾何圖形的基類。
function Geometry(){ this.id = CanvasSketch.getId("geomtry_"); } //bounds屬性定義了當前Geometry外接矩形范圍。 Geometry.prototype.bounds = null; //定義Geometry的id屬性。 Geometry.prototype.id = null; //定義對bounds基類克隆的方法 Geometry.prototype.clone = function () { return new Geometry(); } //銷毀當前的Geometry Geometry.prototype.destroy = function () { this.bounds = null; this.id = null; }
其中的SketchCanvas.getId方法會返回一個唯一的id值。這個方法其實很簡單就是用一個全局變量不斷加1(后面的下載中有提供)。
2.Point類,繼承自Geometry。
point類作為繼承自Geometry最簡單的類,我們首先來介紹他。
function Point(x, y) { Geometry.apply(this, arguments); this.x = x; this.y = y; } Point.prototype = new Geometry(); //point類的橫坐標。 Point.prototype.x = null; //point類的縱坐標。 Point.prototype.y = null; //得到點的范圍。 Point.prototype.getBounds = function () { if(!this.bounds) { var x = this.x; var y = this.y; this.bounds = new CanvasSketch.Bounds(x, y, x, y); return this.bounds; } else { return this.bounds; } } //clone方法。 Point.prototype.clone = function () { return new Point(this.x, this.y); }
這個Point類定義了幾個比較基本,簡單的方法:getBounds、clone,使用prototype進行對Geometry類的繼承。最重要的是Point會接受兩個參數(x、y)也就是其位置信息。
這樣我們就可以用以下的語句聲明一個點了:
var point = new Point(0, 0);
這樣的聲明讓我們的程序看起來更加簡潔,擴展性會更強。
注:點在圖形當中所充當的是一個無大小、只表示位置的幾何對象。但是,通常我們需要為點設置一個半徑,以便讓大家可以看到。所以說點在任何縮放級別下的半徑大小都是一個固定的像素值。同樣,線的寬度也可以這樣理解。
3.添加、顯示矢量圖形
上面我們已經講了如何創建一個矢量的點,但是我們該如何把這個點顯示到我們的瀏覽器當中呢?
一個方法就是把這些矢量圖形先存放到一個圖層當中,這個圖層中的一些屬性(中心點、縮放百分比、視圖范圍)共同影響着矢量圖形的顯示結果。當然我們可以將繪制的核心代碼寫到這個圖層類當中,但是更好的做法就是用一個渲染器類控制關於顯示的方法,使以后可以更好的維護、擴展。
1.圖層類
注:這個課題的第一節,我們的圖層類只有幾個必須的方法,以控制添加、顯示。在以后的隨筆中會更深入的增加圖層的功能。(同樣渲染器類也是如此)
//圖層類 function Layer(div) { var style = div.style; var size = new CanvasSketch.Size(parseInt(style.width), parseInt(style.height)); this.size = size; this.div = div; this.maxBounds = new CanvasSketch.Bounds(-size.w / 2, -size.h / 2, size.w / 2, size.h / 2); this.bounds = new CanvasSketch.Bounds(-size.w / 2, -size.h / 2, size.w / 2, size.h / 2); this.zoom = 100; this.vectors = {}; //加入矢量圖形的總個數。 this.vectorsCount = 0; //創建一個渲染器。 this.renderer = new Canvas(this); } Layer.prototype.addVectors = function (vectors) { this.renderer.lock = true; for(var i = 0, len = vectors.length; i < len; i++) { if(i == len-1) {this.renderer.lock = false;} this.vectors[vectors[i].id] = vectors[i]; this.drawVector(vectors[i]); } this.vectorsCount += vectors.length; } Layer.prototype.drawVector = function (vector) { if(!vector.style) { style = new CanvasSketch.defaultStyle(); } this.renderer.drawGeometry(vector.geometry, style); }
定義過圖層類后,我們就可以為一個div創建圖層。
首先我們得在DOM樹中創建一個div,作為圖層的容器,如:
<body onload="init()"> <div style="width:400px; height:300px;" id="renderer"></div> </body>
這樣我們就可以在init函數中為這個id為“renderer”的div創建圖層了:
var div = document.getElementById("renderer"); var layer = new Layer(div);
之后我們所要做的就是聲明一個矢量圖形數組,調用layer.addVectors來為我們的圖層添加矢量元素。
for(var i = 0; i<1000; i++) { var point = new Point((Math.random()*400-200), (Math.random()*300-150)); vectors.push(new Vector(point)); } layer.addVectors(vectors);
注:創建1000個x坐標隨機在(-200,200)和y坐標隨機在(-150,150)之間的點,並添加到圖層當中。
上面的代碼中,我們通過new Vector()創建一個矢量圖形,其幾何信息就是之前聲明的point變量。
Vector類的聲明如下(在目前看來Vector類,就是將geometry和attributes整合到一起的一個類,但是以后會為這個Vector增加更多的方法和屬性的)
function Vector(geometry, attributes) { this.id = CanvasSketch.getId("vector"); this.geometry = geometry; if(attributes) { this.attributes = attributes; } }
下一篇隨筆會介紹渲染器類的構建方法,在下面提供的源碼當中有今天所有講過的類、和沒有涉及到的類(一些工具類,和渲染器類)。
這一節我們只是構建了一個大體的渲染器使用框架,在后面的隨筆中會完善他的功能~
下面這個demo展示了我們這個渲染器的基本用法。
嘗試一下:改變for循環的個數可以控制產生多少個點,可以自定義x,y值來精確定位點。
下次隨筆預告:1.重點介紹渲染器類的構建。
2.大家發現沒有上面的demo是以左上角為中心點縮放的,下次添加縮放中心點。
3.增加更多的幾何類型(圓和矩形)。
請關注~
本次隨筆的所有源碼+Demo,點擊下載。