Fix tutorial tooltip flash at 0,0 and slow step transitions

- Hide tooltip/ring with visibility:hidden until first step is positioned
- Hide tooltip between steps when scrolling to prevent stale position flash
- Replace scrollend event (poor browser support, 500ms fallback) with
  requestAnimationFrame polling that resolves in ~50ms when scroll settles

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 12:48:13 +03:00
parent d8e73cb2b5
commit 09b4a1b182

View File

@@ -104,6 +104,14 @@ export function startTutorial(config) {
onClose: config.onClose || null
});
// Hide tooltip and ring until first step positions them (prevents flash at 0,0)
const tooltip = overlay.querySelector('.tutorial-tooltip');
const ring = overlay.querySelector('.tutorial-ring');
const backdrop = overlay.querySelector('.tutorial-backdrop');
if (tooltip) tooltip.style.visibility = 'hidden';
if (ring) ring.style.visibility = 'hidden';
if (backdrop) backdrop.style.clipPath = 'none';
overlay.classList.add('active');
document.addEventListener('keydown', handleTutorialKey);
showTutorialStep(0);
@@ -276,19 +284,25 @@ function _positionSpotlight(target, overlay, step, index, isFixed) {
if (tooltip) {
positionTutorialTooltip(tooltip, x, y, w, h, step.position, isFixed);
tooltip.style.visibility = '';
}
if (ring) ring.style.visibility = '';
}
/** Wait for smooth scroll to finish, then resolve. */
function _waitForScrollEnd(scrollTarget) {
/** Wait for scroll to settle (position stops changing). */
function _waitForScrollEnd() {
return new Promise(resolve => {
let timer = setTimeout(resolve, 500); // fallback
const onEnd = () => {
clearTimeout(timer);
scrollTarget.removeEventListener('scrollend', onEnd);
resolve();
};
scrollTarget.addEventListener('scrollend', onEnd, { once: true });
let lastY = window.scrollY;
let stableFrames = 0;
const fallback = setTimeout(resolve, 300);
function check() {
const y = window.scrollY;
if (y === lastY) stableFrames++;
else { stableFrames = 0; lastY = y; }
if (stableFrames >= 3) { clearTimeout(fallback); resolve(); }
else requestAnimationFrame(check);
}
requestAnimationFrame(check);
});
}
@@ -321,8 +335,11 @@ function showTutorialStep(index, direction = 1) {
const needsScroll = preRect.bottom > window.innerHeight || preRect.top < headerH;
if (needsScroll) {
// Hide tooltip while scrolling to prevent stale position flash
const tt = overlay.querySelector('.tutorial-tooltip');
if (tt) tt.style.visibility = 'hidden';
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
_waitForScrollEnd(isFixed ? document.scrollingElement || document.documentElement : activeTutorial.container).then(() => {
_waitForScrollEnd().then(() => {
if (activeTutorial && activeTutorial.step === index) {
_positionSpotlight(target, overlay, step, index, isFixed);
}