Autotel

Micro guide to multi-touch events

There isn't much to say about how this works. if you are here, I am sure that you will understand the code just by reading it.

The code a very bare-bones working example of multi-touch interaction. One thing that was not obvious to me at the beginning, is that I needed to use event.targetTouches instead of event.changedTouches. The former property is more useful to track touches, as it provides the still existing touches, whereas the latter provides what has changed only.

I also added some colors, because they make us happy.

Code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no">
    <title>Multi touch test</title>
</head>
<style>
    body {
        color: white;
        background-color: #73777D;
    }

    #container {
        position:fixed;
        left: 0;
        top: 0;
    }

    #pseudo-console {
        position:fixed;
        left: 0;
        top: 0;
    }
    
    #container .touch-element {
        border: solid 3px;
        border-radius: 50%;
        width: 70px;
        height: 70px;
        position: fixed;
        text-align: center;
        padding-top: -30px;
        color: white;
    }

    #container .touch-element:nth-child(1){
        background-color: #011627;
    }
    #container .touch-element:nth-child(2){
        background-color: #2EC4B6;
    }
    #container .touch-element:nth-child(3){
        background-color: #E71D36;
    }
    #container .touch-element:nth-child(4){
        background-color: #FF9F1C;
    }
    #container .touch-element:nth-child(5){
        background-color: #0061AA;
    }
    #container .touch-element:nth-child(6){
        background-color: #73777D;
    }
    #container .touch-element:nth-child(7){
        background-color: #2EC4B6;
    }
    #container .touch-element:nth-child(8){
        background-color: #FF9F1C;
    }
    #container .touch-element:nth-child(9){
        background-color: #2EC4B6;
    }
    #container .touch-element:nth-child(10){
        background-color: #E71D36;
    }
    .hide {
        display: none;
    }
</style>
<body>
    <div id="container"></div>
    <div id="pseudo-console"></div>
</body>
<script>

const container = document.getElementById("container");
const pseudoLog = (tx) => {
    const cont = document.getElementById("pseudo-console");
    cont.innerHTML = JSON.stringify(tx);
};

const TouchDiv = function () {
    let diameter = 100;
    let radius = (diameter / 2);
    const element = document.createElement("div");
    element.classList.add("touch-element");
    container.appendChild(element);

    this.position = (coords) => {
        element.style.left = (coords.x - radius) + "px";
        element.style.top = (coords.y - radius) + "px";
        if(coords.diameter) {
            diameter = coords.diameter;
            radius = diameter / 2;
            element.style.width = diameter + "px";
            element.style.height = diameter + "px";
        };
    }

    this.text = (txt) => {
        element.innerHTML = txt + "";
    }
    this.hide = () => {
        element.classList.add("hide");
    }
    this.show = () => {
        element.classList.remove("hide");

        element.style.width = diameter + "px";
        element.style.height = diameter + "px";
    }
}

const touchSprites = [];

const touchesUpdate = (touches) => {

    for (let touch of touches) {
        const number = touch.identifier;

        if(!touchSprites[number]){
            touchSprites [number] = new TouchDiv();
        }

        touchSprites[number].show();
        const tts = touchSprites[number];

        tts.position({
            x: touch.clientX,
            y: touch.clientY,
            // diameter:touch.radiusX, //not supported everywhere
        });
        tts.text(number);
    }

    touchSprites.slice(touches.length).forEach((touchSprite)=>touchSprite.hide());
}

document.addEventListener("touchmove",(event) => {
    event.preventDefault();
    touchesUpdate(event.targetTouches);
});

document.addEventListener("touchstart",(event) => {
    event.preventDefault();
    touchesUpdate(event.targetTouches);
});

document.addEventListener("touchend",(event) => {
    event.preventDefault();
    touchesUpdate(event.targetTouches);
});

</script>
</html>

Setup and observations

I tested this on a raspberry-pi to which I added a 7-inch touch display. I had one computer with the source code, which had a static server serving the multi touch test html file. On the raspberry pi, I opened chromium on kiosk mode, pointing to the url of my code. Alternatively you could transfer the html file to the raspberry pi filesystem.

Static serve the html:

light-server -s ./ -b localhost -p:4000

Open chromium via ssh, but show it on the local display:

DISPLAY=:0 chromium-browser --kiosk "http://192.168.1.3:4000/multi-touch-test.html" -touch-devices=2 --remote-debugging-port=9000 --disable-pinch

It works well, although the animation ends up having a low frame rate. I yet have to find out whether this is due to the touch driver report rate being low, or the web app being too much for the processor handle.

Questions and collaboration

If you are left with any questions or you want to suggest an improvement to this code, go to the Gist in github.