WordPress 插入大图看不清怎么办:多档尺寸怎么选 + 灯箱滚轮缩放改造

AI 摘要997笔记AI

文章讲 WordPress 大图插入与灯箱改造:截图、表格等高信息图应正文插完整尺寸,原图宽控在1920内并转WebP,长图不要裁切;灯箱需支持滚轮无级缩放和按住拖动,而不是只切换适应屏幕与原始尺寸。改媒体尺寸只对新图生效,旧图需重建缩略图,相关代码已实测可用。

此内容由AI根据文章内容自动生成。

前几天发 MosDNS 实时统计面板 那篇,正文里贴了好几张 1920 宽的后台截图。发完自己点进去一看,图被压成 409px 宽的一条,鼠标移上去只有一个放大镜图标;点开灯箱之后更难受——只能在「适应屏幕」和「原始尺寸」之间二选一,切到原始尺寸图片比屏幕还大,想看左上角,滚不动。

这事儿不能怪 WordPress,是我自己没把两件事串起来想:一是图该怎么插进去,二是插进去之后读者怎么才能看清。这篇两个都讲,后半段的代码是我昨天刚给自己站改完、实测过的,可以直接拿去用。

一 · 先说结论,不想看原理的直接照做

三条,按顺序做:

  1. 截图、表格这类信息密度高的图,正文里就插「完整尺寸」,别插 medium,字会糊成一团。前提是原图宽度控制在 1920 以内、转 WebP。
  2. 长图绝对不要裁切。Square 缩略图用来做列表封面没问题,正文里一定要保原始比例。
  3. 灯箱必须支持滚轮缩放 + 按住拖动。只给一个「放大/缩小」按钮是不够用的,放大的图一定有超出屏幕的部分。

第三条是本篇的重点,代码在第三节,坑在第四节。

二 · WordPress 插大图:先搞清楚它到底给你生成了几个尺寸

你往媒体库传一张 1920×3400 的截图,WordPress 不会只存一份。它会按后台「设置 → 媒体」里的配置额外切出好几个尺寸,full 之外的都是自动生成的。

WordPress 一张原图自动生成的多档尺寸对比示意
同一张原图,WordPress 会派生出多档尺寸;正文里插哪一档,决定读者最终看到多少细节
尺寸名 默认生成规则 一般用在哪 我的建议
thumbnail 150×150,硬裁成正方形 后台列表、小头像 改成 300×200 硬裁,跟列表卡片比例对上
medium 最长边 300,等比 老版本编辑器默认 放宽到 640,否则正文字太小
medium_large 宽 768,等比 响应式 srcset 中间档 保留,移动端用得上
large 最长边 1024,等比 正文大图 放宽到 1280
full 原图,不动 灯箱、需要看细节的图 宽度上限压到 1920,转 WebP

改设置只对之后上传的图生效

这点很容易踩。你在后台把 medium 从 300 改成 640,已经传过的图不会重新生成。有 WP-CLI 的话跑 wp media regenerate;没有的话装个 Regenerate Thumbnails 插件,或者在主题里临时挂一段代码批量重建。别像我第一次那样,改完发现前台没变化,还以为缓存没清。

1. 在主题里写死尺寸,比在后台点更靠谱

后台设置会被插件、主题、甚至一次误点改掉。我更喜欢在 functions.php 里写死:

主题 functions.php
add_action('init', function () {
    update_option('thumbnail_size_w', 300);
    update_option('thumbnail_size_h', 200);
    update_option('thumbnail_crop', 1);      // 1 = 硬裁到 300x200
    update_option('medium_size_w', 640);
    update_option('medium_size_h', 0);       // 0 = 高度自适应,不裁
    update_option('medium_large_size_w', 768);
    update_option('medium_large_size_h', 0);
    update_option('large_size_w', 1280);
    update_option('large_size_h', 0);
});

// 用不上的档位直接摘掉,省磁盘也省生成时间
add_filter('intermediate_image_sizes_advanced', function ($sizes) {
    unset($sizes['medium_large']);
    return $sizes;
});

主题自己还会注册额外尺寸(我这个主题给首页列表注册了一档 -300x200)。想知道你的站到底生成了哪些,临时打印一下:

临时调试
add_action('wp_footer', function () {
    if (!current_user_can('manage_options')) return;
    echo '<pre>';
    foreach (get_intermediate_image_sizes() as $s) {
        echo $s . '  ';
    }
    echo '</pre>';
});

2. 正文里插哪一档:按图的用途分

我自己的分法很简单,看这张图是「氛围」还是「信息」。

图的类型 插哪一档 理由
封面、氛围图、产品照 large(1280) 看个大概就够,省流量
后台截图、代码截图、表格 full 有文字,缩小一档就看不清了
手机竖截图 full,宽度 720 左右 本身就不大,别再压
超长流程图(高度是宽度 3 倍以上) full,并且在文里额外给个直接打开原图的链接 再好用的灯箱,看 4000px 的长图也累

插原图会不会拖慢首屏?会一点,但没你想的那么严重:WordPress 从 5.5 起默认给正文图片加 loading="lazy",屏幕外的图不会在首屏下载。真正要管的是原图体积——我给自己定的规矩是宽度不超过 1920、转 WebP(质量 82)、单图控制在 300KB 以内。超过就先压再传。

3. 别让缩略图把长图裁了

这是我最开始犯的错。有一张 1920×3400 的竖向截图,被主题的列表缩略图硬裁成 300×200 之后,首页卡片上只剩中间一条,完全看不出是什么。所以给文章设封面图的时候,一定要确认那一档缩略图是「等比」还是「硬裁」。硬裁的档位只适合横向图。

图插得再大,读者看不清也是白搭。主题自带的灯箱一般只做一件事:点一下,在「适应屏幕」和「原始尺寸」之间切换。这个交互有两个硬伤:

  • 没有中间档。要么整张图缩到屏幕里(细节全没),要么直接 1:1(超出屏幕一大截)。
  • 放大之后没法看全。图比容器大,超出的部分滚不到,尤其左上角——这是 CSS flex 居中的一个老问题,下面细说。

我想要的是看图软件那种手感:滚轮无级缩放,按住左键上下左右拖。下面这段是精简后的核心逻辑,依赖主题已有的灯箱 DOM(我这个主题的遮罩层结构是 .lightbox-overlay > .lightbox-body > img.lightbox-image,你按自己的改选择器)。

1. 缩放:以光标为锚点

关键在「锚点」——滚轮缩放时光标指着的那一点应该保持不动,否则放大几下之后你关心的位置早就跑出屏幕了。做法是缩放前后各量一次图片位置,反推滚动补偿:

缩放核心逻辑
function zoomAt(cx, cy, factor) {
  var before = img.getBoundingClientRect();
  if (!before.width) return;
  // 光标落在图片的哪个比例位置
  var ux = (cx - before.left) / before.width;
  var uy = (cy - before.top)  / before.height;

  z = Math.min(8, Math.max(1, z * factor));
  applyScale();                       // 按 z 重设图片宽度

  var after = img.getBoundingClientRect();
  // 让同一个比例点回到光标下面
  body.scrollLeft += (after.left - (cx - ux * after.width));
  body.scrollTop  += (after.top  - (cy - uy * after.height));
}

overlay.addEventListener('wheel', function (e) {
  e.preventDefault();                 // 别让页面跟着滚
  var d = e.deltaY * (e.deltaMode === 1 ? 16 : 1);
  zoomAt(e.clientX, e.clientY, Math.exp(-d * 0.0018));
}, { passive: false });

Math.exp 那一下是为了让缩放手感是「等比」的:每一格滚轮的变化量跟当前倍数成正比,而不是固定加减多少像素。deltaMode 那个三元是兼容 Firefox——它有些版本给的是「行数」不是像素。

2. 拖动:按住左键平移

拖动平移
function startDrag(cx, cy) {
  if (z <= 1.005) return;            // 没放大就不拖
  dragging = true; moved = 0;
  sx = cx; sy = cy;
  ox = body.scrollLeft; oy = body.scrollTop;
  img.classList.add('n997-dragging');
}
function moveDrag(cx, cy) {
  if (!dragging) return;
  body.scrollLeft = ox - (cx - sx);
  body.scrollTop  = oy - (cy - sy);
}
img.addEventListener('mousedown', function (e) {
  if (e.button !== 0) return;
  e.preventDefault();                // 关键:挡掉浏览器「把图片拖出去」的原生行为
  startDrag(e.clientX, e.clientY);
});
document.addEventListener('mousemove', function (e) {
  moveDrag(e.clientX, e.clientY);
});
document.addEventListener('mouseup', endDrag);

e.preventDefault() 那行别省。不加的话按住一拖,浏览器会把图片当文件拖出来,灯箱原地不动。

另外记得:拖动之后会触发 click。如果你还保留了「单击图片切换」的逻辑,得在 mouseup 时判断位移,超过 6px 就不当点击处理,否则拖完一松手图片就跳回适应尺寸了。

四 · 两个 flex 坑,不改的话放大功能等于白做

这两个是我在无头浏览器里量出来的,不是想当然。代码写对了,但显示效果不对,问题都在这。

坑一:flex 居中会让溢出的两边都滚不到

灯箱的容器一般是这么写的(居中 + 允许滚动):

主题原有 CSS
.lightbox-body {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%; height: 100%;
  overflow: auto;
}

看起来没毛病。但当图片比容器大的时候,justify-content: center 会把超出的部分平均分到左右两侧。而 scrollLeft 不能为负,左边那半截就永远滚不到。我实测放大到 800% 时,scrollLeft = 0 状态下图片左边缘在 -240px,也就是说最左边 240 像素的内容这辈子都看不到。

flex 居中导致溢出两侧滚不到,对比 flex-start 加 margin auto
上:居中导致左右各溢出一半,左侧无法滚动到。下:改用 flex-start + margin:auto,有富余时照样居中,溢出时从起始边排布

修法不是去掉居中,而是把居中这件事交给图片的 margin:

修正后的 CSS
.lightbox-overlay .lightbox-body {
  overflow: auto !important;
  justify-content: flex-start !important;
  align-items: flex-start !important;
}
.lightbox-overlay .lightbox-image {
  margin: auto !important;
  flex-shrink: 0 !important;
}

margin: auto 的行为很理想:容器有富余空间时,外边距吃掉剩余空间,图片照样居中;一旦内容溢出,auto 外边距自动归零,图片老老实实从起始边开始排,四边都能滚到。

坑二:图片作为 flex item 会被压回原始尺寸

改完上面那条我以为好了,实测发现放大到某个倍数就不动了——800% 时宽度卡在 1920。巧了,1920 正好是图片原始宽度。

原因是 flex item 默认 flex-shrink: 1,容器装不下时会被压缩,一直压到「自动最小尺寸」。对图片来说这个最小值就是它的原始宽度。所以放大倍数再高,渲染出来最多也只有 1:1。

一行解决:flex-shrink: 0。这就是上面那段 CSS 里第二行的来历。

这两个坑跟主题无关

凡是「flex 居中 + 子元素可能溢出 + 需要滚动」的组合都会中招,不只是灯箱。表格容器、横向卡片列表、代码框都有同样的隐患。遇到「左边/上边看不到」的情况,先怀疑 justify-content: center。

五 · 改完怎么确认真的生效

截图看一眼不算验证,得让浏览器真的去点、去滚、去拖,然后把数值读出来。我用的是 Chrome 的 CDP 协议,Node 22 自带 fetch 和 WebSocket,不用装任何依赖。

验证片段(Node,无第三方依赖)
// 连上 headless Chrome 后,直接派发事件
await send('Page.navigate', { url: articleUrl });
await sleep(6000);

// 1. 打开灯箱
await ev(`document.querySelectorAll('.single-article__content img')[0].click()`);
await sleep(2500);

// 2. 滚轮放大 4 格
await ev(`
  var ov = document.querySelector('.lightbox-overlay');
  var r = img.getBoundingClientRect();
  for (var i = 0; i < 4; i++)
    ov.dispatchEvent(new WheelEvent('wheel', {
      deltaY: -100, clientX: r.left + r.width/2,
      clientY: r.top + r.height/2, bubbles: true, cancelable: true
    }));
  img.offsetWidth;
`);

// 3. 按住左键往下拖,看能不能滚到顶部
await ev(`
  img.dispatchEvent(new MouseEvent('mousedown', {clientX: cx, clientY: cy, button: 0, bubbles: true}));
  for (var i = 1; i <= 30; i++)
    document.dispatchEvent(new MouseEvent('mousemove', {clientX: cx, clientY: cy + i*30, bubbles: true}));
  document.dispatchEvent(new MouseEvent('mouseup', {bubbles: true}));
  body.scrollTop;   // 应该是 0
`);

改完之后我自己站上的实测结果:

检查项 改之前 改之后
适应屏幕宽度 409px 409px(居中不变)
滚轮放大 4 格 只能跳到 1:1 798px,显示 205%
放大后能否滚到顶部 不行 可以,scrollTop 归 0
800% 时图片宽度 1920px(卡在原始尺寸) 3108px
四边可达性 左边 -240px,滚不到 四边全部可达
灯箱滚轮放大后按住左键拖动查看的实拍截图
放大到 205% 后按住左键拖动:顶部提示条实时显示缩放倍数,图片可在容器内自由平移

六 · 小结

回头看,这件事折腾的其实不是 WordPress,是两个一直没被认真对待的细节:图片插进来之后读者要怎么看,以及 CSS 里那两行居中到底在溢出时干了什么。

如果你只想抄最小可用的一套:正文插原图、原图宽度压到 1920 以内转 WebP、灯箱加滚轮缩放和拖动、容器改 flex-start + 图片加 margin:auto 和 flex-shrink:0。做完这四步,读者在你的截图上是能真的看清楚字。

顺带一提,用 nginx 日志自建访问统计 那篇里也有类似的思路——先把数据拿到手,再花力气把它显示清楚。工具是死的,让人看懂才是目的。

上一篇 用 Nginx 访问日志自建网站统计面板
下一篇 AeroCore Pro 首页幻灯片变形:object-fit:fill 的坑和一套 1640×700 Banner 标准