Greasy Fork is available in English.
Show tooltip with emoji name and group when selecting an emoji. Supports caching and immediate replacement.
当前为
// ==UserScript==
// @name Emoji Tooltip
// @namespace http://tampermonkey.net/
// @version 1.5
// @description Show tooltip with emoji name and group when selecting an emoji. Supports caching and immediate replacement.
// @author Emoji Tooltip Script
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @connect raw.githubusercontent.com
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const EMOJI_DATA_URL = 'https://raw.githubusercontent.com/muan/unicode-emoji-json/main/data-by-emoji.json';
const CACHE_KEY = 'emoji_tooltip_data';
const CACHE_VERSION = '1.5'; // Bump when data structure changes
let emojiMap = new Map();
let tooltipElement;
let hideTimer;
let autoHideTimer;
let isTooltipVisible = false;
const AUTO_HIDE_DELAY = 10000; // 10 seconds
const MOUSE_MOVE_THRESHOLD = 300;
let lastMousePosition = { x: 0, y: 0 };
// Initialize tooltip DOM
function initTooltip() {
tooltipElement = document.createElement('div');
tooltipElement.style.cssText = `
position: fixed;
background: #333;
color: white;
padding: 8px 12px;
border-radius: 6px;
box-shadow: 0 2px 15px rgba(0,0,0,0.3);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 1.4;
z-index: 114514;
max-width: 300px;
opacity: 0;
transition: opacity 0.2s, transform 0.2s;
display: none;
transform: translateX(10px) translateY(5px);
`;
document.body.appendChild(tooltipElement);
}
// Show tooltip at (x, y)
function showTooltip(content, x, y) {
clearTimeout(hideTimer);
clearTimeout(autoHideTimer);
tooltipElement.innerHTML = content;
// Force reflow to get accurate dimensions
tooltipElement.style.display = 'block';
tooltipElement.style.opacity = '0';
tooltipElement.style.transform = 'translateX(10px) translateY(5px)';
void tooltipElement.offsetWidth; // Trigger reflow
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const tooltipWidth = Math.min(tooltipElement.scrollWidth, 300) || 200;
const tooltipHeight = tooltipElement.scrollHeight || 80;
let left = x + 15;
let top = y + 15;
if (left + tooltipWidth > viewportWidth - 10) {
left = x - tooltipWidth - 15;
}
if (top + tooltipHeight > viewportHeight - 10) {
top = y - tooltipHeight - 15;
}
tooltipElement.style.left = `${left}px`;
tooltipElement.style.top = `${top}px`;
// Fade in
requestAnimationFrame(() => {
tooltipElement.style.opacity = '1';
tooltipElement.style.transform = 'translateX(0) translateY(0)';
});
isTooltipVisible = true;
autoHideTimer = setTimeout(hideTooltip, AUTO_HIDE_DELAY);
}
// Hide tooltip with fade-out
function hideTooltip() {
if (!isTooltipVisible) return;
clearTimeout(hideTimer);
clearTimeout(autoHideTimer);
tooltipElement.style.opacity = '0';
tooltipElement.style.transform = 'translateX(10px) translateY(5px)';
hideTimer = setTimeout(() => {
tooltipElement.style.display = 'none';
isTooltipVisible = false;
}, 200);
}
// Handle new selection
function handleSelection(e) {
const selection = window.getSelection().toString().trim();
// If selecting non-emoji or invalid length, hide current tooltip
if (!selection || selection.length < 1 || selection.length > 10) {
if (isTooltipVisible) setTimeout(() => {hideTooltip()}, 2000);
return;
}
const emojiData = emojiMap.get(selection);
if (emojiData) {
// Hide existing tooltip immediately (but keep animation)
if (isTooltipVisible) {
hideTooltip();
// Wait for fade-out to finish before showing new one (optional but smoother)
setTimeout(() => {
showNewTooltip(emojiData, selection, e.clientX, e.clientY);
}, 200);
} else {
showNewTooltip(emojiData, selection, e.clientX, e.clientY);
}
} else {
if (isTooltipVisible) hideTooltip();
}
}
function showNewTooltip(emojiData, emoji, x, y) {
const content = `
<div style="display: flex; align-items: center; gap: 8px">
<span style="font-size: 1.2em">${emoji}</span>
<div>
<div style="font-weight: 600; line-height: 1.3">${emojiData.name}</div>
<div style="color: #ccc; font-size: 13px; margin-top: 2px; font-weight: normal">Group: ${emojiData.group}</div>
</div>
</div>
`;
showTooltip(content, x, y);
lastMousePosition = { x, y };
}
// Hide tooltip if mouse moves far away
function handleMouseMove(e) {
if (!isTooltipVisible) return;
const dx = Math.abs(e.clientX - lastMousePosition.x);
const dy = Math.abs(e.clientY - lastMousePosition.y);
if (dx > MOUSE_MOVE_THRESHOLD || dy > MOUSE_MOVE_THRESHOLD) {
hideTooltip();
lastMousePosition = { x: e.clientX, y: e.clientY };
}
}
// Load emoji data with caching
function loadEmojiData() {
const cached = GM_getValue(CACHE_KEY, null);
if (cached && cached.version === CACHE_VERSION) {
try {
const data = cached.data;
Object.entries(data).forEach(([emoji, info]) => {
emojiMap.set(emoji, {
name: info.name,
group: info.group
});
});
console.log(`Emoji Tooltip: ${emojiMap.size} emojis loaded from cache`);
return;
} catch (e) {
console.warn('Cached emoji data corrupted, reloading from network...');
}
}
GM_xmlhttpRequest({
method: 'GET',
url: EMOJI_DATA_URL,
onload: function(response) {
try {
const data = JSON.parse(response.responseText);
GM_setValue(CACHE_KEY, {
version: CACHE_VERSION,
timestamp: Date.now(),
data: data
});
Object.entries(data).forEach(([emoji, info]) => {
emojiMap.set(emoji, {
name: info.name,
group: info.group
});
});
console.log(`Emoji Tooltip: ${emojiMap.size} emojis loaded and cached`);
} catch (error) {
console.error('Failed to parse emoji data:', error);
}
},
onerror: function() {
console.error('Failed to load emoji data from GitHub');
}
});
}
// Initialize everything
function init() {
initTooltip();
loadEmojiData();
document.addEventListener('mouseup', handleSelection, { passive: true });
document.addEventListener('mousemove', handleMouseMove, { passive: true });
window.addEventListener('scroll', hideTooltip, { passive: true });
window.addEventListener('blur', hideTooltip);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();