[譯] 使用 SVG 和 Vue.Js 構建動態樹圖

使用 SVG 和 Vue.Js 構建動態樹圖

本文將會帶你瞭解到我是如何建立一個動態樹圖的,該圖使用 SVG(可縮放矢量圖形)繪製三次貝塞爾曲線(Cubic Bezier)路徑並經過 Vue.js 以實現數據響應。前端

在開始前,先讓咱們來看一個 demovue

基於 SVG 和 Vue.js 框架的強大功能,咱們能夠輕鬆建立基於數據驅動、可交互和可配置的圖表與信息圖。android

該圖是一個三次貝塞爾曲線的集合,它基於用戶提供的數據,從單點出發,並在不一樣的點結束,且點和點之間的距離相同。 所以,該圖會響應用戶輸入的內容。ios

咱們將首先學習如何製做三次貝塞爾曲線,而後經過剪切蒙版在座標系中嘗試找到 <svg> 元素可用的 xy 點。git

我在這個案例中使用了不少視覺動畫以保證趣味性。本文的主要思想是幫助你爲相似的項目設計出本身的圖表。github

SVG

Cubic Bezier 曲線是如何造成的?

你在上面的 demo 中看到的曲線被稱爲三次貝塞爾曲線。我已在下面高亮顯示了此曲線結構的每一個部分。後端

它總共有 4 對座標。第一對座標 —— (x0, y0) —— 是起始錨點,最後一對座標 —— (x3, y3) —— 是結束錨點,指示完成路徑的位置。數組

中間的兩對座標是:緩存

  • 貝塞爾控制點 #1 (x1, y1)
  • 貝塞爾控制點 #2 (x2, y2)

基於這些點實現的路徑是一條平滑曲線。若是沒有這些控制點,這條路徑就是一條筆直的線!bash

讓咱們把這四個座標放入 SVG 語法的 <path> 元素中。

// 三次貝塞爾曲線的路徑語法

<path D="M x0,y0 C x1,y1 x2,y2 x3,y3" />
複製代碼

語法中的字母 c 表明三次貝塞爾曲線。小 c 表示相對值,而大寫 C 表示絕對值。我用絕對值 C 來建立這個圖。

實現對稱性

對稱性是實現該圖的關鍵點。爲了實現這一點,我只使用一個變量來派生出相似於高度,寬度和中點等值。

就讓咱們把這個變量命名爲 size 吧。因爲此樹形圖的方向是水平的,所以能夠將變量 size 視爲整張圖的水平空間。

讓咱們爲這個變量賦予實際值。這樣,你還能夠計算路徑的座標。

size = 1000
複製代碼

尋找座標

在咱們尋找座標前,咱們須要新建一個座標系!

座標系和 viewBox

<svg> 元素的 viewBox 屬性很是重要,由於它定義了 SVG 的用戶座標系。簡而言之,viewBox 定義了用戶空間的位置和維度以便於繪製 SVG。

viewBox 由四個數字組成,順序須要保持一致 —— min-x, min-y, width, height

<svg viewBox="min-x min-y width height">...</svg>
複製代碼

咱們以前定義的 size 變量將控制此座標系的 widthheight

稍後在 Vue.js 部分,viewBox 將綁定到計算屬性以填充 widthheight,而 min-xmin-y 在此實例中始終爲零。

請注意,咱們沒有使用 SVG 元素自己的 widthheight 屬性。由於,咱們稍後會經過 CSS 設置 <svg>width: 100%height: 100%,以便自適應填滿整個 viewport。

如今整張圖的用戶空間 / 座標系已準備好,讓咱們看看 size 變量如何經過使用不一樣的 % 值來幫助計算座標。

恆定和動態座標

Diagram Concept

圓是圖的一部分。這就是爲何從一開始就把它包含在計算中是很重要的。如上圖所示,讓咱們開始導出一個一個樣本路徑的座標值。

垂直高度分爲兩部分:topHeightsize 的 20%)和 bottomHeightsize 剩餘的 80%)。水平寬度分爲兩部分 —— 分別是 size 的 50%

這樣圓座標(halfSize, topHeight)就顯而易見了。圓的 radius 屬性設置爲 topHeight 的一半,這樣的可用空間很是合適。

如今,讓咱們看一下路徑座標……

  • x0, y0 —— 第一對錨點始終保持不變。這裏,x0 是圖表 size 的中心,y0 是圓圈中止的垂直點(所以增長了一個 radius)而且是路徑的起點。
    =(50% 的 size, 20% 的 size + radius)
  • x1, y1 —— 貝塞爾控制點 1,對於全部路徑也保持不變。考慮到對稱性,x1y1 老是圖表 size 的一半。 = (50% 的 size, 50% 的 size)
  • x2, y2 —— 貝塞爾控制點 2,其中 x2 指示哪一側造成曲線而且爲每條路徑動態計算。一樣,y2 是圖表 size 的一半。
    = (x2, 50% 的 size)
  • x3, y3 —— 最後一對錨點,指示路徑繪製結束的位置。這裏,x3 模仿 x2 的值,這是動態計算的。y3 佔據了 size 的 80%。
    = (x3, 80% 的 size)

在合併上述計算結果後,請參閱下面的通用路徑語法。爲了表示 %,我只是簡單的將 % 值除以 100。

<path d="M size*0.5, (size*0.2) + radius C size*0.5, size*0.5 x2, size*0.5 x3, size*0.8"
>
複製代碼

注意:整個代碼邏輯中 % 的選擇最初看起來彷佛全是主觀推斷,但它是爲了實現對稱而選擇的正確比例。一旦你瞭解了構建此圖表的目的,你就能夠嘗試本身的 % 值並檢查不一樣的結果。

下一部分重點是找到剩餘座標 x2x3 的值 —— 這使得可以根據它們的數組索引動態地造成多個彎曲路徑。

根據數組中的多個元素,可用的水平空間應分配到相等的部分,以便每一個路徑在 x-axis 上得到相同的空間量。

公式最終應適用於任意數量的項目,但出於本文的目的,我已經使用了 5 個數組項 —— [0,1,2,3,4]。意思是,我將繪製 5 條貝塞爾曲線。

尋找動態座標(x2 和 x3)

首先,我將 size 除以元素數,即數組長度,並命名爲 distance —— 做爲兩個元素之間的距離。

distance = size/arrayLength
// distance = 1000/5 = 200
複製代碼

而後,我循環遍歷數組中的每一個元素,並將其 index 值乘以 distance。 爲了描述簡單,我用 x 表示 x2x3

// value of x2 and x3
x = index * distance
複製代碼

當我使用 x 的值來表示 x2x3 時,這張圖看起來有點奇怪。

如你所見,座標的位置是正確的,但不是很對稱。左側的元素看起來比右側的元素多。

此時由於一些緣由,我須要將 x3 座標放在 distance 的中心,而不是在一開始的地方。

爲了解決這個問題,讓咱們從新審視下變量 distance —— 對於給定的場景,它的值是 200。我只是給 x 又加了 distance 的一半

x = index * distance + (distance * 0.5)
複製代碼

上式意思是,我找到了 distance 的中點並將最終的 x3 座標放在那裏,並調整了貝塞爾曲線 #2 的 x2

x2x3 座標中添加 distance 的一半,適用於數組的奇數項和偶數項元素。

圖層蒙版

爲了使蒙版形狀爲圓形,我已經在 mask 元素中定義了一個 circle

<defs>
  <mask id="svg-mask">
     <circle :r="radius" 
             :cx="halfSize" 
             :cy="topHeight" 
             fill="white"/>
  </mask>
</defs>
複製代碼

接下來,使用 <svg> 元素中的 <image> 標籤做爲內容,我使用 mask 屬性將圖像綁定到 <mask> 元素裏(已在上述代碼中建立)。

<image mask="url(#svg-mask)" 
      :x="(halfSize-radius)" 
      :y="(topHeight-radius)"
...
> 
</image>
複製代碼

因爲咱們試圖將方形圖像擬合成圓形,我經過減少圓的 radius 來調整圖像位置,以經過圓形蒙版實現圖像的徹底可見性。

讓咱們將全部的值都放入圖表中,以幫助咱們看到完整的圖像。

使用 Vue.js 的動態 SVG

到目前爲止,咱們已經瞭解了貝塞爾曲線的本質,以及它的工做原理。所以,咱們有了靜態 SVG 圖的概念。使用 Vue.js 和 SVG,咱們如今將用數據驅動圖表,並將其從靜態轉換爲動態。

在本節中,咱們將把 SVG 圖分解爲 Vue 組件,並將 SVG 屬性綁定到計算屬性,並使其響應數據更改。

最後,咱們還將查看配置面板組件,該組件用於向動態 SVG 圖提供數據。

咱們將在本節中瞭解如下關鍵主題。

  • 綁定 SVG viewBox
  • 計算 SVG 路徑座標
  • 實現貝塞爾曲線路徑的兩個選項
  • 配置面板
  • 家庭做業 ❤

綁定 SVG viewBox

首先,咱們須要一個座標系統才能在 SVG 內部繪製。 計算屬性 viewbox 將使用 size 變量。它包含由空格分隔的四個值 —— 它被送入 <svg> 元素的 viewBox 屬性。

viewbox() 
{
   return "0 0 " + this.size + " " + this.size;
}
複製代碼

在 SVG 中,viewBox 屬性已經使用駝峯命名法(camelCase)。

<svg viewBox="0 0 1000 1000">
</svg>
複製代碼

所以爲了正確綁定上計算屬性,我在 .camel 修飾符後對該變量使用了短橫線命名(kebab-case)的方式(以下所示)。經過這種方式,HTML 才得以正確綁定此屬性。

如今,每次咱們更改 size 時,圖表都會自行調整,而無需手動更改標記。

計算 SVG 路徑座標

因爲大多數值都是從單個變量 size 派生的,因此我已經爲全部常量座標使用了計算屬性。不要被這裏的常量混淆。這些值是從 size 中派生出來的,但在以後,不管建立多少曲線路徑,它們都保持不變。

若是你改變 SVG 的大小,這些值會再次被計算出來。考慮到這一點,這裏列出了繪製貝塞爾曲線所需的五個值。

  • topHeight — size * 0.2
  • bottomHeight — size * 0.8
  • width — size
  • halfSize — size * 0.5
  • distance — size/arrayLength

此時,咱們只剩下兩個未知值,即 x2x3,咱們有一個公式能夠肯定它們的值。

x = index * distance + (distance * 0.5)
複製代碼

爲了找到上面的 x,咱們須要一次將 index 輸入到每一個路徑的公式中。因此……

在這使用計算屬性合適嗎?確定不合適。

咱們不能將參數傳遞給計算屬性 —— 由於它是一個屬性,而不是函數。另外,須要一個參數來計算意味着——使用計算屬性對緩存也沒什麼好處。

注意:上面有一個例外,Vuex。若是咱們正在使用 Vuex Getters,那麼,咱們能夠經過返回一個函數將參數傳遞給 getter。

在本文所述的狀況下,咱們不使用 Vuex。可即使如此,咱們仍有兩個選擇。

選擇一

咱們能夠定義一個函數,在這裏咱們將數組 index 做爲參數傳遞並返回結果。若是要在模板中的多個位置使用此值,選擇 Bit cleaner

<g v-for="(item, i) in itemArray">
  <path :d="'M' + halfSize + ',' + (topHeight+r) +' '+ 'C' + halfSize + ',' + halfSize +' '+ calculateXPos(i) + ',' + halfSize +' '+ calculateXPos(i) + ',' + bottomHeight" 
  />
</g>
複製代碼

calculateXPos() 方法將在每次調用時進行評估。而且此方法接受索引 —— i —— 做爲參數(代碼以下)。

<script>
  methods: {
    calculateXPos (i)
    {
      return distance * i + (distance * 0.5)
    }
  }
</script>
複製代碼

下面是運行在 CodePen 上 Option 1 的結果。

Option 1 - Bezier Curve Tree Diagram with Vue Js

選擇二

更好的是,咱們能夠將這個小的 SVG 路徑標記提取到它本身的子組件中,並將 index 做爲一個屬性傳遞給它 —— 固然,還有其餘必需的屬性。

在這個例子中,咱們甚至可使用計算屬性來查找 x2x3

<g v-for="(item, i) in items"> 
    <cubic-bezier  :index="i" 
                   :half-size="halfSize" 
                   :top-height="topHeight" 
                   :bottom-height="bottomHeight" 
                   :r="radius"
                   :d="distance"
     >
     </cubic-bezier>
</g>
複製代碼

這種方法可讓咱們的代碼更具條理,例如,咱們能夠爲一個圓形剪切蒙版建立一個或多個子組件,以下所示。

<clip-mask :title="title"
           :half-size="halfSize" 
           :top-height="topHeight"                     
           :r="radius"> 
</clip-mask>
複製代碼

配置面板

Config Panel

您可能已經在 CodePen 左上角看到了 控制面板。它能夠添加和刪除數組中的元素。在 Option 2 中,我建立了一個子組件來容納 Config Panel,使頂級 Vue 組件清晰可讀。咱們的 Vue 組件樹看起來就像下面這樣。

想知道 Option 2 的代碼是什麼樣子的?下面的連接是在 CodePen 上使用了 Option 2 的代碼。

Option 2 - Bezier Curve Tree Diagram with Vue Js

GitHub 倉庫

最後,這裏有一個爲你準備的 GitHub Repo,你能夠在進入下一部分以前查看該項目(使用選項 2)。

家庭做業

嘗試基於本文中介紹的邏輯在垂直模式下建立相同的圖表。

若是你認爲,它是交換座標系中的 x 值和 y 值同樣簡單的話,那麼你是對的!由於最艱難的部分已經完成,在交換了所需的座標後,再用適當的變量和方法更新代碼。

在 Vue.js 的幫助下,該圖能夠經過更多功能進一步擴展,例如,

  • 建立一個開關以便於在水平和垂直模式之間切換
  • 可使用 GSAP 爲路徑設置動畫
  • 從配置面板控制路徑屬性(例如顏色和筆觸寬度)
  • 使用第三方工具庫將圖表保存並下載爲圖像/PDF

如今試一試,若是須要的話,下面是家庭做業的答案連接。

祝你好運!

總結

<path> 是 SVG 中衆多強大的元素之一,由於它容許你精確地建立圖形和圖表。在本文中,咱們瞭解了貝塞爾曲線的工做原理以及如何建立一個自定義圖表應用。

利用現代 JavaScript 框架所使用的數據驅動方法進行調整老是使人生畏的,但 Vue.js 使它變得很是簡單,而且還能夠處理諸如 DOM 操做之類的簡單任務。所以,做爲一名開發人員,即便在處理具備明顯視覺效果的項目時,你也能夠用數據的方式進行思考。

我已經意識到建立這個看起來很複雜的圖表須要 Vue.js 和 SVG 的一些簡單概念。若是你尚未準備好,我建議您閱讀有關使用 Vue.js 構建交互式信息圖的內容。讀完那篇文章後再回過頭閱讀本文就會容易不少。❤這是家庭做業的答案

我但願你從這篇文章中學到了一些東西,並在閱讀本文時可以感覺到我當時創做時的樂趣。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索