说在前面
平时做页面开发、交互动效调试的时候,你们都是怎么看鼠标坐标的?
要么在控制台疯狂打印 clientX/clientY,要么打开开发者工具一点点找位置,一顿操作下来很是麻烦。
最后干脆自己写了个书签小插件,点一下就能在页面上可视化显示鼠标定位和十字辅助线,坐标在哪、元素对不对齐,一眼就看明白,开发调试瞬间方便多了。
插件地址
功能介绍
1.一键可视化鼠标定位
点击书签,立刻在当前页面生成跟随鼠标的十字准星。
2.实时显示多套坐标信息
提示框里会自动显示 4 种常用坐标,一套满足所有调试场景:
- client (X,Y) —— 可视区坐标
- page (X,Y) —— 页面坐标(带滚动)
- screen (X,Y) —— 屏幕坐标
- svg (X,Y) —— SVG 内部真实坐标
3.左键锁定坐标,方便对比
鼠标左键点一下,就能锁定当前鼠标定位,准星停在原地,方便反复核对位置。
4.退出功能
点击 exc 或者再次点击书签即可退出。
关键代码
1.十字准星与覆盖层创建
// 全屏透明覆盖层(核心:不影响页面点击)
const overlay = document.createElement("div");
overlay.id = "mouse-crosshair-overlay";
overlay.style.cssText = `
position:fixed;
top:0; left:0; width:100%; height:100%;
pointer-events:none;
z-index:999999;
touch-action:none;
`;
// 水平线
const hLine = document.createElement("div");
hLine.style.cssText = `
position:absolute;
width:100%; height:1px;
background:rgba(255,0,0,0.7);
top:50%;
`;
// 垂直线
const vLine = document.createElement("div");
vLine.style.cssText = `
position:absolute;
height:100%; width:1px;
background:rgba(255,0,0,0.7);
left:50%;
`;
// 坐标显示面板
const coordBox = document.createElement("div");
coordBox.style.cssText = `
position:absolute;
background:rgba(255,0,0,0.7);
color:white;
padding:4px 8px;
font:12px monospace;
border-radius:4px;
pointer-events:auto;
`;
2.鼠标定位实时更新
function updateCrosshair(e) {
if (isLocked && lockedPos) {
// 锁定状态:固定在某个坐标
hLine.style.top = lockedPos.clientY + "px";
vLine.style.left = lockedPos.clientX + "px";
return;
}
// 正常:跟随鼠标实时更新定位
hLine.style.top = e.clientY + "px";
vLine.style.left = e.clientX + "px";
updateCoordText(e);
}
3.SVG 内部坐标解析
function getSvgCoord(e) {
const svgList = document.querySelectorAll("svg");
// 判断鼠标是否在 SVG 内部
// 再用 matrixTransform 转换成 SVG 内部坐标
}
怎么使用?
1.插件获取
插件已经整理好
可以直接拖拽添加到书签栏。
2.添加插件
将红框框柱的内容拖拽到书签栏即可
3.使用
需要可视化鼠标坐标定位信息的时候点击保存的书签即可
点击 esc 或者再次点击书签可以隐藏十字线
完整格式化代码
对源码感兴趣,想自己改样式、加功能的同学可以看看~
javascript:(function() {
const CONFIG = {
id: "mouse-crosshair-overlay",
colors: {
light: "rgba(255,0,0,0.7)",
dark: "rgba(0,200,255,0.7)"
},
lineWidth: 1,
offset: 8,
zIndex: 999999
};
let state = {
isLocked: false,
lockedPos: null,
currentColor: CONFIG.colors.light
};
const exist = document.getElementById(CONFIG.id);
if (exist) {
exist.remove();
return;
}
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
state.currentColor = isDark ? CONFIG.colors.dark : CONFIG.colors.light;
const overlay = document.createElement("div");
overlay.id = CONFIG.id;
overlay.style = `
position:fixed;top:0;left:0;width:100%;height:100%;
pointer-events:none;z-index:${CONFIG.zIndex};touch-action:none;
`;
const hLine = document.createElement("div");
hLine.style = `
position:absolute;width:100%;height:${CONFIG.lineWidth}px;
background:${state.currentColor};top:50%;transition:background 0.2s;
`;
const vLine = document.createElement("div");
vLine.style = `
position:absolute;height:100%;width:${CONFIG.lineWidth}px;
background:${state.currentColor};left:50%;transition:background 0.2s;
`;
const coordBox = document.createElement("div");
coordBox.style = `
position:absolute;background:${state.currentColor};color:white;
padding:4px 8px;font:12px monospace;border-radius:4px;
cursor:pointer;pointer-events:auto;user-select:none;
`;
overlay.append(hLine, vLine, coordBox);
document.body.appendChild(overlay);
let lastEvent = null;
function getSvgCoord(e) {
const svgs = document.querySelectorAll("svg");
if (!svgs.length) return "无SVG";
let target = null, pt = null;
svgs.forEach(svg => {
const r = svg.getBoundingClientRect();
if (
e.clientX >= r.left && e.clientX <= r.right &&
e.clientY >= r.top && e.clientY <= r.bottom
) {
const p = svg.createSVGPoint();
p.x = e.clientX;
p.y = e.clientY;
pt = p.matrixTransform(svg.getScreenCTM().inverse());
target = svg;
}
});
return pt ? `svg(${Math.round(pt.x)}, ${Math.round(pt.y)})` : "不在SVG内";
}
function updateCoordBox(e) {
if (!e) return;
const top = e.clientY + CONFIG.offset;
const left = e.clientX + CONFIG.offset;
coordBox.style.top = `${top}px`;
coordBox.style.left = `${left}px`;
const lock = state.isLocked ? "🔒 " : "";
const text = `
client(${e.clientX},${e.clientY}) |
page(${e.pageX},${e.pageY}) |
screen(${e.screenX},${e.screenY}) |
${getSvgCoord(e)}
`.replace(/\n/g, "").replace(/ /g, " ");
coordBox.textContent = lock + text;
}
function updateCrosshair(e) {
if (state.isLocked && state.lockedPos) {
hLine.style.top = `${state.lockedPos.clientY}px`;
vLine.style.left = `${state.lockedPos.clientX}px`;
return;
}
hLine.style.top = `${e.clientY}px`;
vLine.style.left = `${e.clientX}px`;
lastEvent = e;
updateCoordBox(e);
}
document.addEventListener("mousemove", updateCrosshair);
document.addEventListener("touchmove", e => {
const t = e.touches[0];
updateCrosshair(t);
e.preventDefault();
}, { passive: false });
document.addEventListener("mousedown", e => {
if (e.button === 0 && !state.isLocked) {
state.isLocked = true;
state.lockedPos = lastEvent;
updateCoordBox(lastEvent);
}
});
coordBox.addEventListener("click", e => {
e.stopPropagation();
if (state.isLocked) {
state.isLocked = false;
coordBox.textContent = "🔓 已解锁";
setTimeout(() => updateCoordBox(lastEvent), 1000);
} else {
navigator.clipboard.writeText(coordBox.textContent.replace("📋 ", "")).then(() => {
coordBox.textContent = "📋 已复制!";
setTimeout(() => updateCoordBox(lastEvent), 1000);
});
}
});
document.addEventListener("keydown", e => {
if (e.key === "Escape") overlay.remove();
});
overlay.addEventListener("remove", () => {
document.removeEventListener("mousemove", updateCrosshair);
document.removeEventListener("touchmove", updateCrosshair);
});
const init = {
clientX: innerWidth / 2,
clientY: innerHeight / 2,
pageX: innerWidth / 2,
pageY: innerHeight / 2,
screenX: screen.width / 2,
screenY: screen.height / 2
};
lastEvent = init;
updateCrosshair(init);
})();
更多有趣插件
还有其它有趣插件都已经整理到了这里:
有兴趣的同学可以看看有没有自己需要的插件~
公众号
关注公众号『 前端也能这么有趣 』,获取更多有趣内容~
发送 加群 还能加入前端交流群,和大家一起讨论技术、分享经验,偶尔也能摸鱼聊天~
说在后面
🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。