相信大家應該看過不少用HTML5做的繪圖板範例(例如: 這個這個這個這個),事實上,只要一個canvas元素配合mousedown, mousemove, mouseup事件,加上幾行Code我們就可以自己寫一個陽春手繪板。不過,這種陽春寫法在iPad、iPhone、Android等觸控平台瀏覽器就吃不開了。

在觸控操作時,自然不會有mousedown、mousemove、mouseup這些事件(難道得抓一隻黃金鼠在螢幕上磨蹭嗎?),而是另外定義了touchstart、touchmove、touchend等觸控平台的專屬事件(參考: iOS APIW3C草案),HTML5 Rocks有一篇很棒的文章介紹開發支援觸控事件網頁的相關知識,小小地研究一番,以下是我的心得筆記:

  1. 在touchstart、touchmove、touchend等事件中,可透過事件參數的touches屬性取得多個觸點位置,可以實現多點觸控甚至捕捉複雜手勢的網頁。(但偵測手勢的演算法會是一大挑戰)
  2. 事件參數提供了touches(螢幕上所有的觸點)、targetTouchs(接觸到特定DOM元素的觸點)、changedTouches(目前事件中發生變化的觸點,例如: touchmove事件移動中的觸點或touchend事件離開螢幕的觸點)等觸點資訊。
  3. 上述觸點資訊物件會提供幾個屬性: identifier(識別碼,可供在多個事件中持續追蹤同一觸點的變化)、target(觸點所操作的DOM元素)、client/page/screen座標(相對於元素、網頁、螢幕的座標)、radius座標及rotationAngle(可用來描述手指接觸面形狀,但目前尚無瀏覽器支援)
  4. 為避免原本瀏覽器的兩指縮放、拖拉捲動操作干擾,可加入viewpoint meta及加掛document.body touchmove事件將其停用,寫法如下:
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    document.body.addEventListener('touchmove', function(event) {
      event.preventDefault();
    }, false);
  5. 由於touchmove的觸發頻率很高,若每次被呼叫就立即執行回應邏輯,可能造成系統過度負擔,因此可在touchmove時將touches等資訊保存下來,再透過setInterval以固定頻率執行回應邏輯。(window.requestAnimationFrame會依瀏覽器更新動畫的頻率觸發,可完全配合裝置基於效能及省電考量下的動態調整,算是更好的解決方案,但要考量瀏覽器是否有支援)
  6. 在PC環境下摸擬觸控效果可降低開發及測試的難度,有個很不錯的Touchable jQuery Plugin可以將mousedown/mousemove/mouseup對應成觸控事件。事實上,它另外定義了tap、touchablemove、touchableend等事件,可同時對應到滑鼠操作及觸控,讓網頁程式可同時支援滑鼠及觸控兩種操作平台,如果網頁只要做到單點觸控,算是很好的跨平台選擇。
  7. 針對MacBook或MagicPad等支援多點觸控的裝備,HTML5 Rocks文章有提供一套在Mac實現瀏覽器多點觸控的方法。
  8. 從jquery.UI.iPad plugin偷學到簡單偵測瀏覽器是否支援觸控的方法:
    $.extend($.support, { touch: "ontouchend" in document });
    以上程式碼擴充了jQuery,如此便可由$.support.touch傳回true或false偵測是否瀏覽器是否有觸控功能囉~

光說不練是嘴砲,實彈射擊不可少!

想實地觀察多點觸控事件所能擷取的資訊,所以我寫了以下的測試網頁:

<!DOCTYPE html>
 
<html>
<head>
    <title>Mutli-Touch Test</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.4.js" 
            type="text/javascript"> </script>   
    <meta name="viewport" 
     content="width=device-width, initial-scale=1.0, user-scalable=no">
    <script>
        $.extend($.support, { touch: "ontouchend" in document });
        $(function () {
            //排除不支援觸控的瀏覽器
            if (!$.support.touch) {
                $("body").html("<span>Not a touchable device!</span>");
                return;
            }
            //停用頁面捲動功能
            document.body.addEventListener('touchmove', function (event) {
                event.preventDefault();
            }, false);
            //使用canvas繪製回應
            var canvas = document.getElementById("sketchpad");
            var ctx = canvas.getContext("2d");
            //宣告變數用來儲存touchstart, touchmove, touchend時傳回的碰觸資訊
            var touches = [], changedTouches = [];
            canvas.addEventListener("touchstart", function (e) {
                touches = e.touches
            });
            canvas.addEventListener("touchmove", function (e) {
                touches = e.touches
                changedTouches = e.changedTouches;
            });
            canvas.addEventListener("touchend", function (e) {
                touches = e.touches
                changedTouches = [];
            });
            //定義不同顏色用來追蹤多點
            var colors =
                ("red,orange,yellow,green,blue,indigo,purple," +
                "aqua,khaki,darkred,lawngreen,salmon,navy," +
                "deeppink,brown,olive,violet,tomato,gray").split(',');
            //在canvas繪製追蹤點
            ctx.lineWidth = 3;
            ctx.font = "10pt Arial";
            var r = 40;
            function drawPoint(i, x, y, c, id, chg) {
                ctx.beginPath();
                ctx.fillStyle = c;
                //若屬changedTouches則顯示黑框
                ctx.strokeStyle = chg ? "#000" : c;
                ctx.arc(x, y, r, 0, 2 * Math.PI, true);
                ctx.fill();
                ctx.stroke();
                //顯示touch的identifier及其在陣列中的序號
                //touches在上排藍字,chagedTouches在下排紅字
                ctx.fillStyle = chg ? "red" : "blue";
                ctx.fillText(id + "-" + i,
                    x - r, y - r - 25 + (chg ? 15 : 0));
            }
            //清除canvas
            function clearCanvas() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
            }
            //利用identifier識別,相同時要保持同一個顏色
            var touchHash = {}, colorIdx = 0;
            function getColor(id) {
                if (touchHash[id] == undefined)
                    touchHash[id] = ++colorIdx % colors.length;
                return colors[touchHash[id]];
            }
            //每秒更新20次,顯示目前的多點觸控資訊
            setInterval(function () {
                clearCanvas();
                for (var i = 0; i < touches.length; i++) {
                    var t = touches[i];
                    drawPoint(i, t.pageX, t.pageY, getColor(t.identifier), 
                              t.identifier);
                }
                for (var i = 0; i < changedTouches.length; i++) {
                    var t = changedTouches[i];
                    drawPoint(i, t.pageX, t.pageY, getColor(t.identifier), 
                              t.identifier, true);
                }
            }, 50);
        });
    </script>
</head>
<body style="padding: 0px; margin: 0px;">
<canvas id="sketchpad" width="1024" height="680" style="border: 1px solid gray">
</canvas>
</body>
</html>

這算是一個將多點觸控資訊視覺化的小工具,它會將touchstart, touchmove, touchend抓到的touches及changedTouches以圓點方式呈現在網頁上,利用identifier維持同一點的顏色不變以觀察瀏覽器對同一點的定義,再以是有無黑框區別touches(無框)或changedTouches(黑框)。另外,identifier及touch物件在touches或changedTouches陣列中的序號會被標明在圓點的右上方,這樣子就能清楚地觀察觸控過程各觸點資訊的變化。

有觸控裝置的朋友可直接開線上展示玩玩。目前,我只在iPad上測過,如果大家在其他機器測到問題,歡迎送我一台測試機我保證會將程式修到好留言回饋跟大家分享。


Comments

# by 小黑

Good

# by Retiam

It works well ! Thanks for your sharing code! - -sent from my transformer

# by SMUGEN

在 Android 3.2.1 上面測試 原生內建的可以支援到10點,再多我沒測,除非腳趾頭也拿來試XD 不過 Opera Mobile (不是mini) 就沒反應了, Firefox 則是直接不支援觸控

# by Hero

Good Job

# by LAI

想請問 這是怎麼抓觸控點的?? 依據什麼原理?? 謝謝您的回答

# by Jeffrey

to LAI, 不確定你詢問的是觸控感應的電子原理,還是程式層面的運作原理? 若為前者: http://zh.wikipedia.org/wiki/%E5%A4%9A%E9%BB%9E%E8%A7%B8%E6%8E%A7,若為後者: 觸控裝置上的瀏覽器有額外的邏輯,會在使用者進行觸控操作時觸發特別的事件並傳入觸控位置的相關資訊,我們的程式可以攔截這些事件,解析控制點的資料,就能對使用者的操作做出回應。

# by fsps60312

我的電腦有觸控螢幕 可是在Edge上直接顯示「Not a touchable device!」

# by Jeffrey

to fs60312, 多點觸控尚無跨瀏覽器的統一標準,IE或Edge並不支援範例程式所需的觸控事件(參考:https://developer.mozilla.org/zh-TW/docs/Web/Events/touchend ),如想在Edge實現需另謀他法。

Post a comment