利用HTML5 Canvas动态产生文字图示

前几篇Google Map API文章,一直有用到Google地图加上Marker的做法。(即下图的红色大头针图案)

不过,若全部的标示点用一样的图示,会显得无趣且容易混淆(如下图所示),虽然将滑鼠移到标示图案上方会显示名称,在使用者体验上总觉得还有改善空间。

事实上,Google Maps API在新增地标时,是可以自订图示的。MarkerOptions提供icon参数可指定图档URL,另外有shadow参点可指定阴影图档的URL,以取代预设的黑点红色大头针图示。

只是有个小问题,在消防分队位置显示范例中,台北市共有44个消防分队,即使能自订图示,可能也只是用44台消防车换掉44根大头针,对于识别度并没有太多提升,除非我们能预先制作44个刻有消防分队名称的图档,再一一对应到44个Marker使用…

身为程式魔人,44个图档用手工做是不被允许的,当然要自动产生才不会被人耻笑。我第一个想到是用ASP.NET动态产生图档的技巧,不过再转念一想,何不用HTML5的Canvas来实做,完全在Client解决? (IE6/7/8:那我们怎么办?念你们曾纵横江湖多年,也算时代英雄,你们自尽吧!)

HTML5 Canvas要即时产生图档不是问题,再配合Canvas.toDataURL()就可取代图档案URL,作为MarkerOptions.icon参数的设定值,就能达到当场动态产生Marker图示的目的。

我试写了一个小函数,用Canvas作图,再用toDataURL()输出作为<img>的src来源:

<!DOCTYPE html>
<html>
<head runat="server">
    <title>動態Canvas圖標</title>
    <script type='text/javascript' 
            src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.js'></script>   
    <style>
        body,input { font-size: 9pt; }
        img { margin: 5px }
    </style>
    <script>
        $(function () {
            //利用canvas產生一個內含文字的圖檔
            function createMarkerIcon(text, opt) {
                //定義預設參數
                var defaultOptions = {
                    fontStyle: "normal", //normal, bold, italic
                    fontName: "Arial",
                    fontSize: 12, //以Pixel為單位
                    bgColor: "darkblue",
                    fgColor: "white",
                    padding: 4,
                    arrowHeight: 6 //下方尖角高度
                };
                options = $.extend(defaultOptions, opt);
                //建立Canvas,開始幹活兒
                var canvas = document.createElement("canvas"),
                    context = canvas.getContext("2d");
                //評估文字尺寸
                var font = options.fontStyle + " " + options.fontSize + "px " + 
                           options.fontName;
                context.font = font;
                var metrics = context.measureText(text);
                //文字大小加上padding作為外部尺寸
                var w = metrics.width + options.padding * 2;
                //高度以Font的大小為準
                var h = options.fontSize + options.padding * 2 +
                        options.arrowHeight;
                canvas.width = w;
                canvas.height = h;
                //邊框及背景
                context.beginPath();
                context.rect(0, 0, w, h - options.arrowHeight);
                context.fillStyle = options.bgColor;
                context.fill();
                //畫出下方尖角
                context.beginPath();
                var x = w / 2, y = h, arwSz = options.arrowHeight;
                context.moveTo(x, y);
                context.lineTo(x - arwSz, y - arwSz);
                context.lineTo(x + arwSz, y - arwSz);
                context.lineTo(x, y);
                context.fill();
                //印出文字
                context.textAlign = "center";
                context.fillStyle = options.fgColor;
                context.font = font;
                context.fillText(text,
                    w / 2,
                    (h - options.arrowHeight) / 2 + options.padding);
                //傳回DataURI字串
                return canvas.toDataURL();
            }
 
            $("<img />", { src: createMarkerIcon("Jeffrey") })
            .appendTo("body");
            $("<img />", { src: createMarkerIcon("黑暗執行緒", {
                //注意: fontName所指定字型,需使用者機器上有安裝才算數
                fontName: "微軟正黑體", fontSize: 14, fontStyle: "bold",
                padding: 5, bgColor: "#6199DF", fgColor: "white"
            }) }).appendTo("body");
        });
    </script>
</head>
<body>
</body>
</html>

姜姜姜姜~~ 这样子只要输入文字,就可以当场产生图示啰!

简单修改程式,加入icon参数指向createMarkerIcon():

            var marker = new google.maps.Marker({
                position: latlng, //經緯度
                title: "八角亭", //顯示文字
                icon: createMarkerIcon("八角亭", {
                    bgColor: "brown"
                }),
                map: map //指定要放置的地圖對象
            });

原本黑头大头针就换成带文字的告示牌,是不是清楚多了呢?

到了该为HTML5欢呼的时刻,赞啦!!

原文:http://blog.darkthread.net/post-2012-06-16-canvas-dynamic-image.aspx

Leave a Reply