import { customElement, noShadowDOM } from "solid-element"; import { createSignal, onMount, onCleanup, Show } from "solid-js"; customElement("md-pin", { x: 0, y: 0 }, (props, { element }) => { noShadowDOM(); const [position, setPosition] = createSignal<{ top: string; left: string }>({ top: "0", left: "0" }); const [visible, setVisible] = createSignal(false); const [containerStyle, setContainerStyle] = createSignal<{ position: string; top: string; left: string; width: string; height: string }>({ position: "absolute", top: "0", left: "0", width: "0", height: "0" }); const [showToast, setShowToast] = createSignal(false); const [toastMessage, setToastMessage] = createSignal(""); let pinContainer: HTMLSpanElement | undefined; let targetImage: HTMLImageElement | undefined; let resizeObserver: ResizeObserver | undefined; // 从 element 的 textContent 获取 pin 标签内容 const label = element?.textContent?.trim() || ""; // 隐藏原始文本内容 if (element) { element.textContent = ""; } // 查找上方最近的图片 const findNearestImage = (): HTMLImageElement | null => { if (!element) return null; // 从当前元素向上查找 let current: Element | null = element; while (current) { // 在当前元素的之前兄弟节点中查找图片 let sibling: Element | null = current.previousElementSibling; while (sibling) { // 检查是否是图片元素 const img = sibling.querySelector('img'); if (img) return img; // 检查元素本身是否有图片相关的类或标签 if (sibling.tagName === 'IMG') return sibling as HTMLImageElement; sibling = sibling.previousElementSibling; } current = current.parentElement; } return null; }; // 更新 pin 位置和容器样式 const updatePosition = () => { if (!targetImage || !pinContainer) return; const imgRect = targetImage.getBoundingClientRect(); const articleEl = element?.closest('article[data-src]'); const articleRect = articleEl?.getBoundingClientRect(); if (!articleRect) return; // 计算图片相对于 article 的位置 const relativeTop = imgRect.top - articleRect.top; const relativeLeft = imgRect.left - articleRect.left; // 设置容器样式,使其定位到图片位置 setContainerStyle({ position: "absolute", top: `${relativeTop}px`, left: `${relativeLeft}px`, width: `${imgRect.width}px`, height: `${imgRect.height}px` }); // 计算 pin 在图片内的相对位置(x/y 是百分比) const x = typeof props.x === 'number' ? props.x : parseFloat(props.x) || 0; const y = typeof props.y === 'number' ? props.y : parseFloat(props.y) || 0; const left = (x / 100) * imgRect.width; const top = (y / 100) * imgRect.height; setPosition({ left: `${left}px`, top: `${top}px` }); }; onMount(() => { // 查找目标图片 targetImage = findNearestImage(); if (targetImage) { // 确保图片容器是 relative 定位 const imgParent = targetImage.parentElement; if (imgParent) { const parentStyle = window.getComputedStyle(imgParent); if (parentStyle.position === 'static') { imgParent.style.position = 'relative'; } } // 初始定位 updatePosition(); // 使用 ResizeObserver 监听图片大小变化 resizeObserver = new ResizeObserver(() => { updatePosition(); }); resizeObserver.observe(targetImage); // 延迟显示以等待位置计算完成 requestAnimationFrame(() => { setVisible(true); }); } else { console.warn('md-pin: 未找到目标图片'); } }); onCleanup(() => { if (resizeObserver && targetImage) { resizeObserver.unobserve(targetImage); } }); // 处理点击,报告坐标并复制到剪贴板 const handleClick = (e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (!targetImage) return; const imgRect = targetImage.getBoundingClientRect(); // 计算点击位置相对于图片的百分比 const clickX = ((e.clientX - imgRect.left) / imgRect.width) * 100; const clickY = ((e.clientY - imgRect.top) / imgRect.height) * 100; // 四舍五入到整数 const x = Math.round(clickX); const y = Math.round(clickY); // 生成格式化的坐标字符串 const coordText = `:md-pin[${label || ''}]{x=${x} y=${y}}`; // 复制到剪贴板 navigator.clipboard.writeText(coordText).then(() => { setToastMessage(`已复制:${coordText}`); setShowToast(true); setTimeout(() => setShowToast(false), 2000); }).catch(err => { console.error('复制失败:', err); }); }; return ( {label || '📍'}
{toastMessage()}
); });