Compare commits

..

2 Commits

Author SHA1 Message Date
hypercross bd2e7ac5a5 style: pin & table 2026-02-26 23:22:28 +08:00
hypercross 63c320f669 refactor: improved positioning 2026-02-26 23:10:13 +08:00
2 changed files with 17 additions and 72 deletions

View File

@ -6,13 +6,7 @@ customElement("md-pin", { x: 0, y: 0 }, (props, { element }) => {
const [position, setPosition] = createSignal<{ top: string; left: string }>({ top: "0", left: "0" }); const [position, setPosition] = createSignal<{ top: string; left: string }>({ top: "0", left: "0" });
const [visible, setVisible] = createSignal(false); const [visible, setVisible] = createSignal(false);
const [containerStyle, setContainerStyle] = createSignal<{ position: string; top: string; left: string; width: string; height: string }>({ const [transformStyle, setTransformStyle] = createSignal<string>("");
position: "absolute",
top: "0",
left: "0",
width: "0",
height: "0"
});
const [showToast, setShowToast] = createSignal(false); const [showToast, setShowToast] = createSignal(false);
const [toastMessage, setToastMessage] = createSignal(""); const [toastMessage, setToastMessage] = createSignal("");
let pinContainer: HTMLSpanElement | undefined; let pinContainer: HTMLSpanElement | undefined;
@ -57,28 +51,19 @@ customElement("md-pin", { x: 0, y: 0 }, (props, { element }) => {
if (!targetImage || !pinContainer) return; if (!targetImage || !pinContainer) return;
const imgRect = targetImage.getBoundingClientRect(); const imgRect = targetImage.getBoundingClientRect();
const articleEl = element?.closest('article[data-src]'); const containerRect = pinContainer.parentElement.getBoundingClientRect();
const articleRect = articleEl?.getBoundingClientRect();
if (!articleRect) return;
// 计算图片相对于 article 的位置 // 计算图片左上角相对于容器原始位置的偏移
const relativeTop = imgRect.top - articleRect.top; const offsetX = imgRect.left - containerRect.left;
const relativeLeft = imgRect.left - articleRect.left; const offsetY = imgRect.top - containerRect.top;
// 设置容器样式,使其定位到图片位置 // 使用 transform 将容器移动到图片位置
setContainerStyle({ setTransformStyle(`translate(${offsetX}px, ${offsetY}px)`);
position: "absolute",
top: `${relativeTop}px`,
left: `${relativeLeft}px`,
width: `${imgRect.width}px`,
height: `${imgRect.height}px`
});
// 计算 pin 在图片内的相对位置x/y 是百分比) // 计算 pin 在图片内的相对位置x/y 是百分比)
const x = typeof props.x === 'number' ? props.x : parseFloat(props.x) || 0; 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 y = typeof props.y === 'number' ? props.y : parseFloat(props.y) || 0;
const left = (x / 100) * imgRect.width; const left = (x / 100) * imgRect.width;
const top = (y / 100) * imgRect.height; const top = (y / 100) * imgRect.height;
@ -91,20 +76,11 @@ customElement("md-pin", { x: 0, y: 0 }, (props, { element }) => {
onMount(() => { onMount(() => {
// 查找目标图片 // 查找目标图片
targetImage = findNearestImage(); targetImage = findNearestImage();
if (targetImage) {
// 确保图片容器是 relative 定位
const imgParent = targetImage.parentElement;
if (imgParent) {
const parentStyle = window.getComputedStyle(imgParent);
if (parentStyle.position === 'static') {
imgParent.style.position = 'relative';
}
}
if (targetImage) {
// 初始定位 // 初始定位
updatePosition(); updatePosition();
// 使用 ResizeObserver 监听图片大小变化 // 使用 ResizeObserver 监听图片大小变化
resizeObserver = new ResizeObserver(() => { resizeObserver = new ResizeObserver(() => {
updatePosition(); updatePosition();
@ -126,42 +102,11 @@ customElement("md-pin", { x: 0, y: 0 }, (props, { element }) => {
} }
}); });
// 处理点击,报告坐标并复制到剪贴板
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 ( return (
<span <div
ref={pinContainer} ref={pinContainer}
class="md-pin-container" class="md-pin-container"
style={{ display: 'inline', position: containerStyle().position, top: containerStyle().top, left: containerStyle().left, width: containerStyle().width, height: containerStyle().height }} style={{ transform: transformStyle(), 'transform-origin': 'top left' }}
onClick={handleClick}
> >
<Show when={visible() && targetImage}> <Show when={visible() && targetImage}>
<span <span
@ -183,6 +128,6 @@ customElement("md-pin", { x: 0, y: 0 }, (props, { element }) => {
{toastMessage()} {toastMessage()}
</div> </div>
</Show> </Show>
</span> </div>
); );
}); });

View File

@ -186,7 +186,7 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) =>
<Show when={props.roll}> <Show when={props.roll}>
<button <button
onClick={handleRoll} onClick={handleRoll}
class="text-gray-500 hover:text-gray-700 flex-shrink-0" class="text-gray-500 hover:text-gray-700 flex-shrink-0 cursor-pointer"
title="随机切换" title="随机切换"
> >
🎲 🎲
@ -197,7 +197,7 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) =>
{(row, index) => ( {(row, index) => (
<button <button
onClick={() => setActiveTab(index())} onClick={() => setActiveTab(index())}
class={`font-medium transition-colors flex-shrink-0 min-w-[1.6em] ${ class={`font-medium transition-colors flex-shrink-0 min-w-[1.6em] cursor-pointer ${
activeTab() === index() activeTab() === index()
? 'text-blue-600 border-b-2 border-blue-600' ? 'text-blue-600 border-b-2 border-blue-600'
: 'text-gray-500 hover:text-gray-700' : 'text-gray-500 hover:text-gray-700'