第一次滑动后,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/3,具体取决于我删除了多少。这不包括第一个轮播,它仍然可以正常工作
- 我试过删除一堆 CSS,没用
- 尝试将事件侦听器添加到 'main' div,也许我做错了什么,但滑动一个轮播最终会滑动所有轮播
- 我认为它与我在自定义轮播组件中创建的触摸事件处理函数有关,但它们似乎工作正常,因为在将它们更改为 console.logs 并手动翻译幻灯片后,触摸事件仍未触发。
更新 #1
我将触摸事件处理程序放在包装 {child} 的新 div 中。第二张幻灯片现在可以滑动,但第三张幻灯片仍然是半身像。
参考文献
这是教程 link 我按照此教程创建了轮播以获取自定义轮播组件的更多上下文。
几天几小时后,解决方案有点奇怪。我猜您需要将目标正确设置为元素的 div id。我把它放在 useEffect 中,所以结果是这样的:
useEffect(() => {
document.getElementById(id).addEventListener('touchstart', () => false, { passive: false })
return () => {
clearTimeout(transitionTimeout)
}
}, [])
请注意,id 必须是唯一的,尤其是在像我所做的那样创建相同组件的地图时。否则,滑动一个轮播将滑动所有轮播。
我在 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/3,具体取决于我删除了多少。这不包括第一个轮播,它仍然可以正常工作
- 我试过删除一堆 CSS,没用
- 尝试将事件侦听器添加到 'main' div,也许我做错了什么,但滑动一个轮播最终会滑动所有轮播
- 我认为它与我在自定义轮播组件中创建的触摸事件处理函数有关,但它们似乎工作正常,因为在将它们更改为 console.logs 并手动翻译幻灯片后,触摸事件仍未触发。
更新 #1
我将触摸事件处理程序放在包装 {child} 的新 div 中。第二张幻灯片现在可以滑动,但第三张幻灯片仍然是半身像。
参考文献
这是教程 link 我按照此教程创建了轮播以获取自定义轮播组件的更多上下文。
几天几小时后,解决方案有点奇怪。我猜您需要将目标正确设置为元素的 div id。我把它放在 useEffect 中,所以结果是这样的:
useEffect(() => {
document.getElementById(id).addEventListener('touchstart', () => false, { passive: false })
return () => {
clearTimeout(transitionTimeout)
}
}, [])
请注意,id 必须是唯一的,尤其是在像我所做的那样创建相同组件的地图时。否则,滑动一个轮播将滑动所有轮播。