Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions content_scripts/link_hints.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ class LinkHintsMode {
this.hintMode = null;
// A count of the number of Tab presses since the last non-Tab keyboard event.
this.tabCount = 0;
// If set, we will fade this element out for a short time before removing all markers.
this.pendingActivatedFadeEl = null;
this.pendingFadeRemovalScheduled = false;
this.fadeDurationMs = 300;

if (hintDescriptors.length === 0) {
HUD.show("No links to select.", 2000);
Expand Down Expand Up @@ -698,7 +702,45 @@ class LinkHintsMode {
if (userMightOverType == null) {
userMightOverType = false;
}
this.removeHintMarkers();
// Delay removing all markers so the activated hint can fade out.
// We'll remove the container after the fade completes.
const activatedMarkerEl = linkMatched.isLocalMarker() ? linkMatched.element : null;
if (activatedMarkerEl) {
this.pendingActivatedFadeEl = activatedMarkerEl;
// Hide all other markers immediately so only the activated marker remains visible to fade.
if (this.containerEl) {
for (const child of Array.from(this.containerEl.children)) {
if (child !== activatedMarkerEl) child.style.display = "none";
}
} else {
for (const m of this.hintMarkers) {
if (m.isLocalMarker() && m.element !== activatedMarkerEl) m.element.style.display = "none";
}
}
// Keep all hints visible for now; we'll start a fade on the activated one and then remove all.
// Prepare starting state for smooth transform; then animate to 150% and opacity 0 inline.
activatedMarkerEl.style.willChange = "opacity, transform";
activatedMarkerEl.style.transition = `opacity ${this.fadeDurationMs}ms ease, transform ${this.fadeDurationMs}ms ease`;
activatedMarkerEl.style.transformOrigin = "center center";
activatedMarkerEl.style.transform = "scale(1)";
activatedMarkerEl.style.opacity = "1";
// Force a reflow so the browser registers the initial styles before we change them.
void activatedMarkerEl.getBoundingClientRect();
// Next tick, apply the end state to trigger the transition.
Utils.nextTick(() => {
activatedMarkerEl.style.transform = "scale(1.5)";
activatedMarkerEl.style.opacity = "0";
});
if (!this.pendingFadeRemovalScheduled) {
this.pendingFadeRemovalScheduled = true;
Utils.setTimeout(this.fadeDurationMs, () => {
this.removeHintMarkers();
this.pendingFadeRemovalScheduled = false;
});
}
} else {
this.removeHintMarkers();
}

if (linkMatched.isLocalMarker()) {
const localHint = linkMatched.localHint;
Expand Down Expand Up @@ -784,15 +826,20 @@ class LinkHintsMode {
}

deactivateMode() {
this.removeHintMarkers();
// Exit the mode immediately, but if we're in the middle of a fade-out, defer DOM removal
// until the scheduled timeout in activateLink() fires.
if (this.hintMode != null) this.hintMode.exit();
if (this.pendingFadeRemovalScheduled) return;
this.removeHintMarkers();
}

removeHintMarkers() {
if (this.containerEl) {
DomUtils.removeElement(this.containerEl);
}
this.containerEl = null;
this.pendingActivatedFadeEl = null;
this.pendingFadeRemovalScheduled = false;
}
}

Expand Down
7 changes: 7 additions & 0 deletions content_scripts/vimium.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ div.internal-vimium-hint-marker {
z-index: 2147483647;
}

/* Applied briefly when a hint is activated to fade it out before removal. */
div.internal-vimium-hint-marker.vimium-hint-fade-out {
opacity: 0;
transform: scale(1.5);
transform-origin: center center;
}

div.internal-vimium-hint-marker span {
color: #302505;
font-family: Helvetica, Arial, sans-serif;
Expand Down