構建Canvas矢量圖形渲染器(一)—— 基礎架構、矢量點的繪制


 本課題是我今年畢業設計的課題,現在我邊做邊跟大家分享,希望能通過“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,點擊下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM