React打字测试应用getBoundingClientRect()跨设备top值差异问题
React打字测试应用移动端光标定位偏移问题
我开发React打字测试应用时,通过getBoundingClientRect()获取当前字符的绝对位置来定位光标,桌面端显示正常,但移动端返回的字符top值略小。日志显示移动端top值为212.72727966308594,桌面端为213.09091186523438。
相关容器组件代码:
<div onClick={handleContainerClick} ref={containerRef} className={`border leading-[3em] max-h-[10rem] h-[10rem] overflow-hidden ${showTextTransition ? 'opacity-0' : 'opacity-1'} transition duration-100`}> {!showTextTransition && ( <> {sampleText.split('').map((char, index) => { return <Letter key={index} windowWidth={windowWidth} scrollableDivScrollTop={scrollableDivScrollTop} char={char} index={index} setCurrentCharacterPos={setCurrentCharacterPos} currentIndex={inputText.length} isIncorrect={charactersStatus.length > 0 ? charactersStatus[index] !== null && !charactersStatus[index] : false} /> })} {/* simulating an extra line */} <span className="text-4xl whitespace-pre"> </span> { (inputText.length < sampleText.length && (currentCharacterPos.left >= 0 && currentCharacterPos.top+4 >= containerRef.current.getBoundingClientRect().top)) && <div className={`bg-light-active dark:bg-dark-active inline-block h-12 w-[2px] absolute transition-all duration-100 ${!startTime ? 'animate-fade' : ''}`} style={{ left: `${currentCharacterPos.left}px`, top: `${currentCharacterPos.top + 3}px` }}></div> } </> )} </div>
Letter组件代码:
import { useEffect, useRef, useState } from "react" export default function Letter({ char, index, currentIndex, isIncorrect, setCurrentCharacterPos, scrollableDivScrollTop, windowWidth }) { const spanRef = useRef(null); useEffect(()=>{ if(index===currentIndex && spanRef.current){ const rect = spanRef.current.getBoundingClientRect(); setCurrentCharacterPos({top: rect.top, left: rect.left}); } }, [currentIndex, index, spanRef, scrollableDivScrollTop, windowWidth]); return ( <span className="text-4xl" ref={spanRef}> <span className={`transition duration-100 ${isIncorrect ? 'text-light-danger dark:text-dark-danger' : (index < currentIndex ? 'text-light-hover dark:text-dark-hover' : 'text-light-icon dark:text-dark-icon')}`}>{char}</span> </span> ) }
解决思路与方案
1. 统一字体渲染
移动端和桌面端字体引擎差异会导致字符位置偏移,给容器和Letter组件统一指定字体:
/* 在全局样式或组件样式中添加 */ .font-uniform { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; }
给容器和Letter的span加上这个类,减少跨平台渲染差异。
2. 针对移动端做位置补偿
在获取字符位置时,判断设备类型,给移动端的top值添加补偿:
useEffect(()=>{ if(index===currentIndex && spanRef.current){ requestAnimationFrame(() => { const rect = spanRef.current.getBoundingClientRect(); // 针对移动端屏幕宽度做补偿,数值根据实际偏差调整 const isMobile = window.innerWidth < 768; const adjustedTop = isMobile ? rect.top + 0.36 : rect.top; setCurrentCharacterPos({top: adjustedTop, left: rect.left}); }); } }, [currentIndex, index, spanRef, scrollableDivScrollTop, windowWidth]);
加入requestAnimationFrame确保DOM完全渲染后再获取位置,避免渲染未完成导致的坐标偏差。
3. 替换相对行高为固定值
容器的leading-[3em]是相对单位,在不同设备上计算可能有差异,换成固定像素行高:
// 将容器的leading-[3em]替换为leading-[48px](根据text-4xl的实际字号调整) <div onClick={handleContainerClick} ref={containerRef} className={`border leading-[48px] max-h-[10rem] h-[10rem] overflow-hidden ${showTextTransition ? 'opacity-0' : 'opacity-1'} transition duration-100`}>
4. 使用相对位置计算替代视口坐标
改用offsetTop结合容器的滚动位置计算,避免视口坐标的跨平台差异:
useEffect(()=>{ if(index===currentIndex && spanRef.current){ const containerRect = containerRef.current.getBoundingClientRect(); const charOffsetTop = spanRef.current.offsetTop; // 计算相对于视口的top值 const adjustedTop = containerRect.top + charOffsetTop; setCurrentCharacterPos({top: adjustedTop, left: rect.left}); } }, [currentIndex, index, spanRef, scrollableDivScrollTop, windowWidth]);
内容的提问来源于stack exchange,提问作者Aman
相关产品推荐
相关产品推荐

