用100行實現HTML5可存檔塗鴉版

這是開發工具程式時的副產品,整理成使用HTML5 Canvas實做手繪塗鴉板的範例(可適用IE9)。

程式結構分成四個主要部分: 第一部分在網頁加入八個不同背景顏色的20x20<div>,掛上click()事件當作點選後指定畫筆顏色的調色盤;第二部分則用同樣概念加入八個20x20的<div>,中央分別放入1x1, 2x2, 3x3, …, 8x8大小的具背景色<div>小方塊,供使用者點選指定畫筆粗細,當調色盤切換顏色時,也會連動變換筆劃粗細小方塊的顏色。

第三部分建立一個300x300的<canvas>物件,利用getContext("2d")可以取得context(概念上context很像GDI+中的Graphics物件提供DrawLine、FillRectangle、DrawString等方法可以在上面繪製線條、形狀、輸出文字... 等等),然後在mousedown(), mousemove(), mouseup()事件加入落筆、劃線、起筆的對應邏輯。(介紹HTML5 Canvas的資源還蠻多的,例如: MDN Canvas教學、整個網站都在教Canvas的http://www.html5canvastutorials.com/...)

第四部分則是將<canvas>的繪製結果轉成圖檔,Javascript無法存取本機資源,所以很難直接產生檔案,而canvas提供了toDataURL()方法,可將canvas內容轉成png圖檔(或其他圖檔格式)的base64編碼字串(關於圖檔編碼字串可參考先前的Data URI的介紹文),再把它設定成<img>的src,就達到在網頁上顯示該圖檔的效果,使用者則可透過在圖檔按右鍵另存新檔的方式將檔案保存在本機上。

IE9+支援toDataURL(),故這個程式在IE9及Firefox、Chrome、Safari上均可順利執行。

依照慣例,這個陽春可匯出式塗鴉板範例遵HTML+Script不超過100行的優秀傳統,展現jQuery的簡潔~~

<!DOCTYPE html>
 
<html>
<head>
    <title>HTML5 Canvas塗鴉板</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.4.js">
    </script>
    <style>
        body,input { font-size: 9pt; }
        #dCanvas,#dLine { clear: both; }
        .option
        {
            float: left; width: 20px; height: 20px; border: 2px solid #cccccc;
            margin-right: 4px; margin-bottom: 4px;
        }
        .active { border: 2px solid black; }
        .lw { text-align: center; vertical-align: middle; }
        img.output { border: 1px solid green; }
        #cSketchPad { cursor: arrow; }
    </style>
    <script>
        $(function () {
            //產生不同顏色的div方格當作調色盤選項
            var colors =
            "red;orange;yellow;green;blue;indigo;purple;black;white".split(';');
            var sb = [];
            $.each(colors, function (i, v) {
                sb.push("<div class='option' style='background-color:" + v + "'></div>");
            });
            $("#dPallete").html(sb.join("\n"));
            //產生不同尺寸的方格當作線條粗細選項
            sb = [];
            for (var i = 1; i <= 9; i++)
                sb.push("<div class='option lw'>" +
         "<div style='margin-top:#px;margin-left:#px;width:%px;height:%px'></div></div>"
                .replace(/%/g, i).replace(/#/g, 10 - i / 2));
            $("#dLine").html(sb.join('\n'));
            var $clrs = $("#dPallete .option");
            var $lws = $("#dLine .option");
            //點選調色盤時切換焦點並取得顏色存入p_color,
            //同時變更線條粗細選項的方格的顏色
            $clrs.click(function () {
                $clrs.removeClass("active");
                $(this).addClass("active");
                p_color = this.style.backgroundColor;
                $lws.children("div").css("background-color", p_color);
            }).first().click();
            //點選線條粗細選項時切換焦點並取得寬度存入p_width
            $lws.click(function () {
                $lws.removeClass("active");
                $(this).addClass("active");
                p_width =
                    $(this).children("div").css("width").replace("px", "");
 
            }).eq(3).click();
            //取得canvas context
            var $canvas = $("#cSketchPad");
            var ctx = $canvas[0].getContext("2d");
            ctx.lineCap = "round";
            ctx.fillStyle = "white"; //整個canvas塗上白色背景避免PNG的透明底色效果
            ctx.fillRect(0, 0, $canvas.width(), $canvas.height());
            var drawMode = false;
            //canvas點選、移動、放開按鍵事件時進行繪圖動作
            $canvas.mousedown(function (e) {
                ctx.beginPath();
                ctx.strokeStyle = p_color;
                ctx.lineWidth = p_width;
                ctx.moveTo(e.pageX - $canvas.position().left, e.pageY - $canvas.position().top);
                drawMode = true;
            })
            .mousemove(function (e) {
                if (drawMode) {
                    ctx.lineTo(e.pageX - $canvas.position().left, e.pageY - $canvas.position().top);
                    ctx.stroke();
                }
            })
            .mouseup(function (e) {
                drawMode = false;
            });
            //利用.toDataqURL()將繪圖結果轉成圖檔
            $("#bGenImage").click(function () {
                $("#dOutput").html(
                $("<img />", { src: $canvas[0].toDataURL(),
                    "class": "output"
                }));
            });
        });
    </script>
</head>
<body>
<div id="dPallete"></div>
<div id="dLine"></div>
<div id="dCanvas">
<canvas id="cSketchPad" width="300" height="300" style="border: 2px solid gray" />
</div>
<input type="button" id="bGenImage" value="Generate Image" />
<div id="dOutput"></div>
</body>
</html>

程式雖短,但已足以提供基本的手繪功能,我就用它復刻了小閃光的即興塗鴉作品:

按下"Generate Image",網頁的下方會出現當時<canvas>內容所轉換成的png圖檔,另存新檔就可以永久保存囉!

有興趣的人可以到線上展示親手玩看看~

歡迎推文分享:
Published 30 October 2011 11:51 PM 由 Jeffrey
Filed under: ,
Views: 73,111



意見

# lsk said on 30 October, 2011 09:42 PM

我用FireFox4.0不行耶,要多少才可以?

有辦法讓IE9以下的也可以用嗎?

不知道SilverLight也可做出同樣效果?

# Shinyo said on 30 October, 2011 10:04 PM

黑暗大大~剛拿了您這段CODE來試玩,發現跑起來好像有些符號跟您DEMO的部太一樣,冒昧提醒,請您看看小弟的改法對不對

               sb.push("<div class='option' style='background-color:' + v + "'></div>");

           });

改成

               sb.push("<div class='option' style='background-color:" + v + "'></div>");

           });

2.         "<div style='margin-top:#px;margin-left:#px;width:%px;height:%px'></div></div>'

.replace(/%/g, i).replace(/#/g, 10 - i / 2));

改成

        "<div style='margin-top:#px;margin-left:#px;width:%px;height:%px'></div></div>"

.replace(/%/g, i).replace(/#/g, 10 - i / 2));

之後小弟的電腦就可以跑了,不知道我改的對不對,還請大大賜教,謝謝 :)

# Jeffrey said on 30 October, 2011 10:15 PM

to lsk, FireFox目前已經出到7了,除了IE,一般我都假設使用者會安裝瀏覽器的最新版本(XD),所以很少會準備早期版本進行測試及驗證程式向前相容性。但依據MDN(developer.mozilla.org/.../HTMLCanvasElement) FireFox很早就支援toDataURL()了才對,不知你遇到的錯誤狀況為何?

IE要到9.0+對HTML5才會有較完整的支援,在IE8以下要實現相同功能,可能如你說的要考量改用Silverlight或Flash,但不用擔心,用Silverlight肯定可以做出比這個還炫的效果。

# Jeffrey said on 30 October, 2011 10:27 PM

to Shinyo, 謝謝你的指正,不知道原因為何,但文中的程式範例碼確實有你所說的引號錯置狀況,我已修改了,感謝!

# Shinyo said on 31 October, 2011 01:57 AM

不客氣 ~~

但小弟發現在firefox能開但不能畫線耶!!

我用的是最新版的,不知道是不是我少設定了甚麼東西 :(

# lsk said on 31 October, 2011 02:36 AM

FireFox的錯誤是

錯誤: uncaught exception: [Exception... "An invalid or illegal string was specified"  code: "12" nsresult: "0x8053000c (NS_ERROR_DOM_SYNTAX_ERR)"  location: "www.darkthread.net/.../canvas1.htm Line: 68"]

因為不見得使用者都會更新到最新版本,而且你叫xp的人為了IE9,還得先換windows7,一定先死給你看,所以想說還不如用像SilverLight或其他方法達到同樣的效果

# jain said on 31 October, 2011 03:16 AM

firefox 7.0.1可以開但畫不出線~

# Jeffrey said on 31 October, 2011 04:33 AM

to lsk, Shinyo, jain, My Bad! 範例中的座標計算未處理好跨瀏覽器Issue,稍晚再釋出修正,不好意思造成大家困擾。

# 阿遠 said on 31 October, 2011 04:56 AM

只有FF(7.0.1)不行

chrome17 ie9 opera12

都可以玩~

# Li said on 31 October, 2011 07:18 AM

@阿遠

我的FF7.0.1可以喔,速度也很快。

# Jeffrey said on 31 October, 2011 11:38 AM

Hi all, 程式修改過了,現在IE9,FF,Chrome,Safari,Opera應該都可以跑,謝謝大家的回饋。

# MotoVB said on 01 November, 2011 09:47 PM

厲害哩~

# 張逸中 said on 24 March, 2012 06:45 PM

模仿閣下的程式,我也做了一個網頁塗鴉程式:

ycc.tsu.edu.tw/.../Painter.htm

主要是當作網頁設計課的教學單元之用,

怕學生覺得難,我的版本還更簡單一點。

真的很感謝您的程式分享!

好玩又簡單寫的程式範例不多,學生都很喜歡。

# 路人喵 said on 06 May, 2012 10:34 PM

暗黑老大的sample code好棒哦~~樓上寫給學生用的範例也好棒哦~~

# 電腦玩瞎咪 said on 29 May, 2012 08:28 AM

您好,不知可否將這個原始碼修改後放置自己的網站上?

需要特別註明什麼嗎?

# Jeffrey said on 31 May, 2012 05:52 AM

to 電腦玩瞎咪, 歡迎修改利用,請在網頁或程式碼註明參考來源連結回本網頁即可。

# 電腦玩瞎咪 said on 02 June, 2012 11:32 PM

To Jeffrey,非常謝謝您 :)

# 電腦玩瞎咪 said on 15 July, 2012 01:12 AM

您好我已經將您的技術搬上網站了 (咦

這裡有篇效果

xiaoan888.co.cc/.../post.php

我也在繪畫版註明是由您這裡使用程式碼的。

謝謝。

# Mason said on 17 January, 2013 04:29 PM

如果要加Undo功能,不知道能不能指點一下

# Jeffrey said on 21 January, 2013 12:45 AM

to Mason, 依我個人看法,要實現Undo,表示要以Sroke概念記下畫圖動作以便取消或重繪,會比直接lineTo()直接對Canvas進行不可逆修改複雜頗高。另一個較易實現的做法是lineTo前先把未繪圖前的Canvas以快照概念保留一份(可以只記錄會影響的區塊),程式較好寫,但會耗費可觀的記憶體就是了。

# rwu said on 02 April, 2013 07:01 AM

感覺 .mouseleave 後也要 drawMode = false 比較合理 :D ?

# Remi said on 08 July, 2013 02:32 AM

請問如果想把塗鴉板的滑鼠事件改成觸控要怎麼做?

我使用touchstart, touchmove, touchend指令, 但是好像都不work欸, 是漏掉什麼嗎?

# Reggiewen said on 30 July, 2013 11:53 PM

我想請問一下這些Code可以在手機的網頁程式裡面應用嗎?

我照樣寫了一個在電腦裡面的Chrome可以使用

但是在Android手機裡面的Chrome....畫面可以開但是就是畫不出線條....請問一下暗黑大大知不知道可能的原因....謝謝....

# 大龍 said on 21 November, 2013 10:28 AM

手機不行畫~嗚~

# 凱文 said on 24 November, 2015 08:29 PM

請問黑暗大大,畫布的底圖能放一張照片嗎?

# Jeffrey said on 24 November, 2015 09:54 PM

to 凱文, 要在畫佈繪製圖片可以用drawImage(),但圖檔來源必須來自同網站(或網域),參考 www.html5canvastutorials.com/.../html5-canvas-images

# 凱文 said on 28 November, 2015 10:21 PM

我可以私訊請您撰寫一段語法嗎?

pan**1@gmail.com

# sheena said on 22 February, 2016 08:52 AM

版主你好 很感謝你分享的程式碼 可是我把事件改成touchstart、touchmove與touchend就好像沒有像網頁這麼順了@@  請問有解決方法嗎?

# Jeffrey said on 22 February, 2016 08:05 PM

to sheena, jQuery並沒有內建touchstart/touchmove等事件,而觸控座標的抓法複雜許多(要考慮多點的情況),而瀏覽器實做尚未完美統一(參考:blog2.darkthread.net/post-2012-11-19-mutlitouch-for-ie10.aspx),評估需要大幅改寫才能支援觸控。

你的看法呢?

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

5 + 3 =

搜尋

Go

<October 2011>
SunMonTueWedThuFriSat
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

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

文章典藏
其他功能

這個部落格


Syndication