肉眼难以分辨 UI 是否对齐,写个插件来辅助

说在前面

平时进行页面开发的时候,你们都是怎么判断元素是否对齐的?之前我都是使用截图工具利用截图的边缘来判断两个元素在水平和垂直方向上是否对齐。

但是这样操作久了总觉得有些繁琐,所以最后直接写了一个书签插件,一键给页面加上网格线,这样元素是否对齐就一目了然了。

功能介绍

1.给页面添加网格线

点击书签文件即可一键生成网格线

2.调整网格大小

可以通过配置面板调整网格大小

3.调整网格线颜色

可以通过配置面板调整网格线颜色

4.调整网格线透明度

可以通过配置面板调整网格线透明度

关键代码

1.网格覆盖层与控制面板的创建

javascript 复制代码
// 网格覆盖层
const gridOverlay = document.createElement("div");
gridOverlay.id = "custom-grid-overlay";
// 关键样式: fixed 定位铺满全屏, pointer-events:none 让鼠标可以穿透它, z-index 设为极高值
gridOverlay.style.cssText = "position:fixed; top:0; left:0; right:0; bottom:0; pointer-events:none; z-index:2147483646;";

// 控制面板
const controlPanel = document.createElement("div");
controlPanel.id = "grid-control-panel";
// 关键样式: 磨砂玻璃效果, 高 z-index, pointer-events:auto 使其可交互
controlPanel.style.cssText = "position:fixed; top:10px; right:10px; background:rgba(255, 255, 255, 0.8); backdrop-filter: blur(5px); ... z-index:2147483647; pointer-events:auto;";

2.网格绘制

javascript 复制代码
function drawGrid() {
  gridOverlay.innerHTML = ''; // 先清空旧网格
  const width = window.innerWidth, height = window.innerHeight;
  // 计算需要的行数和列数
  const colCount = Math.ceil(width / config.gridSize);
  const rowCount = Math.ceil(height / config.gridSize);

  // 循环创建垂直线
  for (let i = 0; i <= colCount; i++) {
    const line = document.createElement("div");
    line.style.cssText = `position:absolute; top:0; bottom:0; width:${config.lineWidth}px; left:${i * config.gridSize}px; background-color:${config.gridColor};`;
    gridOverlay.appendChild(line);
  }
  // 循环创建水平线
  // ... 逻辑类似 ...
}

3.配置面板拖拽功能

javascript 复制代码
function startDrag(e) {
  // ... (省略部分代码)
  dragState.isDragging = true;
  // 记录初始位置和鼠标/触摸点坐标
  const panelRect = controlPanel.getBoundingClientRect();
  const coords = getClientCoords(e);
  dragState.startX = coords.x;
  dragState.startY = coords.y;
  dragState.initialLeft = panelRect.left;
  dragState.initialTop = panelRect.top;
  
  // 注册移动和结束事件
  document.addEventListener("mousemove", onDrag);
  document.addEventListener("mouseup", endDrag);
  // ... (触摸事件)
}

function onDrag(e) {
  if (!dragState.isDragging) return;
  // 计算偏移量并更新面板位置
  const coords = getClientCoords(e);
  const deltaX = coords.x - dragState.startX;
  const deltaY = coords.y - dragState.startY;
  controlPanel.style.left = `${dragState.initialLeft + deltaX}px`;
  controlPanel.style.top = `${dragState.initialTop + deltaY}px`;
}

function endDrag(e) {
  if (!dragState.isDragging) return;
  dragState.isDragging = false;
  // 移除事件监听器
  document.removeEventListener("mousemove", onDrag);
  document.removeEventListener("mouseup", endDrag);
  // ... (触摸事件)
}

怎么使用?

1.代码复制

复制下面压缩过的代码

javascript 复制代码
javascript:!function(){var e;if(document.getElementById("custom-grid-overlay")){window.cleanupGridBookmarklet&&window.cleanupGridBookmarklet();return}let t={gridSize:20,gridColor:"rgba(255, 0, 0, 0.1)",lineWidth:1},i=!0,n=document.createElement("div");n.id="custom-grid-overlay",n.style.cssText="position:fixed; top:0; left:0; right:0; bottom:0; pointer-events:none; z-index:2147483646;";let r=document.createElement("div");r.id="grid-control-panel",r.style.cssText="position:fixed; top:10px; right:10px; background:rgba(255, 255, 255, 0.6); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); border:1px solid rgba(204, 204, 204, 0.5); border-radius:8px; z-index:2147483647; pointer-events:auto; font-family:Arial,sans-serif; font-size:12px; box-shadow:0 4px 12px rgba(0,0,0,0.2); width:220px;";let o=document.createElement("div");o.style.cssText="padding:8px 10px; background:rgba(245, 245, 245, 0.7); border-bottom:1px solid rgba(238, 238, 238, 0.7); cursor:move; display:flex; justify-content:space-between; align-items:center; user-select:none; touch-action:none; border-top-left-radius: 8px; border-top-right-radius: 8px;",o.innerHTML='<strong>网格控制</strong><button id="toggle-panel" style="border:none;background:none;cursor:pointer;padding:2px 5px;">▼</button>';let l=document.createElement("div");l.style.padding="10px",l.innerHTML=`    <div style="margin-bottom:8px;"><label>网格大小 (px): </label><input type="number" value="${t.gridSize}" min="5" max="200" id="grid-size" style="width:60px;margin-left:5px;"></div>    <div style="margin-bottom:8px;"><label>网格颜色: </label><input type="color" value="#ff0000" id="grid-color" style="width:30px;height:20px;padding:0;margin-left:5px;"></div>    <div style="margin-bottom:8px;"><label>透明度: </label><input type="range" min="0" max="1" step="0.1" value="0.1" id="grid-alpha" style="width:100px;margin-left:5px;"></div>  %60;let a=document.createElement("div");a.style.cssText="display:flex; gap:5px; margin-top:10px;";let d=document.createElement("button");d.textContent="应用设置",d.style.padding="5px 8px";let s=document.createElement("button");function p(){n.innerHTML="";let e=window.innerWidth,i=window.innerHeight,r=Math.ceil(e/t.gridSize),o=Math.ceil(i/t.gridSize);for(let l=0;l<=r;l++){let a=document.createElement("div");a.style.cssText=%60position:absolute; top:0; bottom:0; width:${t.lineWidth}px; left:${l*t.gridSize}px; background-color:${t.gridColor};%60,n.appendChild(a)}for(let d=0;d<=o;d++){let s=document.createElement("div");s.style.cssText=%60position:absolute; left:0; right:0; height:${t.lineWidth}px; top:${d*t.gridSize}px; background-color:${t.gridColor};%60,n.appendChild(s)}}s.textContent="关闭网格",s.style.padding="5px 8px",a.appendChild(d),a.appendChild(s),l.appendChild(a),r.appendChild(o),r.appendChild(l),document.body.appendChild(n),document.body.appendChild(r);let g={isDragging:!1,startX:0,startY:0,initialLeft:0,initialTop:0};function c(e){return e.touches&&e.touches.length?{x:e.touches[0].clientX,y:e.touches[0].clientY}:{x:e.clientX,y:e.clientY}}function u(e){if("INPUT"===e.target.tagName||"BUTTON"===e.target.tagName)return;e.preventDefault(),e.stopPropagation(),g.isDragging=!0,o.style.cursor="grabbing";let t=r.getBoundingClientRect(),i=c(e);g.startX=i.x,g.startY=i.y,g.initialLeft=t.left,g.initialTop=t.top,document.addEventListener("mousemove",m),document.addEventListener("mouseup",x),document.addEventListener("mouseleave",x),document.addEventListener("touchmove",m,{passive:!1}),document.addEventListener("touchend",x)}function m(e){if(!g.isDragging)return;e.preventDefault(),e.stopPropagation();let t=c(e),i=t.x-g.startX,n=t.y-g.startY,o=g.initialLeft+i,l=g.initialTop+n;o=Math.max(0,Math.min(o,window.innerWidth-r.offsetWidth)),l=Math.max(0,Math.min(l,window.innerHeight-r.offsetHeight)),r.style.right="auto",r.style.left=%60${o}px%60,r.style.top=%60${l}px%60}function x(e){g.isDragging&&(e.stopPropagation(),g.isDragging=!1,o.style.cursor="move",document.removeEventListener("mousemove",m),document.removeEventListener("mouseup",x),document.removeEventListener("mouseleave",x),document.removeEventListener("touchmove",m,{passive:!1}),document.removeEventListener("touchend",x))}d.onclick=function e(){t.gridSize=parseInt(document.getElementById("grid-size").value,10);let i=document.getElementById("grid-color").value,n=parseFloat(document.getElementById("grid-alpha").value),r=parseInt(i.substring(1,3),16),o=parseInt(i.substring(3,5),16),l=parseInt(i.substring(5,7),16);t.gridColor=%60rgba(${r},${o},${l},${n})%60,p()},s.onclick=b,document.getElementById("toggle-panel").addEventListener("click",function e(){i=!i,l.style.display=i?"block":"none",this.textContent=i?"▼":"▶"}),o.addEventListener("mousedown",u),o.addEventListener("touchstart",u,{passive:!1});let v,$=(e=p,function(...t){clearTimeout(v),v=setTimeout(()=>e.apply(this,t),250)});function b(){n.remove(),r.remove(),window.removeEventListener("resize",$),window.removeEventListener("orientationchange",$),delete window.cleanupGridBookmarklet}window.addEventListener("resize",$),window.addEventListener("orientationchange",$),window.cleanupGridBookmarklet=b,p()}();

2.创建新书签

在浏览器中,右键点击你的书签栏,选择"添加网页..."或"添加书签..."

3.编辑书签信息

将前面复制的代码粘贴到网址输入框里,点击保存即可

4.使用

在需要生成网格线的页面中点击保存的书签即可

完整格式化代码

对源码感兴趣的同学可以看看~

javascript 复制代码
javascript:(function() {
  function debounce(func, delay) {
    let timeout;
    return function(...args) {
      clearTimeout(timeout);
      timeout = setTimeout(() => func.apply(this, args), delay);
    };
  }

  if (document.getElementById("custom-grid-overlay")) {
    if (window.cleanupGridBookmarklet) {
      window.cleanupGridBookmarklet();
    }
    return;
  }

  let config = {
    gridSize: 20,
    gridColor: 'rgba(255, 0, 0, 0.1)',
    lineWidth: 1
  };
  let isPanelOpen = true;

  const gridOverlay = document.createElement("div");
  gridOverlay.id = "custom-grid-overlay";
  gridOverlay.style.cssText = "position:fixed; top:0; left:0; right:0; bottom:0; pointer-events:none; z-index:2147483646;";

  const controlPanel = document.createElement("div");
  controlPanel.id = "grid-control-panel";
  controlPanel.style.cssText = "position:fixed; top:10px; right:10px; background:rgba(255, 255, 255, 0.8); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); border:1px solid rgba(204, 204, 204, 0.5); border-radius:8px; z-index:2147483647; pointer-events:auto; font-family:Arial,sans-serif; font-size:12px; box-shadow:0 4px 12px rgba(0,0,0,0.2); width:220px;";

  const panelHeader = document.createElement("div");
  panelHeader.style.cssText = "padding:8px 10px; background:rgba(245, 245, 245, 0.7); border-bottom:1px solid rgba(238, 238, 238, 0.7); cursor:move; display:flex; justify-content:space-between; align-items:center; user-select:none; touch-action:none; border-top-left-radius: 8px; border-top-right-radius: 8px;";
  panelHeader.innerHTML = '<strong>网格控制</strong><button id="toggle-panel" style="border:none;background:none;cursor:pointer;padding:2px 5px;">▼</button>';

  const panelBody = document.createElement("div");
  panelBody.style.padding = "10px";
  panelBody.innerHTML = `
    <div style="margin-bottom:8px;"><label>网格大小 (px): </label><input type="number" value="${config.gridSize}" min="5" max="200" id="grid-size" style="width:60px;margin-left:5px;"></div>
    <div style="margin-bottom:8px;"><label>网格颜色: </label><input type="color" value="#ff0000" id="grid-color" style="width:30px;height:20px;padding:0;margin-left:5px;"></div>
    <div style="margin-bottom:8px;"><label>透明度: </label><input type="range" min="0" max="1" step="0.1" value="0.1" id="grid-alpha" style="width:100px;margin-left:5px;"></div>
  `;

  const panelFooter = document.createElement("div");
  panelFooter.style.cssText = "display:flex; gap:5px; margin-top:10px;";
  const applyButton = document.createElement("button");
  applyButton.textContent = "应用设置";
  applyButton.style.padding = "5px 8px";
  const closeButton = document.createElement("button");
  closeButton.textContent = "关闭网格";
  closeButton.style.padding = "5px 8px";

  panelFooter.appendChild(applyButton);
  panelFooter.appendChild(closeButton);
  panelBody.appendChild(panelFooter);
  controlPanel.appendChild(panelHeader);
  controlPanel.appendChild(panelBody);
  document.body.appendChild(gridOverlay);
  document.body.appendChild(controlPanel);

  function drawGrid() {
    gridOverlay.innerHTML = '';
    const width = window.innerWidth, height = window.innerHeight;
    const colCount = Math.ceil(width / config.gridSize), rowCount = Math.ceil(height / config.gridSize);
    for (let i = 0; i <= colCount; i++) {
      const line = document.createElement("div");
      line.style.cssText = `position:absolute; top:0; bottom:0; width:${config.lineWidth}px; left:${i * config.gridSize}px; background-color:${config.gridColor};`;
      gridOverlay.appendChild(line);
    }
    for (let i = 0; i <= rowCount; i++) {
      const line = document.createElement("div");
      line.style.cssText = `position:absolute; left:0; right:0; height:${config.lineWidth}px; top:${i * config.gridSize}px; background-color:${config.gridColor};`;
      gridOverlay.appendChild(line);
    }
  }

  function applySettings() {
    config.gridSize = parseInt(document.getElementById("grid-size").value, 10);
    const colorHex = document.getElementById("grid-color").value;
    const alpha = parseFloat(document.getElementById("grid-alpha").value);
    const r = parseInt(colorHex.substring(1, 3), 16), g = parseInt(colorHex.substring(3, 5), 16), b = parseInt(colorHex.substring(5, 7), 16);
    config.gridColor = `rgba(${r},${g},${b},${alpha})`;
    drawGrid();
  }

  function togglePanel() {
    isPanelOpen = !isPanelOpen;
    panelBody.style.display = isPanelOpen ? "block" : "none";
    this.textContent = isPanelOpen ? "▼" : "▶";
  }

  const dragState = { isDragging: false, startX: 0, startY: 0, initialLeft: 0, initialTop: 0 };

  function getClientCoords(e) {
    return e.touches && e.touches.length ? { x: e.touches[0].clientX, y: e.touches[0].clientY } : { x: e.clientX, y: e.clientY };
  }
  
  function startDrag(e) {
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return;
    e.preventDefault();
    e.stopPropagation();
    
    dragState.isDragging = true;
    panelHeader.style.cursor = "grabbing";
    
    const panelRect = controlPanel.getBoundingClientRect();
    const coords = getClientCoords(e);
    
    dragState.startX = coords.x;
    dragState.startY = coords.y;
    dragState.initialLeft = panelRect.left;
    dragState.initialTop = panelRect.top;

    document.addEventListener("mousemove", onDrag);
    document.addEventListener("mouseup", endDrag);
    document.addEventListener("mouseleave", endDrag);
    document.addEventListener("touchmove", onDrag, { passive: false });
    document.addEventListener("touchend", endDrag);
  }

  function onDrag(e) {
    if (!dragState.isDragging) return;
    e.preventDefault();
    e.stopPropagation();

    const coords = getClientCoords(e);
    const deltaX = coords.x - dragState.startX;
    const deltaY = coords.y - dragState.startY;

    let newLeft = dragState.initialLeft + deltaX;
    let newTop = dragState.initialTop + deltaY;

    newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - controlPanel.offsetWidth));
    newTop = Math.max(0, Math.min(newTop, window.innerHeight - controlPanel.offsetHeight));

    controlPanel.style.right = "auto";
    controlPanel.style.left = `${newLeft}px`;
    controlPanel.style.top = `${newTop}px`;
  }

  function endDrag(e) {
    if (!dragState.isDragging) return;
    e.stopPropagation();
    
    dragState.isDragging = false;
    panelHeader.style.cursor = "move";

    document.removeEventListener("mousemove", onDrag);
    document.removeEventListener("mouseup", endDrag);
    document.removeEventListener("mouseleave", endDrag);
    document.removeEventListener("touchmove", onDrag, { passive: false });
    document.removeEventListener("touchend", endDrag);
  }

  applyButton.onclick = applySettings;
  closeButton.onclick = cleanup;
  document.getElementById("toggle-panel").addEventListener("click", togglePanel);
  
  panelHeader.addEventListener("mousedown", startDrag);
  panelHeader.addEventListener("touchstart", startDrag, { passive: false });

  const debouncedDrawGrid = debounce(drawGrid, 250);
  window.addEventListener("resize", debouncedDrawGrid);
  window.addEventListener("orientationchange", debouncedDrawGrid);

  function cleanup() {
    gridOverlay.remove();
    controlPanel.remove();
    window.removeEventListener("resize", debouncedDrawGrid);
    window.removeEventListener("orientationchange", debouncedDrawGrid);
    delete window.cleanupGridBookmarklet;
  }
  
  window.cleanupGridBookmarklet = cleanup;
  
  drawGrid();

})();

公众号

关注公众号『 前端也能这么有趣 』,获取更多有趣内容~

发送 加群 还能加入前端交流群,和大家一起讨论技术、分享经验,偶尔也能摸鱼聊天~

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

相关推荐
fox_3 小时前
别再踩坑!JavaScript的this关键字,一次性讲透其“变脸”真相
前端·javascript
盛夏绽放3 小时前
uni-app Vue 项目的规范目录结构全解
前端·vue.js·uni-app
少卿3 小时前
React Native Vector Icons 安装指南
前端·react native
国家不保护废物3 小时前
Vue组件通信全攻略:从父子传到事件总线,玩转组件数据流!
前端·vue.js
写不来代码的草莓熊4 小时前
vue前端面试题——记录一次面试当中遇到的题(9)
前端·javascript·vue.js
JinSo4 小时前
pnpm monorepo 联调:告别 --global 参数
前端·github·代码规范
程序员码歌5 小时前
豆包Seedream4.0深度体验:p图美化与文生图创作
android·前端·后端
urhero5 小时前
工作事项管理小工具——HTML版
前端·html·实用工具·工作事项跟踪·任务跟踪小工具·本地小程序
二十雨辰5 小时前
eduAi-智能体创意平台
前端·vue.js