ユーザーアイコン

mizuko

約1か月前

0
0

framer-motionを利用したスワイプに基づいてスクロールするタブも追いかけるようにする

Next.js
framer-motion

これで横スワイプを実装したが、react-indiana-drag-scrollで実装したスクロールできるタブが、一緒に動いてくれない問題を修正した。

タブ変更ロジック内に以下を仕込み、react-indiana-drag-scrollで実装したタブ要素にrefを渡す

// タブの状態が更新された後にスクロールを実行 requestAnimationFrame(() => { tabsRef.current?.scrollToActiveTab(); });

framer-motionで実装したSwipe Containerに以下の様なpassiveの設定をeventListenerを使って設定する

useEffect(() => { const container = containerRef.current; if (!container) return; const touchStartHandler = (e: TouchEvent): void => { setTouchEnd(null); setTouchStart(e.targetTouches[0].clientX); touchStartYRef.current = e.targetTouches[0].clientY; setIsScrolling(false); }; const touchMoveHandler = (e: TouchEvent): void => { if (!touchStart) return; const currentX = e.targetTouches[0].clientX; const currentY = e.targetTouches[0].clientY; const diffX = Math.abs(touchStart - currentX); const diffY = Math.abs((touchStartYRef.current ?? 0) - currentY); // 横方向の移動が縦方向より大きい場合、スクロールフラグを立てない if (!isScrolling && diffY < diffX) { e.preventDefault(); } // 縦方向のスクロールを検出 if (!isScrolling && diffY > SCROLL_TOLERANCE && diffY > diffX) { setIsScrolling(true); return; } // コードブロック内での横スクロールを許可 if ( e.target instanceof HTMLElement && (e.target.closest('.mockup-code') ?? e.target.closest('pre')) ) { return; } setTouchEnd(currentX); }; const touchEndHandler = (): void => { if (!touchStart || !touchEnd || isScrolling) { setTouchStart(null); setTouchEnd(null); return; } const distance = touchStart - touchEnd; const isLeftSwipe = distance > SWIPE_MIN_DISTANCE; const isRightSwipe = distance < -SWIPE_MIN_DISTANCE; if (isLeftSwipe && currentIndex < totalItems - 1) { onSwipe(currentIndex + 1); } else if (isRightSwipe && currentIndex > 0) { onSwipe(currentIndex - 1); } setTouchStart(null); setTouchEnd(null); }; // passive: falseを指定する理由 // 1. デフォルトではpassive: trueとなっており、preventDefault()が無視される // 2. スワイプ時の画面スクロールを防ぐためにpreventDefault()が必要 // 3. そのため、addEventListener時にpassive: falseの指定が必須 container.addEventListener('touchstart', touchStartHandler, { passive: true, }); container.addEventListener('touchmove', touchMoveHandler, { passive: false, }); container.addEventListener('touchend', touchEndHandler, { passive: true }); return () => { container.removeEventListener('touchstart', touchStartHandler); container.removeEventListener('touchmove', touchMoveHandler); container.removeEventListener('touchend', touchEndHandler); }; }, [currentIndex, isScrolling, onSwipe, touchStart, touchEnd, totalItems]);