检测用于 polyfilling 的 iOS 指针捕获错误

Tha*_*Guy 5 javascript safari ios

尝试在触摸和手写笔输入上使用指针捕获时,iOS 13/14 似乎存在错误。问题在于,setPointerCapture当在不是事件原始目标的元素上调用时,调用实际上并没有重定向未来的事件。但是,调用hasPointerCapture仍然有效并按true预期返回。有什么办法可以对这个问题进行功能测试,以便为它实现一个polyfill?

如果有帮助,我在 GitHub 上为这个问题创建了一个演示。如果指针在相应的元素上开始,绿色和黄色 div 将正确跟踪指针移动,但橙色 div 的行为应相同,红色 div 不应移动。只要指针停留在 div 上,后者仍然会跟踪指针。

我目前用于填充错误的代码如下。它可以工作,但它的检测过程是一个 UA 字符串测试,因此即使它们共享相同的 JavaScript 引擎,它也不会在其他 iOS 浏览器中工作。

// Need to figure out some way to test if this is needed.

if (navigator.userAgent.match(/Version\/1[34]\.\d+(?:\.\d+)? Safari/)) {
  const {
    setPointerCapture: set,
    hasPointerCapture: has,
    releasePointerCapture: release
  } = Element.prototype

  let targets = {},
      captures = {}

  Element.prototype.setPointerCapture = function setPointerCapture(pointerId) {
    if (pointerId in captures) {
      if (document.contains(this)) {
        captures[pointerId] = this
        return set.call(targets[pointerId], pointerId)
      } else {
        throw new TypeError("Element not in valid location")
      }
    } else {
      return set.call(this, pointerId)
    }
  }
  Element.prototype.hasPointerCapture = function hasPointerCapture(pointerId) {
    if (pointerId in captures) {
      return captures[pointerId] == this
    } else {
      return has.call(this, pointerId)
    }
  }
  Element.prototype.releasePointerCapture = function releasePointerCapture(pointerId) {
    if (pointerId in captures) {
      if (this.hasPointerCapture(pointerId)) {
        captures[pointerId] = null
        return release.call(targets[pointerId], pointerId)
      }
    } else {
      return release.call(this, pointerId)
    }
  }

  let registerPointer = function registerPointer(event) {
        if (event.pointerType == "touch" || event.pointerType == "pen") {
          targets[event.pointerId] = event.target
          captures[event.pointerId] = null
        }
      },
      redirectPointer = function redirectPointer(event) {
        if (captures[event.pointerId] != null && captures[event.pointerId] != event.target) {
          // Stop the original event
          event.preventDefault()
          event.stopPropagation()

          // Redispatch a new, cloned event
          captures[event.pointerId].dispatchEvent(new PointerEvent(event.type, event))
        }
      },
      redirectAndUnregisterPointer = function redirectAndUnregisterPointer(event) {
        redirectPointer(event)
        delete targets[event.pointerId]
        delete captures[event.pointerId]
      }

  addEventListener("pointerdown", registerPointer, {capture: true, passive: true})
  addEventListener("pointermove", redirectPointer, {capture: true, passive: false})
  addEventListener("pointerup", redirectAndUnregisterPointer, {capture: true, passive: false})
  addEventListener("pointercancel", redirectAndUnregisterPointer, {capture: true, passive: false})
}

Run Code Online (Sandbox Code Playgroud)