利用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歡呼的時刻,讚啦!!

歡迎推文分享:
Published 16 June 2012 02:54 PM 由 Jeffrey
Filed under:
Views: 32,820



意見

# Vexed said on 16 June, 2012 12:15 PM

blog.xuite.net/.../59848483

滿類似的 :p

# Jeffrey said on 18 June, 2012 05:17 PM

to Vexed, 哈! 載入底圖這招省力又精美,學習了。謝謝分享~

# 小黑 said on 19 June, 2012 12:37 AM

原來IE8 這麼不支援 html5 ,

那使用 asp.net mvc 4 開發的網站,IE8 也不一定能正常顯示就對了!

# Jeffrey said on 19 June, 2012 06:34 AM

to 小黑,針對不支援HTML5的瀏覽器,有些技巧可以讓HTML5網頁盡可能正常顯示與運作。ASP.NET MVC 4雖然大量引用HTML5,但針對老瀏覽器仍透過Modernizr等外掛維持相容( blog.ericsk.org/.../1444),預設的範本可以跨瀏覽器正常執行無誤。除非你加入了IE6/7/8不支援的程式碼,又不肯加入相容性處理(像本文用了Canvas就不顧老瀏死活是壞榜樣,請大家不要跟我做好朋友),才會出現問題。

所以預設ASP.NET MVC4可以跨瀏覽器,但一路開發下來要維持原則不崩壞,對開發者是項挑戰就是了 XD

# 小黑 said on 19 June, 2012 11:40 AM

謝謝指導,看來真的是場硬仗了!

# andrew said on 25 July, 2012 02:29 AM

照你的方式做,可是出現的icon卻很小,連字都看不太清楚,請問這是什麼原因?

# Jeffrey said on 25 July, 2012 05:34 PM

to andrew, 由你的描述無法判斷問題,恐需要再提供程式碼範例等進一步資訊較能看出端倪。

# 小張 said on 21 February, 2015 09:40 AM

黑暗執行緒您好,感謝你設計出這麼實用的canvas物件,

想請問是否可修改您的程式碼在我的作業上面,剛好在

研究一些關於Google map的東西,原本我是用markwithlabel

在icon上呈現姓名標幟,現在有了html5 canvas後其實只要

有姓名就可以了,相對更為實用。

# 小張 said on 21 February, 2015 09:41 AM

黑暗執行緒您好,感謝你設計出這麼實用的canvas物件,

想請問是否可修改您的程式碼在我的作業上面,剛好在

研究一些關於Google map的東西,原本我是用markwithlabel

在icon上呈現姓名標幟,現在有了html5 canvas後其實只要

有姓名就可以了,相對更為實用。

# Jeffrey said on 22 February, 2015 07:40 AM

to 小張,歡迎引用,加註程式碼參考來源即可。

# 小張 said on 24 February, 2015 08:09 AM

黑暗大您好,如果說在文字的部分希望能分成上下兩行,如上行是姓名,下行是身份證字號,該如何去設計,加了<br>標籤或把一開始的text改成html都沒有辦法,是否有解決的方案呢?

# Jeffrey said on 25 February, 2015 08:15 PM

to 小張,要印成兩行,可分兩次呼叫context.fillText(),透過Y座標參數控制上下位置即可。

# 小張 said on 02 March, 2015 09:57 AM

黑暗大謝謝,已調整成功。

# 小猴 said on 03 June, 2016 07:05 AM

你好請問一下:最後那個加入ICON 要如何使用

要加在哪  為什麼我加在後面都沒東西

# Jeffrey said on 03 June, 2016 09:17 AM

to 小猴,你是指icon: createMarkerIcon("八角亭", {bgColor: "brown"})嗎?icon原本可傳入png或gif URL指向圖示圖檔,我的程式改用HTML5當場繪製。你可先試試寫死URL看是否能正常顯示,若寫死URL可以換成createMarkerIcon()不行,有可能是createMarkerIcon裡的邏輯出了問題。

# 小猴 said on 03 June, 2016 09:51 PM

不是 我意思是最後那個地圖加上八角亭的圖 怎麼做出來的?

因為我把上面的程式碼加在一起後,沒辦法呈現出來

可以讓我看一下那張圖得程式碼嗎(最後八角亭那張)

不好意思 我是新手 正在學寫地圖很爛

謝謝你黑暗大大

# Jeffrey said on 04 June, 2016 08:47 AM

to 小猴,我做了一個Live Demo: jsbin.com/.../edit 請參考

# 小猴 said on 05 June, 2016 12:32 AM

謝謝你  解決了我的問題

感謝你耐心的指導~~

# JING said on 25 July, 2016 08:15 AM

黑暗大大你好 請問一下

那個最後八角停 因為顯示出來 會擋到地圖

想請問一下要如何使用透明元素

想呈現畫出來的背景是"半透明 文字不透明"

我嘗試過opacity=0.5但效果都沒出來  想請問一下大大能幫忙解決嗎?

# Jeffrey said on 25 July, 2016 08:59 AM

to JING, 試試marker.setOpacity(0.5);,參考:developers.google.com/.../reference

# JING said on 25 July, 2016 09:43 AM

黑暗大大 請問一下是要加在哪裡?

因為我剛看了參考網頁

再去網路找資訊還是無法了解要加在哪

var marker = new google.maps.Marker({

               position: latlng, //經緯度

               title: "八角亭", //顯示文字

               icon: createMarkerIcon("八角亭", {

                   bgColor: "brown"

               }),

               marker.setOpacity(0.5),

               map: map //指定要放置的地圖對象

           });

是加在這裏面嗎 嘗試很久還是跑不出來

希望大大能幫忙

# Jeffrey said on 25 July, 2016 08:12 PM

to JING, 要在marker物件建立後呼叫,也就是

var marker = new google.maps.Marker({...});

marker.setOpacity(0.5);

範例:jsbin.com/.../edit

# JING said on 26 July, 2016 05:53 AM

謝謝大大指導~~~

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<June 2012>
SunMonTueWedThuFriSat
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication