实时获取光标位置优化思路

真正能在滚动时拿到光标“位置更新”同时又最轻量的做法,其实是不每次都去测 Selection → getClientRects() 或插入 marker,而是直接把上一次的坐标做“增量”调整。


核心思路:基于滚动偏移的增量更新

  1. 缓存上一次的绝对坐标

    let lastPos = { x: /* 上次 computed x */, y: /* 上次 computed y */ };
    let lastScroll = new Map();  // key: scrollable element, value: 上次 scrollTop/scrollLeft
    
  2. 初始化时,给每个可滚动容器记录初始 scrollTop/scrollLeft

    scrollableEls.forEach(el => {
      lastScroll.set(el, { top: el.scrollTop, left: el.scrollLeft });
    });
    
  3. 滚动事件里,只对 delta 做平移

    function onScroll(e) {
      const el = e.target;
      const prev = lastScroll.get(el);
      const deltaY = el.scrollTop - prev.top;
      const deltaX = el.scrollLeft - prev.left;
      // 更新缓存
      lastScroll.set(el, { top: el.scrollTop, left: el.scrollLeft });
      // 在 transform 里直接做平移
      lastPos.x -= deltaX;
      lastPos.y -= deltaY;
      customCursor.style.transform = `translate(${lastPos.x}px, ${lastPos.y}px)`;
      // 然后立刻用 lastPos 去检测是否超出“指定元素”边界
      checkOutOfBounds(lastPos);
    }
    
  4. 其他需要精确定位的时刻(点击、输入、selectionchange)

    function recalcCursor() {
      const pos = getStablePosition();  // 你的完整测量流程
      if (pos) {
        lastPos = pos;
        customCursor.style.transform = `translate(${pos.x}px, ${pos.y}px)`;
        checkOutOfBounds(pos);
      }
    }
    document.addEventListener('click', recalcCursor);
    document.addEventListener('input', recalcCursor);
    document.addEventListener('selectionchange', recalcCursor);
    

这样:

  • 滚动时:完全不打任何测量流程,只做简单的坐标减法,O(1) 的操作
  • 点击/移动/输入时:才跑一次完整的 getStablePosition()​(只有用户真正改光标位置的场景才触发),保持高度和精度
  • 出界检测:始终用当前的 lastPos​(滚动时同步平移后、或重新计算后)去判断是否超出指定容器的 getBoundingClientRect()​ 边界即可

为什么这样最优?

  • 滚动最频繁,用 O(1) 的平移替代 O(n) 的测量 + DOM 读写
  • 既保证了即使光标在一个嵌套滚动容器内,也能第一时间同步移动坐标
  • 你依然可以在任意时刻根据 lastPos​ 去判断“是否超出指定元素区域”,而不用隐藏再测
  • 当用户真正把光标移到新位置(点击/输入/选择)时,再去跑完整精准的测量,保证不会累积误差

边界情况

  • 如果页面同时存在多个滚动容器,你需要给它们都注册 scroll​ 监听,并维护各自的 lastScroll​,delta 作用到同一套 lastPos
  • 如果滚动容器突然被重置(如内容刷新、重排),你可以在那次操作后 一并 调用 recalcCursor()​,以重新同步一次绝对坐标

这样,你就能在保证“任何时候都能检测出是否超出区域”的同时,把滚动时的性能开销降到最低:滚动里只做几行纯 JS 运算,无需任何布局读写。

image.png

留下你的脚步
推荐阅读