refactor: make each floating button independently draggable
This commit is contained in:
1 parent
27d020e843
commit
58099578c9
1 file changed
+72
-87
+72
-87
@@ -295,78 +295,67 @@ export default function Home() {
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 可拖动浮动按钮组 */}
|
||||
{(() => {
|
||||
const STORAGE_KEY = 'author-fab-pos';
|
||||
const saved = typeof window !== 'undefined' ? JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null') : null;
|
||||
const defaultPos = { right: 24, bottom: 24 };
|
||||
return (
|
||||
<div
|
||||
id="tour-fab-group"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: `${saved?.right ?? defaultPos.right}px`,
|
||||
bottom: `${saved?.bottom ?? defaultPos.bottom}px`,
|
||||
zIndex: 40,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '8px',
|
||||
cursor: 'grab',
|
||||
userSelect: 'none',
|
||||
}}
|
||||
onPointerDown={(e) => {
|
||||
if (e.target.closest('a') || (e.target.tagName === 'BUTTON' && !e.target.classList.contains('fab-drag-handle'))) return;
|
||||
const el = e.currentTarget;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const parentRect = el.parentElement.getBoundingClientRect();
|
||||
const offsetX = e.clientX - rect.left;
|
||||
const offsetY = e.clientY - rect.top;
|
||||
el.style.cursor = 'grabbing';
|
||||
let moved = false;
|
||||
const onMove = (ev) => {
|
||||
moved = true;
|
||||
const newRight = parentRect.right - ev.clientX - (rect.width - offsetX);
|
||||
const newBottom = parentRect.bottom - ev.clientY - (rect.height - offsetY);
|
||||
const clampedRight = Math.max(0, Math.min(parentRect.width - rect.width, newRight));
|
||||
const clampedBottom = Math.max(0, Math.min(parentRect.height - rect.height, newBottom));
|
||||
el.style.right = `${clampedRight}px`;
|
||||
el.style.bottom = `${clampedBottom}px`;
|
||||
};
|
||||
const onUp = () => {
|
||||
el.style.cursor = 'grab';
|
||||
document.removeEventListener('pointermove', onMove);
|
||||
document.removeEventListener('pointerup', onUp);
|
||||
if (moved) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify({
|
||||
right: parseInt(el.style.right),
|
||||
bottom: parseInt(el.style.bottom),
|
||||
}));
|
||||
}
|
||||
};
|
||||
document.addEventListener('pointermove', onMove);
|
||||
document.addEventListener('pointerup', onUp);
|
||||
}}
|
||||
>
|
||||
{/* 独立可拖动浮动按钮 */}
|
||||
{['github', 'help'].map(btnKey => {
|
||||
const storageKey = `author-fab-${btnKey}`;
|
||||
const saved = typeof window !== 'undefined' ? JSON.parse(localStorage.getItem(storageKey) || 'null') : null;
|
||||
const defaults = { github: { right: 24, bottom: 76 }, help: { right: 24, bottom: 24 } };
|
||||
const pos = saved || defaults[btnKey];
|
||||
const makeDraggable = (e) => {
|
||||
const el = e.currentTarget;
|
||||
const parentRect = el.parentElement.getBoundingClientRect();
|
||||
const rect = el.getBoundingClientRect();
|
||||
const startX = e.clientX, startY = e.clientY;
|
||||
const offsetX = e.clientX - rect.left;
|
||||
const offsetY = e.clientY - rect.top;
|
||||
let dragging = false;
|
||||
const onMove = (ev) => {
|
||||
if (!dragging && Math.abs(ev.clientX - startX) + Math.abs(ev.clientY - startY) < 5) return;
|
||||
dragging = true;
|
||||
ev.preventDefault();
|
||||
const r = Math.max(0, Math.min(parentRect.width - rect.width, parentRect.right - ev.clientX - (rect.width - offsetX)));
|
||||
const b = Math.max(0, Math.min(parentRect.height - rect.height, parentRect.bottom - ev.clientY - (rect.height - offsetY)));
|
||||
el.style.right = `${r}px`;
|
||||
el.style.bottom = `${b}px`;
|
||||
};
|
||||
const onUp = (ev) => {
|
||||
document.removeEventListener('pointermove', onMove);
|
||||
document.removeEventListener('pointerup', onUp);
|
||||
if (dragging) {
|
||||
ev.preventDefault();
|
||||
localStorage.setItem(storageKey, JSON.stringify({ right: parseInt(el.style.right), bottom: parseInt(el.style.bottom) }));
|
||||
}
|
||||
};
|
||||
document.addEventListener('pointermove', onMove);
|
||||
document.addEventListener('pointerup', onUp);
|
||||
};
|
||||
const commonStyle = {
|
||||
position: 'absolute',
|
||||
right: `${pos.right}px`,
|
||||
bottom: `${pos.bottom}px`,
|
||||
zIndex: 40,
|
||||
borderRadius: '50%',
|
||||
width: '44px',
|
||||
height: '44px',
|
||||
boxShadow: 'var(--shadow-md)',
|
||||
fontSize: '18px',
|
||||
opacity: 0.8,
|
||||
cursor: 'grab',
|
||||
userSelect: 'none',
|
||||
touchAction: 'none',
|
||||
};
|
||||
if (btnKey === 'github') {
|
||||
return (
|
||||
<a
|
||||
key="github"
|
||||
id="tour-github"
|
||||
href="https://github.com/YuanShiJiLoong/author"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="btn btn-secondary btn-icon"
|
||||
style={{
|
||||
borderRadius: '50%',
|
||||
width: '44px',
|
||||
height: '44px',
|
||||
boxShadow: 'var(--shadow-md)',
|
||||
fontSize: '18px',
|
||||
transition: 'opacity 0.15s',
|
||||
opacity: 0.8,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
}}
|
||||
style={{ ...commonStyle, display: 'flex', alignItems: 'center', justifyContent: 'center', textDecoration: 'none', color: 'inherit' }}
|
||||
onPointerDown={makeDraggable}
|
||||
onClick={(e) => { if (e.defaultPrevented) return; }}
|
||||
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.8'}
|
||||
title="GitHub"
|
||||
@@ -375,28 +364,24 @@ export default function Home() {
|
||||
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
|
||||
</svg>
|
||||
</a>
|
||||
<button
|
||||
id="tour-help"
|
||||
className="btn btn-secondary btn-icon"
|
||||
style={{
|
||||
borderRadius: '50%',
|
||||
width: '44px',
|
||||
height: '44px',
|
||||
boxShadow: 'var(--shadow-md)',
|
||||
fontSize: '18px',
|
||||
transition: 'opacity 0.15s',
|
||||
opacity: 0.8
|
||||
}}
|
||||
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.8'}
|
||||
onClick={() => setShowHelp(true)}
|
||||
title={t('page.helpAndGuide')}
|
||||
>
|
||||
📖
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<button
|
||||
key="help"
|
||||
id="tour-help"
|
||||
className="btn btn-secondary btn-icon"
|
||||
style={commonStyle}
|
||||
onPointerDown={makeDraggable}
|
||||
onClick={(e) => { if (!e.defaultPrevented) setShowHelp(true); }}
|
||||
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.8'}
|
||||
title={t('page.helpAndGuide')}
|
||||
>
|
||||
📖
|
||||
</button>
|
||||
);
|
||||
})()}
|
||||
})}
|
||||
</main>
|
||||
|
||||
{/* ===== AI 对话侧栏 ===== */}
|
||||
|
||||
Reference in new issue
Block a user