第一次滑动后,React 自定义轮播组件中的 IOS Safari 上的 Touchevents 未触发

Touchevents not firing on IOS Safari in React custom carousel component after first swipe

我在 React 中创建了一个自定义照片轮播组件(因为外部库要么太难使用,要么没有做我想要它做的事情),您可以在其中滑动到 next/previous手机上的照片。在 Android 上一切正常,但它只是 IOS Safari。

问题

我有一个页面绘制了几个旋转木马。地图中的第一个轮播效果非常好。随后的轮播将在第一张幻灯片之后正确滑动,但一旦它过渡到第二张幻灯片,触摸事件就会停止触发,并且不会滑动。我要的是第一个轮播一样的所有轮播。也没有看到错误消息。见 video:

代码

这是自定义组件:

import { useState, useEffect } from 'react'

const Carousel = ({ children }) => {
  const IMG_WIDTH = 400

  const [currentIndex, setCurrentIndex] = useState(0)
  const [lastTouch, setLastTouch] = useState(0)
  const [movement, setMovement] = useState(0)
  const [transitionDuration, setTransitionDuration] = useState('')
  const [transitionTimeout, setTransitionTimeout] = useState(null)
  const maxLength = children.length - 1,
    maxMovement = maxLength * IMG_WIDTH

  useEffect(() => {
    return () => {
      clearTimeout(transitionTimeout)
    }
  }, [])

  const transitionTo = (index, duration) => {
    setCurrentIndex(index)
    setMovement(index * IMG_WIDTH)
    setTransitionDuration(`${duration}s`)

    setTransitionTimeout(
      setTimeout(() => {
        setTransitionDuration('0s')
      }, duration * 100))
  }

  const handleMovementEnd = () => {
    const endPosition = movement / IMG_WIDTH
    const endPartial = endPosition % 1
    const endingIndex = endPosition - endPartial
    const deltaInteger = endingIndex - currentIndex

    let nextIndex = endingIndex

    if (deltaInteger >= 0) {
      if (endPartial >= 0.1) {
        nextIndex++
      }
    } else if (deltaInteger < 0) {
      nextIndex = currentIndex - Math.abs(deltaInteger)
      if (endPartial > 0.9) {
        nextIndex++
      }
    }

    transitionTo(nextIndex, Math.min(0.5, 1 - Math.abs(endPartial)))
  }

  const handleMovement = delta => {
    clearTimeout(transitionTimeout)

    const maxLength = children.length - 1
    let nextMovement = movement + delta

    if (nextMovement < 0) {
      nextMovement = 0
    }

    if (nextMovement > maxLength * IMG_WIDTH) {
      nextMovement = maxLength * IMG_WIDTH
    }
    setMovement(nextMovement)
    setTransitionDuration('0s')
  }

  const handleTouchStart = event => {
    setLastTouch(event.nativeEvent.touches[0].clientX)
  }

  const handleTouchMove = event => {
    const delta = lastTouch - event.nativeEvent.touches[0].clientX
    setLastTouch(event.nativeEvent.touches[0].clientX)
    handleMovement(delta)
  }

  const handleTouchEnd = () => {
    handleMovementEnd()
    setLastTouch(0)
  }

  return (
    <div
      className="main"
      style={{ width: IMG_WIDTH }}
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleTouchEnd}>
      <div
        className="swiper"
        style={{
          transform: `translateX(${movement * -1}px)`,
          transitionDuration: transitionDuration
        }}>
        {children} // This is just <img /> tags
      </div>
      <div className="bullets">
        {[...Array(children.length)].map((bullet, index) => (
          <div key={`bullet-${index}`} className={`dot ${currentIndex === index && 'red-dot'}`} />
        ))}
      </div>
    </div>
  )
}

export default Carousel

这是我使用自定义组件的代码部分:

return (
    <>
      <Announcement />
      <Header />

      <section className="tiles-section">
        <Title />
        {Component} // This is just a component that simply maps out the Carousel, nothing special and no extra styling
      </section>
      ...
    </>
  )

和CSS:

.main {
    overflow: hidden;
    position: relative;
    touch-action: pan-y;
}

.swiper {
    display: flex;
    overflow-x: visible;
    transition-property: transform;
    will-change: transform;
}

什么我know/tried

更新 #1

我将触摸事件处理程序放在包装 {child} 的新 div 中。第二张幻灯片现在可以滑动,但第三张幻灯片仍然是半身像。

参考文献

这是教程 link 我按照此教程创建了轮播以获取自定义轮播组件的更多上下文。

几天几小时后,解决方案有点奇怪。我猜您需要将目标正确设置为元素的 div id。我把它放在 useEffect 中,所以结果是这样的:

useEffect(() => {
    document.getElementById(id).addEventListener('touchstart', () => false, { passive: false })

    return () => {
      clearTimeout(transitionTimeout)
    }
  }, [])

请注意,id 必须是唯一的,尤其是在像我所做的那样创建相同组件的地图时。否则,滑动一个轮播将滑动所有轮播。