【问题标题】:Touch event handler overrides click handlers触摸事件处理程序覆盖点击处理程序
【发布时间】:2015-03-28 22:12:17
【问题描述】:

我正在 AngularJS 中创建一个自定义的可拖动指令。它是 jQuery 事件和 vanilla javascript 的组合。我试图让它尽可能通用和可重复使用,而且它还必须是触摸友好的。

TL;DR

我无法在触摸环境中单击可拖动指令上的按钮。

重现步骤:

  1. 打开代码笔示例:CodePen
  2. 在 Chrome 上,F12,模拟 iPad 3/4
  3. 按标题拖动面板 = 作品!
  4. 单击按钮 = 无警报。

更长的解释

该指令可选地使其放置在其上的整个元素可拖动,除非放置了具有“拖动句柄”类的元素,在这种情况下,该元素用作该元素的拖动句柄。我通常将它与引导面板一起使用,因为它是一个简单的示例。

该指令在桌面上运行良好,但在触摸设备上,如果拖动句柄上有任何可点击的项目,则拖动处理程序会覆盖点击事件并且它永远不会被调用。

示例 HTML 将是:

<div class="panel panel-default" app-draggable>
    <div class="panel-heading drag-handle"> <!-- Drag Handle -->
        <div class="panel-title">
            Example Title
            <button onclick="alert('clicked')" class="btn btn-xs btn-primary pull-right" type="button">Click</button>
        </div>
    </div>
    <div class="panel-body">Example body</div>
</div>

因此,在台式机上,您既可以拖动面板,也可以单击按钮来获取警报。但是,当我在 Chrome 上模拟 iPad 3/4(或在真正的 iPad 上将其拉起)时,从未触发过点击。

我的指令如下。它将容器设置为绝对的(除非容器已经固定,在这种情况下它将补偿并仍然使其可拖动。

 /*
 * @summary
 * Directive that makes an element draggable.
 * @description
 * This directive should be used in conjunction with specifying a drag handle 
 * on the element. If not, then entire element will be draggable.
 * @example
 * <div class='myDiv' app-draggable>
 *   <div class='drag-handle'>This will be the drag handle</div>
 *   <div>This will be dragged</div>
 * </div>
 */
angular.module("app")
    .directive('appDraggable', appDraggable);

function appDraggable() {
    var directive = {
        restrict: 'A',
        link: link
    };

    function link(scope, element) {
        var startX = 0, startY = 0, x = 0, y = 0;
        var startTop;
        var startLeft;
        var dragHandle = element[0].querySelector(".drag-handle");
        var dragHandleElement;
        /*
         * If there is a dragHandle specified, add the touch events to it.
         * Otherwise, make the entire element draggable.
         */ 
        if (dragHandle) {
            dragHandleElement = angular.element(dragHandle);
            addTouchHandlers(dragHandle);
        } else {
            dragHandleElement = element;
            addTouchHandlers(element[0]);
        }

        var position = element.css('position');

        if (position !== "absolute") {
            if (position === "fixed") {
                // If fixed, get the start offset relative to the document.
                startTop = element.offset().top;
                startLeft = element.offset().left;
                /*
                 * Explicitly set the height and width of the element to prevent
                 * overrides by preset values.
                 */ 
                var height = parseInt(element.height(), 10);
                var width = parseInt(element.width(), 10);
                element.css({
                    height: height,
                    width: width
                });
            } else {
                // If it's not fixed, it needs to be absolute.
                element.css({
                    position: 'absolute',
                });
                // And positioned originally relative to the parent.
                startTop = element.position().top;
                startLeft = element.position().left;
            }
        }


        /*
         * @function
         * @description
         * Add event handlers to the drag handle to capture events.
         */
        dragHandleElement.on('mousedown', function (event) {

            /*
             * Prevent default dragging of selected content
             */
            event.preventDefault();
            startX = event.pageX - x;
            startY = event.pageY - y;
            dragHandleElement.on('mousemove', mousemove);
            dragHandleElement.on('mouseup', mouseup);
        });

        function mousemove(event) {

            y = event.pageY - startY;
            x = event.pageX - startX;
            var finalTop = y + startTop;
            var finalLeft = x + startLeft;
            element.css({
                top: finalTop + 'px',
                left: finalLeft + 'px'
            });
        }

        function mouseup() {
            dragHandleElement.off('mousemove', mousemove);
            dragHandleElement.off('mouseup', mouseup);
        }

        function touchHandler(event) {
            var touch = event.changedTouches[0];

            if (event.target !== dragHandleElement) {
                //////////////// HACK ///////////////////////////
                //event.target.click(); // Hack as a work around.
            }

            var simulatedEvent = document.createEvent("MouseEvent");
            simulatedEvent.initMouseEvent({
                touchstart: "mousedown",
                touchmove: "mousemove",
                touchend: "mouseup"
            }[event.type], true, true, window, 1,
            touch.screenX, touch.screenY,
            touch.clientX, touch.clientY, false,
            false, false, false, 0, null);

            touch.target.dispatchEvent(simulatedEvent);
            event.preventDefault();
        }

        function addTouchHandlers(element) {
            element.addEventListener("touchstart", touchHandler, true);
            element.addEventListener("touchmove", touchHandler, true);
            element.addEventListener("touchend", touchHandler, true);
            element.addEventListener("touchcancel", touchHandler, true);
        }


    }
    return directive;
}

您会注意到上面的指令中有一个 hack:

if (event.target !== dragHandleElement) {
     //////////////// HACK ///////////////////////////
     //event.target.click(); // Hack as a work around.
}

如果我取消注释,它适用于触摸设备,因为它会检查触摸目标是否是 dragHandle,如果不是,则手动单击目标。这可行,但对我来说似乎很讨厌,我真的想要一个更好的解决方案。它不会返回 false 或 stopPropagation,因为目标并不总是直接的 dragHandle,但它仍然需要拖动。

我不知道为什么这不起作用,因为它不会手动停止触摸事件的传播,因为它使用 event.preventDefault 而不是 event.stopPropagation,但我确定我错过了东西。

您可以复制here

此外,欢迎任何其他关于如何改进上述代码以使其与平台设备无关或更健壮的建议!

想法?

谢谢!

【问题讨论】:

    标签: javascript jquery html angularjs touch


    【解决方案1】:

    找到问题了。

    我上面的touchHandler 函数总是在触摸时传输“mousedown”事件,即使它更准确地说是它应该传输的“click”事件。由于我所有的事件处理程序都在寻找“click”事件,因此他们忽略了正在传输的“mousedown”事件。

    我将我的 touchHandler 函数更改为下面的函数,它就像一个魅力。

        var mouseMoved = false;
        function touchHandler(event) {
            // Declare the default mouse event.
            var mouseEvent = "mousedown";
            // Create the event to transmit.
            var simulatedEvent = document.createEvent("MouseEvent");
    
            switch (event.type) {
            case "touchstart":
                mouseEvent = "mousedown";
                break;
            case "touchmove":
                /*
                * If this has been hit, then it's a move and a mouseup, not a click
                * will be transmitted.
                */
                mouseMoved = true;
                mouseEvent = "mousemove";
                break;
            case "touchend":
                /*
                * Check to see if a touchmove event has been fired. If it has
                * it means this have been a move and not a click, if not
                * transmit a mouse click event.
                */
                if (!mouseMoved) {
                    mouseEvent = "click";
                } else {
                    mouseEvent = "mouseup";
                }
                // Initialize the mouseMove flag back to false.
                mouseMoved = false;
                break;
            }
    
            var touch = event.changedTouches[0];
    
            /*
             * Build the simulated mouse event to fire on touch events.
             */
            simulatedEvent.initMouseEvent(mouseEvent, true, true, window, 1,
            touch.screenX, touch.screenY,
            touch.clientX, touch.clientY, false,
            false, false, false, 0, null);
    
            /*
             * Transmit the simulated event to the target. This, in combination
             * with the case statement above, ensures that click events are still
             * transmitted and bubbled up the chain.
             */
            touch.target.dispatchEvent(simulatedEvent);
    
            /*
             * Prevent default dragging of element.
             */
            event.preventDefault();
        }
    

    此实现在touchstarttouchend 之间查找touchmove 事件。如果有,则它设置一个标志并传输click 事件而不是mousedown 事件。

    它也可以与计时器一起使用,这样即使鼠标移动了一点,它也会发送一个点击事件,但对于我的目的来说,这非常有效。

    【讨论】:

      猜你喜欢
      • 2010-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多