React复刻MDN触摸手势日志器遇两问题求助
React版MDN触摸事件日志器问题修复
问题1:日志输出无内容
原因
React会自动转义HTML字符串,直接渲染包含<br>的logText会将标签当作纯文本显示,而非解析为换行。此外,enableLog函数中的逻辑错误,错误地将logText赋值给testText,导致目标元素显示异常。
修复方案
- 使用
dangerouslySetInnerHTML渲染日志内容,让React解析HTML标签:// 替换原日志显示的div <div dangerouslySetInnerHTML={{ __html: logText }}></div> - 修正
enableLog函数逻辑,仅切换日志记录状态:const enableLog = () => { setLogEvents(prev => !prev); };
问题2:双指捏合缩放功能未触发
原因
- 触摸点缓存逻辑错误:在
touchstart时,检测到双指触摸后未清空原有缓存,而是追加新触摸点,导致缓存中存在无效数据,无法匹配后续触摸事件的identifier。 - 捏合判断逻辑错误:原MDN通过计算两个触摸点之间的距离变化判断捏合动作,当前代码错误地计算单个触摸点的X轴位移,且
else分支位置错误,导致缓存清空逻辑触发时机不正确。 - State异步更新问题:直接依赖当前
tpCache状态更新可能获取不到最新值,需使用函数式更新保证状态的正确性。
修复方案
- 修正
startHandler中的触摸点缓存逻辑,清空缓存后存储双指触摸点的坐标(而非整个事件对象):const startHandler = (ev) => { ev.preventDefault(); if (ev.targetTouches.length === 2) { setTpCache(() => { const newCache = []; for (let i = 0; i < ev.targetTouches.length; i++) { newCache.push({ identifier: ev.targetTouches[i].identifier, clientX: ev.targetTouches[i].clientX, clientY: ev.targetTouches[i].clientY }); } return newCache; }); } if (logEvents) { log("touchStart", ev, true); } updateBackground(ev); }; - 修正
handlePinchZoom函数,改为计算双指距离变化判断捏合:const handlePinchZoom = (ev) => { if (ev.targetTouches.length === 2 && ev.changedTouches.length === 2) { let point1 = -1, point2 = -1; for (let i = 0; i < tpCache.length; i++) { if (tpCache[i].identifier === ev.targetTouches[0].identifier) point1 = i; if (tpCache[i].identifier === ev.targetTouches[1].identifier) point2 = i; } if (point1 >= 0 && point2 >= 0) { // 计算初始双指距离 const initialDistance = Math.sqrt( Math.pow(tpCache[point1].clientX - tpCache[point2].clientX, 2) + Math.pow(tpCache[point1].clientY - tpCache[point2].clientY, 2) ); // 计算当前双指距离 const currentDistance = Math.sqrt( Math.pow(ev.targetTouches[0].clientX - ev.targetTouches[1].clientX, 2) + Math.pow(ev.targetTouches[0].clientY - ev.targetTouches[1].clientY, 2) ); const PINCH_THRESHHOLD = ev.target.clientWidth / 10; // 判断距离变化是否超过阈值 if (Math.abs(currentDistance - initialDistance) >= PINCH_THRESHHOLD) { ev.target.style.background = "green"; } } else { setTpCache([]); } } }; - 调整
moveHandler中的逻辑,确保双指触摸时不覆盖背景色:const moveHandler = (ev) => { ev.preventDefault(); if (logEvents) { log("touchMove", ev, false); } // 仅当触摸点数量不是2时更新背景色 if (ev.targetTouches.length !== 2) { updateBackground(ev); } ev.target.style.outline = "dashed"; handlePinchZoom(ev); };
修复后的完整代码
import React, { useState, useEffect } from 'react'; const TouchEventsContainer = () => { const [logEvents, setLogEvents] = useState(false); const [tpCache, setTpCache] = useState([]); const [logText, setLogText] = useState(""); const [testText, setTestText] = useState("Tap Swipe"); const enableLog = () => { setLogEvents(prev => !prev); }; const clearLog = () => setLogText(''); const log = (name, ev, printTargetIds) => { let s = `${name}: touches = ${ev.touches.length} ; targetTouches = ${ev.targetTouches.length} ; changedTouches = ${ev.changedTouches.length}`; if (printTargetIds) { for (var i = 0; i < ev.targetTouches.length; i++) { s += `... id = ${ev.targetTouches[i].identifier} <br>`; } } setLogText((prevLogText) => prevLogText + s + "<br>"); }; const updateBackground = (ev) => { switch (ev.targetTouches.length) { case 1: ev.target.style.background = "yellow"; break; case 2: ev.target.style.background = "pink"; break; default: ev.target.style.background = "lightblue"; } } const handlePinchZoom = (ev) => { if (ev.targetTouches.length === 2 && ev.changedTouches.length === 2) { let point1 = -1, point2 = -1; for (let i = 0; i < tpCache.length; i++) { if (tpCache[i].identifier === ev.targetTouches[0].identifier) point1 = i; if (tpCache[i].identifier === ev.targetTouches[1].identifier) point2 = i; } if (point1 >= 0 && point2 >= 0) { const initialDistance = Math.sqrt( Math.pow(tpCache[point1].clientX - tpCache[point2].clientX, 2) + Math.pow(tpCache[point1].clientY - tpCache[point2].clientY, 2) ); const currentDistance = Math.sqrt( Math.pow(ev.targetTouches[0].clientX - ev.targetTouches[1].clientX, 2) + Math.pow(ev.targetTouches[0].clientY - ev.targetTouches[1].clientY, 2) ); const PINCH_THRESHHOLD = ev.target.clientWidth / 10; if (Math.abs(currentDistance - initialDistance) >= PINCH_THRESHHOLD) { ev.target.style.background = "green"; } } else { setTpCache([]); } } } const startHandler = (ev) => { ev.preventDefault(); if (ev.targetTouches.length === 2) { setTpCache(() => { const newCache = []; for (let i = 0; i < ev.targetTouches.length; i++) { newCache.push({ identifier: ev.targetTouches[i].identifier, clientX: ev.targetTouches[i].clientX, clientY: ev.targetTouches[i].clientY }); } return newCache; }); } if (logEvents) { log("touchStart", ev, true); } updateBackground(ev); }; const moveHandler = (ev) => { ev.preventDefault(); if (logEvents) { log("touchMove", ev, false); } if (ev.targetTouches.length !== 2) { updateBackground(ev); } ev.target.style.outline = "dashed"; handlePinchZoom(ev); }; const endHandler = (ev) => { ev.preventDefault(); if (logEvents) { log(ev.type, ev, false); } if (ev.targetTouches.length === 0) { ev.target.style.background = "white"; ev.target.style.outline = "1px solid black"; } }; const setHandlers = (name) => { const el = document.getElementById(name); el.ontouchstart = startHandler; el.ontouchmove = moveHandler; el.ontouchcancel = endHandler; el.ontouchend = endHandler; }; useEffect(() => { setHandlers("target1"); setHandlers("target2"); setHandlers("target3"); setHandlers("target4"); }, []); return ( <> <h1>Multi-touch interaction</h1> <div id="target1">{testText}</div> <div id="target2"> Tap, Hold or Swipe me 2</div> <div id="target3"> Tap, Hold or Swipe me 3</div> <div id="target4"> Tap, Hold or Swipe me 4</div> <button id="log" onClick={enableLog}>Start/Stop event logging</button> <button id="clearlog" onClick={clearLog}>Clear the log</button> <p></p> <div dangerouslySetInnerHTML={{ __html: logText }}></div> </> ); }; export default TouchEventsContainer;
内容的提问来源于stack exchange,提问作者DDD
相关产品推荐
相关产品推荐

