JavaScript - 將HTML DOM元件畫至Canvas上

前陣子有介紹到如何使用Cytoscape.js來畫網路拓譜圖,限量使用Cytoscape.jsSignalR做出一個即時監控機器狀態的網頁,為了符合一些特殊需求,限量修改Cytoscape.js的Code,讓它可以支援在Canvas節點上掛上HTML DOM元件,在拖拉時HTML DOM元件會跟著節點動。此功能在Chrome是OK的,但是在IE9上因為Browser Threading的問題,所以導致SignalR在一直更新HTML DOM元件內容時會Lock頁面,導致無法對Cytoscape.js的節點進行動作。後來想一想只能想辦法將HTML DOM元件和Cytoscape.js節點一起畫入Canvas內。後來查一查資料就發現此法是可行的,以下就來說明如何將HTML DOM元件畫入Canvas上。


Canvas是HTML5支援的新的元件,主要是用來在Web Page上畫圖。只提供畫圖形, 文字, 與圖片,圖片的話需要將圖片轉換成Stream的方式在畫上去,其他圖形就像是使用小畫家一樣,用點與線繪出來,所以Canvas是不支援直接畫HTML DOM元件的。這時候就要繞個道,先使用SVG把HTML DOM轉成圖片,再用Canvas畫上去。

SVG(Scalable Vector Graphics)定義了許多XML-based的圖形基本圖形定義檔,例如:<circle>圓形, <rec>長方形, <line>線, <polygon>不規則形...等,有了SVGCanvas在畫個圖時就不用再那麼麻煩(因為Canvas沒有定義基本圖形檔,所以畫個基本圖形都需要定義許多東西,例如:角度, 長, 寬...等),而且SVG裡面可以放HTML DOM元件
接下來就來看看將HTML DOM畫至Canvas上的步驟,大致上就是先將HTML DOM放置在SVG裡,然後再將SVG轉成Image物件暫存在Web上,接著指定Image onload事件,讓Image load時將此Image畫到Canvas上。詳細範例程式碼如下:
<!DOCTYPE html>
<html>
<head>
    <title>SVGTest</title>
        <script type="text/javascript">
            // 在頁面載入後進行事件綁定處理
            window.onload = function() {
                var myCanvas = document.getElementById('myCanvas');
                var ctx = myCanvas.getContext('2d');

                    // 將HTML DOM放置在SVG裡
                    var data = 
                    '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">\
                        <foreignObject width="100%" height="100%" >\
                            <div xmlns="http://www.w3.org/1999/xhtml" style="font-size: 20px">\
                                <b>TEST</b>\
                                <input type="text" placeholder="input value" />\
                                <input type="button" value="Send" />\
                            </div>\
                        </foreignObject>\
                    </svg>';

                    // 取得window.URL物件以用來後續呼叫其方法
                    var DOMURL = window.URL || window.webkitURL || window;

                    var img = new Image();

                    // 將SVG圖傳成BLOB型態暫存,並指定MIME type為image/svg+xml
                    var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});

                    // 產生暫存圖的object url(因為HTML中img需要指定src的url位置)
                    var url = DOMURL.createObjectURL(svg);

                    img.onload = function () {
                        // 將暫存的SVG圖畫至Canvas上,並指定位置
                        ctx.drawImage(img, 100, 50);
                        // 釋放掉DOMURL資源
                        DOMURL.revokeObjectURL(url);
                    }

                    img.src = url;
                }
        </script>
</head>
<body>
<div id="main">
<canvas id="myCanvas" style="width:700; height: 500; border:1px solid black">
</canvas>
</div>
</body>
</html>

執行結果:


從執行後你可以點點看裡面的Button,是不是連點都不能點,因為變成了圖當然不能點。再來把此解法應用到Cytoscape.js就可以解決了(還是需要去改Cytoscape.js底層Code),真是皆大歡喜。


...




你以為這樣就Happy Ending了嗎?
在IE9上並沒有,因為該死的IE9沒有支援BLOB,要到IE10才有支援。所以限量又遇到了另一個問題,至於如何解決就又是另一段故事了。



參考來源:
MDN - Drawing DOM objects into a canvas
W3CSchool - SVG Tutorial
MDN - SVG





留言