Source: input/touch.js

Object.assign(pc, function () {
    /**
     * @constructor
     * @name pc.Touch
     * @classdesc A instance of a single point touch on a {@link pc.TouchDevice}
     * @description Create a new Touch object from the browser Touch
     * @param {Touch} touch The browser Touch object
     * @property {Number} id The identifier of the touch
     * @property {Number} x The x co-ordinate relative to the element that the TouchDevice is attached to
     * @property {Number} y The y co-ordinate relative to the element that the TouchDevice is attached to
     * @property {Element} target The target element of the touch event
     * @property {Touch} touch The original browser Touch object
     */
    var Touch = function (touch) {
        var coords = pc.getTouchTargetCoords(touch);

        this.id = touch.identifier;

        this.x = coords.x;
        this.y = coords.y;

        this.target = touch.target;

        this.touch = touch;
    };

    /**
     * @constructor
     * @name pc.TouchEvent
     * @classdesc A Event corresponding to touchstart, touchend, touchmove or touchcancel. TouchEvent wraps the standard
     * browser event and provides lists of {@link pc.Touch} objects.
     * @description Create a new TouchEvent from an existing browser event
     * @param {pc.TouchDevice} device The source device of the touch events
     * @param {TouchEvent} event The original browser TouchEvent
     * @property {Element} element The target Element that the event was fired from
     * @property {pc.Touch[]} touches A list of all touches currently in contact with the device
     * @property {pc.Touch[]} changedTouches A list of touches that have changed since the last event
     */
    var TouchEvent = function (device, event) {
        this.element = event.target;
        this.event = event;

        this.touches = [];
        this.changedTouches = [];

        if (event) {
            var i, l = event.touches.length;
            for (i = 0; i < l; i++) {
                this.touches.push(new Touch(event.touches[i]));
            }

            l = event.changedTouches.length;
            for (i = 0; i < l; i++) {
                this.changedTouches.push(new Touch(event.changedTouches[i]));
            }
        }
    };

    Object.assign(TouchEvent.prototype, {
        /**
         * @function
         * @name pc.TouchEvent#getTouchById
         * @description Get an event from one of the touch lists by the id. It is useful to access
         * touches by their id so that you can be sure you are referencing the same touch.
         * @param {Number} id The identifier of the touch.
         * @param {pc.Touch[]} list An array of touches to search.
         * @returns {pc.Touch} The {@link pc.Touch} object or null.
         */
        getTouchById: function (id, list) {
            var i, l = list.length;
            for (i = 0; i < l; i++) {
                if (list[i].id === id) {
                    return list[i];
                }
            }

            return null;
        }
    });

    /**
     * @constructor
     * @name pc.TouchDevice
     * @classdesc Attach a TouchDevice to an element and it will receive and fire events when the element is touched.
     * See also {@link pc.Touch} and {@link pc.TouchEvent}
     * @description Create a new touch device and attach it to an element
     * @param {Element} element The element to attach listen for events on
     */
    var TouchDevice = function (element) {
        this._element = null;

        this._startHandler = this._handleTouchStart.bind(this);
        this._endHandler = this._handleTouchEnd.bind(this);
        this._moveHandler = this._handleTouchMove.bind(this);
        this._cancelHandler = this._handleTouchCancel.bind(this);

        this.attach(element);

        pc.events.attach(this);
    };

    Object.assign(TouchDevice.prototype, {
        /**
         * @function
         * @name pc.TouchDevice#attach
         * @description Attach a device to an element in the DOM.
         * If the device is already attached to an element this method will detach it first
         * @param {Element} element The element to attach to
         */
        attach: function (element) {
            if (this._element) {
                this.detach();
            }

            this._element = element;

            this._element.addEventListener('touchstart', this._startHandler, false);
            this._element.addEventListener('touchend', this._endHandler, false);
            this._element.addEventListener('touchmove', this._moveHandler, false);
            this._element.addEventListener('touchcancel', this._cancelHandler, false);
        },

        /**
         * @function
         * @name pc.TouchDevice#detach
         * @description Detach a device from the element it is attached to
         */
        detach: function () {
            if (this._element) {
                this._element.removeEventListener('touchstart', this._startHandler, false);
                this._element.removeEventListener('touchend', this._endHandler, false);
                this._element.removeEventListener('touchmove', this._moveHandler, false);
                this._element.removeEventListener('touchcancel', this._cancelHandler, false);
            }
            this._element = null;
        },

        _handleTouchStart: function (e) {
            this.fire('touchstart', new TouchEvent(this, e));
        },

        _handleTouchEnd: function (e) {
            this.fire('touchend', new TouchEvent(this, e));
        },

        _handleTouchMove: function (e) {
            // call preventDefault to avoid issues in Chrome Android:
            // http://wilsonpage.co.uk/touch-events-in-chrome-android/
            e.preventDefault();
            this.fire('touchmove', new TouchEvent(this, e));
        },

        _handleTouchCancel: function (e) {
            this.fire('touchcancel', new TouchEvent(this, e));
        }
    });

    return {
        /**
         * @function
         * @name pc.getTouchTargetCoords
         * @description Similiar to {@link pc.getTargetCoords} for the MouseEvents.
         * This function takes a browser Touch object and returns the co-ordinates of the
         * touch relative to the target element.
         * @param {Touch} touch The browser Touch object
         * @returns {Object} The co-ordinates of the touch relative to the touch.target element. In the format {x, y}
         */
        getTouchTargetCoords: function (touch) {
            var totalOffsetX = 0;
            var totalOffsetY = 0;
            var target = touch.target;
            while (!(target instanceof HTMLElement)) {
                target = target.parentNode;
            }
            var currentElement = target;

            do {
                totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
                totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
                currentElement = currentElement.offsetParent;
            } while (currentElement);

            return {
                x: touch.pageX - totalOffsetX,
                y: touch.pageY - totalOffsetY
            };
        },

        TouchDevice: TouchDevice,
        TouchEvent: TouchEvent
    };
}());