AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79589318
Accepted
magrega
magrega
Asked: 2025-04-24 02:54:25 +0800 CST2025-04-24 02:54:25 +0800 CST 2025-04-24 02:54:25 +0800 CST

Bloquear rolagem vertical de um pai ao deslizar horizontalmente para um filho

  • 772

Estou escrevendo um gancho que me permitirá detectar ações de deslizar em um elemento. O problema é que recebo um arrastar vertical irritante ao tentar deslizar horizontalmente. Consegui atingir o comportamento desejado bloqueando a rolagem vertical do elemento pai após detectar que o deslizar horizontal se moveu 5px para a esquerda ou direita.

Mas recebo isso no console ao tentar rolar o elemento: [Intervention] Ignored attempt to cancel a touchmove event with cancelable=false, for example because scrolling is in progress and cannot be interrupted. Tentei verificar a veracidade, e.cancelablemas o gancho parou de funcionar. Como posso corrigir isso?

Aqui está o gancho:

import { useRef } from "react";

export const useSwipe = () => {
  const touchStartX = useRef<number | null>(null);
  const touchEndX = useRef<number | null>(null);
  const touchStartY = useRef<number | null>(null);
  const touchEndY = useRef<number | null>(null);

  const minSwipeDistance = 100;

  const box = document.getElementById("scrollable-cards-box");

  const cancelTouch = (e: TouchEvent) => e.preventDefault();

  const onTouchStart = (e: React.TouchEvent) => {
    touchEndX.current = null;
    touchEndY.current = null;
    touchStartX.current = e.targetTouches[0].clientX;
    touchStartY.current = e.targetTouches[0].clientY;
  };

  const onTouchMove = (e: React.TouchEvent) => {
    touchEndX.current = e.targetTouches[0].clientX;
    touchEndY.current = e.targetTouches[0].clientY;

    if (!touchStartY.current || !touchEndY.current) return;
    if (!touchStartX.current || !touchEndX.current) return;
//something here causes the error
    if (
      touchEndX.current > touchStartX.current + 5 || 
      touchEndX.current < touchStartX.current - 5
    ) {
      if (box) {
        box.addEventListener("touchmove", cancelTouch, {
          passive: false,
        });
        box.style.touchAction = "none";
        console.log("set touch to none");
      }
    }
  };

  const onTouchEnd = () => {
    if (!touchStartX.current || !touchEndX.current) return;
    const distanceX = touchStartX.current - touchEndX.current;
    const isLeftSwipe = distanceX > minSwipeDistance;
    const isRightSwipe = distanceX < -minSwipeDistance;
    if (box) {
      box.style.touchAction = "auto";
      console.log("set touch to autoo");
      box.removeEventListener("touchmove", cancelTouch);
    }
    if (isRightSwipe) return "right";
    if (isLeftSwipe) return "left";
  };

  return { onTouchStart, onTouchMove, onTouchEnd };
};

Eu uso funções retornadas nas propriedades do elemento filho com o mesmo nome e adiciono um ouvinte de eventos ao pai.

const {
  useRef
} = React;


const useSwipe = () => {
  const touchStartX = useRef(null);
  const touchEndX = useRef(null);
  const touchStartY = useRef(null);
  const touchEndY = useRef(null);

  const minSwipeDistance = 100;
  const lockAfterX = 10;

  const box = document.getElementById("scrollable-cards-box")
  
  const cancelTouch = (e: TouchEvent) => e.preventDefault();

  const onTouchStart = (e: React.TouchEvent) => {
    touchEndX.current = null;
    touchEndY.current = null;
    touchStartX.current = e.targetTouches[0].clientX;
    touchStartY.current = e.targetTouches[0].clientY;
  };
//something happens here
  const onTouchMove = (e: React.TouchEvent) => {
    touchEndX.current = e.targetTouches[0].clientX;
    touchEndY.current = e.targetTouches[0].clientY;

    if (!touchStartY.current || !touchEndY.current) return;
    if (!touchStartX.current || !touchEndX.current) return;

    if (
      touchEndX.current > touchStartX.current + lockAfterX ||
      touchEndX.current < touchStartX.current - lockAfterX
    ) {
      if (box) {
        box.style.touchAction = "none";
        box.addEventListener("touchmove", cancelTouch, {
          passive: false,
        });
        console.log("set touch to none");
      }
    }
  };

  const onTouchEnd = () => {
    if (!touchStartX.current || !touchEndX.current) return;
    const distanceX = touchStartX.current - touchEndX.current;
    const isLeftSwipe = distanceX > minSwipeDistance;
    const isRightSwipe = distanceX < -minSwipeDistance;
    if (box) {
      box.style.touchAction = "auto";
      console.log("set touch to autoo");
      box.removeEventListener("touchmove", cancelTouch);
    }
    if (isRightSwipe) return "right";
    if (isLeftSwipe) return "left";
  };

  return { onTouchStart, onTouchMove, onTouchEnd };
};

const List = () => {
  const { onTouchStart, onTouchMove, onTouchEnd } = useSwipe();
  const handleTouchEnd = () => {
    const swipeDirection = onTouchEnd();
    if (swipeDirection === "left") console.log("left swipe");
    if (swipeDirection === "right") console.log("right swipe");
  };

  const uls = Array(30).fill("");
  return (
     uls.map((ul, index) => (
        <ul
          className="item"
          key={index}
          onTouchStart={onTouchStart}
          onTouchMove={onTouchMove}
          onTouchEnd={handleTouchEnd}
        >
          {index + ul}
        </ul>
      ))
  );
};


ReactDOM.createRoot(
  document.getElementById("scrollable-cards-box")
).render( <
  List / >
);
.container {
  background-color: gray;
}

.item {
  background-color: skyblue;
  padding: 15px 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<div id="root">
 <div className="container" id="scrollable-cards-box">
</div>
</div>

javascript
  • 2 2 respostas
  • 62 Views

2 respostas

  • Voted
  1. David
    2025-04-24T20:11:01+08:002025-04-24T20:11:01+08:00

    Resumo

    • Não adicione touchmove dinamicamente com { passive: false }— isso é tarde demais.
    • Use e.preventDefault()no início do ciclo de vida do evento durante touchmove.
    • Use Math.abs(deltaX) > Math.abs(deltaY)para detectar direção.

    A especificação para addEventListener() define o valor padrão para a opção passiva como sempre falso. No entanto, para obter os benefícios de desempenho de rolagem dos ouvintes passivos em código legado, os navegadores modernos alteraram o valor padrão da opção passiva para verdadeiro para os eventos wheel, mousewheel, touchstart e touchmove nos nós de nível de documento Window, Document e Document.body. Isso impede que o ouvinte de eventos cancele o evento, impedindo que ele bloqueie a renderização da página enquanto o usuário estiver rolando a página.

    Do MDN em addEventListener

    Você está tentando cancelar um touchmoveevento depois que ele já começou, e o navegador não permite isso se o evento estiver marcado como passivo. Isso acontece porque os navegadores modernos tratam touchmoveos ouvintes como passivos por padrão, o que significa que preventDefault()não tem efeito a menos que você os torne explicitamente não passivos durante a vinculação inicial do evento. Portanto, tentar adicioná-lo dinamicamente após o início do deslizamento é tarde demais.

    Se você estiver usando ouvintes de eventos passivos em outro lugar, certifique-se de que eles não estejam anulando seu esforço. Por exemplo, se o contêiner pai tiver um touchmoveouvinte marcado como passivo, isso poderá interferir.

    Tente algo assim:

    export const useSwipe = () => {
      const touchStartX = useRef<number | null>(null);
      const touchStartY = useRef<number | null>(null);
      const isHorizontalSwipe = useRef(false);
    
      const minSwipeDistance = 100;
      const lockAfterX = 10;
    
      const onTouchStart = (e: React.TouchEvent) => {
        touchStartX.current = e.targetTouches[0].clientX;
        touchStartY.current = e.targetTouches[0].clientY;
        isHorizontalSwipe.current = false; // reset flag
      };
    
      const onTouchMove = (e: React.TouchEvent) => {
        if (
          touchStartX.current === null ||
          touchStartY.current === null
        ) return;
    
        const currentX = e.targetTouches[0].clientX;
        const currentY = e.targetTouches[0].clientY;
    
        const deltaX = currentX - touchStartX.current;
        const deltaY = currentY - touchStartY.current;
    
        // Determine if it's a horizontal gesture
        if (Math.abs(deltaX) > lockAfterX && Math.abs(deltaX) > Math.abs(deltaY)) {
          isHorizontalSwipe.current = true;
        }
    
        // Lock vertical scroll
        if (isHorizontalSwipe.current && e.cancelable) {
          e.preventDefault(); // prevent vertical scroll!
        }
      };
    
      const onTouchEnd = () => {
        if (!touchStartX.current || isHorizontalSwipe.current === null) return;
        const distanceX = touchStartX.current - (touchStartX.current + (isHorizontalSwipe.current ? 100 : 0));
        const isLeftSwipe = distanceX > minSwipeDistance;
        const isRightSwipe = distanceX < -minSwipeDistance;
    
        if (isRightSwipe) return "right";
        if (isLeftSwipe) return "left";
      };
    
      return { onTouchStart, onTouchMove, onTouchEnd };
    };
    
    • 0
  2. Best Answer
    magrega
    2025-04-26T01:54:46+08:002025-04-26T01:54:46+08:00

    Acho que consegui fazer funcionar alterando cancelToucha função:

    const cancelTouch = (e: TouchEvent) => e.cancelable && e.preventDefault();
    
    • 0

relate perguntas

  • classificação de mesclagem não está funcionando - código Javascript: não é possível encontrar o erro mesmo após a depuração

  • método select.remove() funciona estranho [fechado]

  • Sempre um 401 res em useOpenWeather () - react-open-weather lib [duplicado]

  • O elemento de entrada não possui atributo somente leitura, mas os campos ainda não podem ser editados [fechado]

  • Como editar o raio do primeiro nó de um RadialTree D3.js?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve