1. 程式人生 > 實用技巧 >純 CSS 實現大量小塊兒繪製

純 CSS 實現大量小塊兒繪製

現在做的專案是公司內部全部組要用的 viewer 庫. Viewer 需要的功能非常的多,其中的一個就是需要提供一些常用的繪圖API功能, 比如使用者滑鼠移動畫箭頭,畫圈圈,高光選中文字等等。

挑戰

目前遇到的挑戰就是在 canvas, svg, dom +css之間如何選擇的問題,canvas 繪圖的方案已經有了成品,我們可以直接拿過來新增到現有的 Viewer 上面。但是基於以下考慮,主要用哪種技術遲遲不能下定論。

Canvas 糾結點:

1) 各部門合作問題,如果採用 canvas, 其他部門需要新增自定義圖形操作的時候就需要他們瞭解 Canvas 開發 重繪到 Viewer 已有的 Canvas 上面。
2) Canvas 會失去很多 Element 原生的屬性,比如文字選中,aria標籤, 事件等。
3) Viewer 的檔案可能會非常的大,並且可能會有成千上萬個頁面,每個頁面管理自己的繪圖。如果每個頁面一個Canvas, 效能會非常差(採用了 View Virtualization 思想,只渲染檢視需要的元素,這裡是做了個極限假設)。如果所有的圖形都繪製在一個Canvas上面,需要根據檢視區域的頁面屬性重繪圖形計算,開發成本會增加非常多。
4)Canvas 只能用於最底層,否則會覆蓋其他元素(Viewer上面的層非常多,需要繪圖層能管理事件,同時不丟失其他層的事件)。

於是老大就給幾天時間,讓我們盡情地先試著用其他的方式畫一下各種圖形,比較一下優劣(最後,我們應該會幾樣技術結合起來用)。其中一個案例就是高光選中文字,後端傳會傳一堆需要高光塊兒大小位置,前端畫出高光部分。 像這種非常多小面積的繪圖,Canvas是最合適不過的了,不過這幾天時間就是看看有沒有還有什麼其他的方案。大概就長這樣,高光是無數個小塊兒。

電腦刺繡繡花廠 http://www.szhdn.com 廣州品牌設計公司https://www.houdianzi.com

尋找方案

Canvas 畫高光的方案已經有了,我們首先想到的就是每一個塊兒給個 span 渲染,但因為需要高光的塊兒可能會非常多,並且可能會是動態的。哪麼這就涉及到 DOM 的頻繁操作,效能非常不好。後來採用createDocumentFragment,新增到DOM,效能明顯提升。

我當時有個非常大膽的想法,要是隻用一個span,然後所有的塊兒都新增成背景可不可以實現(考慮到現在的css3已經有了多背景重疊技術)。找路子的時候千萬次覺得不可能,結果實現之後再返回去看,其實原理非常的簡單。全部採用css, 用linear-gradient畫塊兒(採用linear-gradient, 是因為它可以背景重疊,如果有更好的方法歡迎指教),background-position, background-size分別給每個塊兒定位,結束!效能當然是驚人的好,因為根本沒有涉及到元素的增加刪除,只是css重繪(Viewer 有大量消耗效能的scroll監聽,操作等情況下都能無縫操作,Canvas 有非常微小地快閃)。這一試,彷彿大概了新世界,很多大量,簡單的圖形繪製完全可以嘗試純css實現,上程式碼。

Angular template

<spanclass="layer-content"[style.background]="_background"[style.backgroundSize]="_backgroundSize"[style.backgroundPosition]="_backgroundPosition"></span>

Angular ts file

    this._subscription = this.highlights$.subscribe((highlights) => {
      let background = ``;
      let backgroundSize = ``;
      let backgroundPosition = ``;
      for (let i = 0; i < highlights.length; i++) {
        const { x, y, width, height } = highlights[i];
        // add connection comma when i is not the last one.
        const comma = (i < highlights.length - 1) ? ', ' : '';
        // 0px transparent to fill gradient syntax.
        background += `linear-gradient(CurrentColor 100%, transparent 0px)${comma}`;
        backgroundSize += `${width}px ${height}px${comma}`;
        backgroundPosition += `${x}px ${y}px${comma}`;
      }
      this._background = this.sanitizer.bypassSecurityTrustStyle(background);
      this._backgroundSize = backgroundSize;
      this._backgroundPosition = backgroundPosition;
    });

Css

span.layer-content {
  z-index: 0;
  display: inline-block;
  width: 100%;
  height: 100%;
  background-repeat: no-repeat !important;
}