news 2026/9/28 22:34:36

滚轮缩放图片:基于transform和JavaScript的图片预览实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
滚轮缩放图片:基于transform和JavaScript的图片预览实现

最近好几个前端新手问我同一个需求:做一个图片预览功能,鼠标滚轮一滚,图片就能变大变小。很多人第一反应是给图片加一个 CSS3 的 zoom,结果要么在 Firefox 上没反应,要么缩放之后旁边的布局被挤得乱七八糟。如果你也正卡在这里,别慌,这个需求本质上就一句话:监听滚轮事件,然后对你的图片元素做缩放变换。用 CSS3 的 transform 加几行 JavaScript 就能搞定,而且能躲开 zoom 的不少毛病。

我会从需求拆解开始,把 zoom 和 scale 的区别、滚轮事件的计算、鼠标位置的锚点处理全部讲透,最后给一份可以直接复制改改就用的源码。适合刚接触前端交互的小白,也适合写业务页面写腻了、想回头补一补交互细节的老手。核心思路很简单:鼠标滚轮秒改图大小,关键不在于用哪个属性,而在于不要让浏览器默认的滚轮滚动干扰你。

1. 先别急着写代码,把“缩放”拆清楚

1.1 滚轮缩放的三种典型场景

很多人一说滚轮缩放,脑子里就是“图片编辑器的放大镜”。实际上这个交互在真实项目里出现频率很高,至少有三类场景:

第一种是图片预览组件,比如聊天框里的图片点击放大后,支持滚轮缩放细节;第二种是可视化大屏里的画布,用户要在地图、拓扑图、流程图里缩放查看局部;第三种是电商详情页的局部放大,鼠标悬停在小图上,滚轮调整放大倍数。

这三种场景虽然最终效果都是“滚轮改图片大小”,但实现细节完全不同。图片预览和画布缩放通常是“整体放大缩小”,而局部放大往往要配合遮罩区域。咱这篇文章主要讲第一种和第二种,也就是给一个元素做整体缩放。

如果你只是想在原有页面上让图片变大变小,用 CSS 的 zoom 或 transform: scale() 都可以,但两者行为差别很大,选错会让后续维护很痛苦。

1.2 zoom 与 scale:一对容易混淆的兄弟

CSS 里有两个属性都带“缩放”之意:zoom 和 transform: scale()。很多新手以为它们是同一个东西的两种写法,其实不是。

zoom 是 IE 时代就有的属性,后来 Chrome 和 Edge 一直支持,Firefox 在 126 版本之后也开始支持了,但目前它的标准状态仍然比较暧昧。最要命的是,zoom 会改变元素在正常文档流中占用的空间:图片 zoom: 2 之后,它撑开的布局宽度会变成原来的 2 倍,除非你用绝对定位或 flex 容器把它“罩住”,否则旁边的文字和元素会跟着跑。

transform: scale() 则不同,它只做视觉缩放,元素实际占用的布局空间不变。这就像你用手机拍照后双指放大看照片,照片在屏幕上的“显示大小”变了,但照片文件本身并没有变大,页面结构也不受影响。

我把两者常用的差异整理成一张表:

对比项zoomtransform: scale()
影响文档流会改变元素占位尺寸不影响,占位尺寸不变
动画性能会强制重排,性能较差只触发合成层,性能更优
transform-origin不支持支持,可自由设置缩放原点
对子元素的影响会整体缩放布局仅视觉缩放,子元素布局不变
兼容性现代浏览器陆续支持,但标准未稳所有现代浏览器稳定支持
配合 JS 读取实际尺寸容易混淆 offsetWidth/scrollWidth仍返回原始尺寸,需手动乘 scale

实际开发中,我几乎不用 zoom 做滚轮缩放。它的唯一优势大概是“一行 CSS 就能放大”,但一旦涉及鼠标位置锚定、拖动平移、限制边界,zoom 的布局影响就是个巨坑。

1.3 选型结论:默认选 transform,特殊场景才考虑 zoom

标题里写着“CSS3 Zoom”,但实战中我更推荐用 transform: scale()。你在搜索引擎里搜“CSS3 zoom 缩放”,出来的结果大部分也都在讲 transform,只是因为大家习惯把缩放都叫 Zoom 而已。

什么时候可以考虑 zoom?如果需求是“在非标准环境里快速放大一个绝对定位的浮层”,而且明确不会把动画、鼠标锚点、边界计算做得很复杂,那 zoom 也能凑合。但只要你做的是“可交互的滚轮缩放”,我的建议是直接放弃 zoom,用 transform: scale() 加 translate(),这套组合才是正经做法。

这里多说一句:CSS3 中真正负责“变换”的是 transform 属性,scale() 只是它的一个函数。类似的还有 translate()、rotate()、skew()。滚轮缩放的核心逻辑就是用 scale() 控制大小,用 translate() 控制位置。

2. 读懂滚轮事件和缩放倍率,细节全在这里

2.1 wheel 事件的正确姿势:deltaY 的方向和数值

实现滚轮缩放,必须先监听 wheel 事件。这个事件在所有现代浏览器里都有,它有一个重要属性 deltaY,表示滚轮垂直滚动的距离。

方向要记住:deltaY 为负数,表示手指或滚轮向上滚动,通常对应“放大”;deltaY 为正数,表示向下滚动,通常对应“缩小”。这不是必须的,但符合大多数图片查看器的习惯。

还有一个容易忽略的点:触控板(笔记本触摸板、妙控板)的 deltaY 和实体鼠标滚轮差异极大。实体鼠标滚动一次,deltaY 通常是 100 或 120;触控板轻轻一滑,deltaY 可能是 1 到 30。这导致如果你用固定步长去缩放,触控板上会“慢得让人抓狂”。所以不能写死“每次滚动加 0.1”,而要采用乘法比例因子。

另外,滚轮的 deltaX 和 deltaY 都可能存在,如果你只关心垂直缩放,就只用 deltaY。部分浏览器还有 deltaMode 属性,1 代表按行滚动,2 代表按页滚动,默认 0 代表按像素滚动。绝大多数场景按像素处理即可,不需要为 deltaMode 写特殊分支。

2.2 缩放倍率怎么定?乘法比加法靠谱得多

新手最容易写错的地方在这里:

let scale = 1; img.style.transform = `scale(${scale + 0.1})`;

这样写会出两个问题。第一,放大倍率是线性的,当你已经放大到 10 倍时,再加 0.1 几乎看不出变化;当你还在 1 倍时,0.1 又显得太快。第二,缩小到接近 0 时,0.1 的减法很容易变成负数,图片就“翻面”了。

正确做法是每次乘以一个固定系数:

const factor = event.deltaY > 0 ? 0.98 : 1.02; scale = scale * factor;

为什么是 0.98 和 1.02?因为乘法是对数关系。从 1 倍到 2 倍,需要连续向上滚动约 35 次;从 2 倍到 4 倍,也需要约 35 次。这样手感是均匀的,用户不会觉得“越滚越快”。

如果你觉得 0.98/1.02 太灵敏,可以改成 0.995/1.005,对应的完整公式是:

const step = 0.1; const factor = deltaY > 0 ? 1 - step : 1 + step;

这里 step 是步长,0.1 表示每次滚动缩放 10%,比较接近主流图片查看器的默认手感。

同时一定要设置上下限,比如最小 0.2 倍,最大 8 倍。限制放在乘法之后:

scale = Math.min(Math.max(scale * factor, MIN_SCALE), MAX_SCALE);

这样避免用户把图片缩到看不见,或者放到巨大导致浏览器卡顿。

2.3 transform-origin 为什么决定“手感”

默认情况下,transform: scale() 的缩放原点是元素中心(center center)。简单缩放没问题,但用户自然期望“鼠标指哪里,就围绕哪里缩放”。比如鼠标指着图片左上角,向上滚动后,左上角那一片区域应该始终在鼠标下,而不是图片以中心放大,导致鼠标指的区域跑到别处去。

这就涉及 transform-origin。你可以动态计算鼠标在容器内部的位置,把它设置成缩放原点:

const rect = container.getBoundingClientRect(); const originX = event.clientX - rect.left; const originY = event.clientY - rect.top; img.style.transformOrigin = `${originX}px ${originY}px`; img.style.transform = `scale(${scale})`;

这种方法自由度很高,代码也直观,但在连续滚轮缩放时容易出现“抖动”。原因在于每次滚轮事件里 transform-origin 都在变化,而 transform 变化不是基于当前视觉状态累加计算的。

想要真正顺滑,需要换一种思路:把 transform-origin 固定在左上角(0 0),然后用 translate() 控制位置,用 scale() 控制大小,围绕鼠标点的数学关系重新推导。

3. 手把手写一个可用的滚轮缩放图片组件

3.1 基础版:HTML 结构 + CSS 样式

先做一个最简版本,目标是在一个固定大小的容器里显示一张图片,滚轮缩放,图片不能跑出容器边界。

HTML 结构:

<div id="viewer" class="viewer"> <img id="image" class="viewer-img" src="your-image.jpg" alt="预览图片" /> </div>

CSS 样式:

.viewer { width: 600px; height: 400px; overflow: hidden; position: relative; border: 1px solid #ddd; background: #f5f5f5; cursor: grab; user-select: none; } .viewer-img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; transform-origin: 0 0; will-change: transform; pointer-events: none; }

注意这里 img 设置了 width 100% 和 height 100%,object-fit: contain 让图片按比例完整显示在容器内。为什么还要 transform?因为你要缩放的是图片在容器里“显示出来的大小”,而不是 img 元素本身的基础尺寸。

transform-origin: 0 0 是必须的。后面缩放加定位都基于左上角计算,这样坐标系统统一,不容易乱。

3.2 JS 核心:wheel 监听 + preventDefault + scale 更新

最核心的 JS 代码其实不超过 30 行:

const viewer = document.getElementById('viewer'); const img = document.getElementById('image'); let scale = 1; const MIN_SCALE = 0.2; const MAX_SCALE = 8; viewer.addEventListener('wheel', (event) => { event.preventDefault(); const deltaY = event.deltaY; const step = 0.1; const factor = deltaY > 0 ? 1 - step : 1 + step; const nextScale = Math.min(Math.max(scale * factor, MIN_SCALE), MAX_SCALE); scale = nextScale; img.style.transform = `scale(${scale})`; // 这里先不做位置调整,图片会始终以左上角为缩放中心 }, { passive: false });

这段代码有个问题:图片缩放时永远固定左上角,用户滚轮时无法控制“以鼠标位置为中心”,而且图片放大后左上角会超出容器。所以要加锚点计算。

3.3 进阶版:围绕鼠标位置缩放

为了手感顺滑,推荐采用“translate + scale”方案。原理是:

把 transform-origin 固定在 (0, 0),图片的默认位置是左上角 (0, 0)。鼠标在容器里的位置是 cursorX, cursorY。图片上距离左上角某个点 originX, originY 的元素,缩放后应该仍然保持在鼠标下方。于是图片左上角需要移动到:

const newX = cursorX - originX * nextScale; const newY = cursorY - originY * nextScale;

其中 originX 和 originY 是鼠标相对于图片左上角的坐标。因为图片当前左上角位置就是 translateX 和 translateY,所以:

const originX = (event.clientX - rect.left - currentX) / currentScale; const originY = (event.clientY - rect.top - currentY) / currentScale;

这里 currentX 和 currentY 是当前 translate 的坐标,currentScale 是当前 scale。每次滚轮事件发生后,更新 scale 和 translate。

我把这个逻辑写成一个完整的状态管理:

const viewer = document.getElementById('viewer'); const img = document.getElementById('image'); let scale = 1; let translateX = 0; let translateY = 0; const MIN_SCALE = 0.2; const MAX_SCALE = 8; function updateTransform() { img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; } viewer.addEventListener('wheel', (event) => { event.preventDefault(); const rect = viewer.getBoundingClientRect(); const mouseX = event.clientX - rect.left; const mouseY = event.clientY - rect.top; const originX = (mouseX - translateX) / scale; const originY = (mouseY - translateY) / scale; const step = 0.1; const factor = event.deltaY > 0 ? 1 - step : 1 + step; const nextScale = Math.min(Math.max(scale * factor, MIN_SCALE), MAX_SCALE); translateX = mouseX - originX * nextScale; translateY = mouseY - originY * nextScale; scale = nextScale; updateTransform(); }, { passive: false });

这段代码比“动态改 transform-origin”顺滑得多,因为所有计算连续且基于当前状态,不存在原点跳变问题。

如果需要拖动平移图片,再加三个事件:mousedown 记录开始位置,mousemove 更新 translate,mouseup 结束拖动。注意 mousemove 要绑定在 document 上,防止鼠标移出容器后丢失事件。

3.4 附源码:完整可复制的滚轮缩放 + 拖拽平移示例

这里我整理了一个可直接运行的完整版本,包含 HTML、CSS、JS,注释也写清楚了。复制保存成 .html 文件双击即可预览。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>滚轮缩放图片示例</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #1e1e1e; font-family: Arial, sans-serif; } .viewer { width: 600px; height: 400px; overflow: hidden; position: relative; border: 1px solid #444; background: #2c2c2c; cursor: grab; user-select: none; touch-action: none; border-radius: 8px; } .viewer.dragging { cursor: grabbing; } .viewer-img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; transform-origin: 0 0; will-change: transform; pointer-events: none; } .tip { position: absolute; bottom: 12px; left: 12px; color: #aaa; font-size: 12px; pointer-events: none; z-index: 10; } </style> </head> <body> <div id="viewer" class="viewer"> <img id="image" class="viewer-img" src="https://picsum.photos/id/1011/1200/800" alt="演示图片" /> <div class="tip">滚轮缩放 | 拖拽平移 | 双击复位</div> </div> <script> (function () { const viewer = document.getElementById('viewer'); const img = document.getElementById('image'); let scale = 1; let translateX = 0; let translateY = 0; let isDragging = false; let startX = 0; let startY = 0; const MIN_SCALE = 0.2; const MAX_SCALE = 8; function updateTransform() { img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; } function resetView() { scale = 1; translateX = 0; translateY = 0; updateTransform(); } viewer.addEventListener('wheel', function (event) { event.preventDefault(); const rect = viewer.getBoundingClientRect(); const mouseX = event.clientX - rect.left; const mouseY = event.clientY - rect.top; const originX = (mouseX - translateX) / scale; const originY = (mouseY - translateY) / scale; const step = 0.1; const factor = event.deltaY > 0 ? 1 - step : 1 + step; const nextScale = Math.min(Math.max(scale * factor, MIN_SCALE), MAX_SCALE); translateX = mouseX - originX * nextScale; translateY = mouseY - originY * nextScale; scale = nextScale; updateTransform(); }, { passive: false }); viewer.addEventListener('mousedown', function (event) { isDragging = true; viewer.classList.add('dragging'); startX = event.clientX - translateX; startY = event.clientY - translateY; }); document.addEventListener('mousemove', function (event) { if (!isDragging) return; translateX = event.clientX - startX; translateY = event.clientY - startY; updateTransform(); }); document.addEventListener('mouseup', function () { if (isDragging) { isDragging = false; viewer.classList.remove('dragging'); } }); viewer.addEventListener('dblclick', resetView); })(); </script> </body> </html>

这段源码最核心的价值,是它把滚轮缩放和拖拽平移都放到同一个坐标系里计算。你没有必要硬背公式,但需要理解其中的换算关系:鼠标位置、图片左上角位置、缩放比例三者如何相互推算。掌握了这个,后面做地图缩放、白板缩放,思路都是一模一样的。

4. 避坑指南:实战中必踩的 6 个坑

4.1 忘加 passive: false,页面跟着一起滚

浏览器默认认为 wheel 事件是“passive”的,意思是监听器不打算调用 preventDefault,页面滚动可以照常进行。如果你监听 wheel 后想调用 preventDefault 阻止页面滚动,不写{ passive: false }就不生效,浏览器会忽略你的 preventDefault,然后页面还是会滚动。

正确的监听代码:

viewer.addEventListener('wheel', handler, { passive: false });

如果你用 React 在 JSX 里写 onWheel,它默认不是 passive 模式,但也需要小心,因为 React 的合成事件在某些版本上对 preventDefault 处理不一致。稳妥做法是用原生 addEventListener 加 passive: false。

4.2 不限制缩放边界,图片飞到屏幕外

这是新手最容易踩的坑。忘记设置最小和最大缩放倍数,用户在触控板上多滚两下,图片要么缩成一个点,要么放大到整个浏览器都卡死。

限制一定要放在求 nextScale 后、应用到 transform 前。这样即使滚轮事件触发再频繁,scale 值都不会越界。

还有一个隐藏问题:即使 scale 被限制了,translate 越界也可能发生。当图片放大到 8 倍且往一个方向拖多了,图片会完全移出容器。需要的话,再算一个边界约束,把 translateX 限定在容器宽度和图片缩放后宽度之间。代码里简单版可以先不处理,但你要知道这个坑。

4.3 transform 缩放不影响布局尺寸,读取尺寸要乘 scale

transform: scale() 只改变视觉大小,不改变元素的 clientWidth、offsetWidth 和 layout 占位。这既是它的优点,也是它的坑。

比如你要判断图片是否超出容器,直接拿 img.offsetWidth 拿到的还是原始宽度,不是缩放后的宽度。必须手动乘 scale:

const scaledWidth = img.offsetWidth * scale;

如果你用 zoom 属性,offsetWidth 返回的已经是缩放后的值,反而容易在维护时产生困惑。这也是我推荐 transform 的原因之一,至少你清楚“原始尺寸”从哪来,“视觉尺寸”怎么算。

4.4 触控板滚轮太快,缩放像“跳格”

实体鼠标滚轮一次滚动 deltaY 约 100,触控板一次可能只有 1 到 5。如果你在代码里写死“deltaY 大于 0 就缩小,小于 0 就放大”,触控板用户会感觉缩放特别迟钝。

我的处理方式是不直接用 deltaY 的大小,而是用它的正负号决定方向,并固定缩放比例因子。这样做的好处是,不论鼠标还是触控板,每次滚动事件只影响固定比例,不会出现一次滚动放大三四倍的情况。

如果你想做得更细腻,可以对连续滚动事件做一下节流或合并。比如记录事件时间戳,如果 50 毫秒内连续触发多次,就把 deltaY 累加,达到阈值再缩放。但普通项目用固定比例因子已经足够。

4.5 图片放大后模糊,内存占用飙升

图片是位图,放得越大越模糊,这是物理规律,不是 bug。但你可以提前准备大图资源,用 2 倍图甚至 3 倍图,让用户放大到 8 倍时还能看。如果项目里图片会很大,建议不要直接把原图塞进预览容器,而是用一个降采样后的缩略图先显示,滚轮放大到接近原图尺寸时再替换原图。

内存方面,transform 本身不会额外复制图片。但你要注意,如果图片原始尺寸是 8000 像素宽,浏览器在合成缩放时会消耗较多 GPU 内存。优化方式是给图片加will-change: transform,让它单独成层,避免每次滚动都触发整页重绘。

4.6 坐标系混乱:offsetX 和 clientX 哪个才是对的

很多教程里推荐用 event.offsetX、event.offsetY 计算鼠标相对图片的位置,但这个值在不同浏览器里的表现不一致。更关键的是,当图片本身有 transform 时,offsetX 是相对原始元素的,不会随 scale 变化,计算容易错。

安全做法是统一用 getBoundingClientRect():

const rect = viewer.getBoundingClientRect(); const mouseX = event.clientX - rect.left; const mouseY = event.clientY - rect.top;

这样获取的是鼠标相对于容器的坐标,不受子元素 transform 影响。然后再把当前 translate 和 scale 代入公式,换算出图片内部坐标。

5. 常见问题速查表

问题原因解决方案
滚轮缩放时页面也滚动wheel 监听缺少 passive: false改为 addEventListener('wheel', fn, { passive: false })
图片放大后左上角固定,不能围绕鼠标没有做锚点换算使用 translate + scale 方案,计算 origin
缩放太快或太慢用加法调整倍率改为乘法因子,如 0.98 / 1.02
图片缩到消失或大到卡死没有限制边界使用 Math.min / Math.max 限制 scale
拖动图片后继续缩放会跳变拖拽和缩放没有共用同一套坐标状态维护统一的 translateX / translateY / scale
触控板缩放不灵敏用 deltaY 大小直接控制倍率只取 deltaY 正负,固定缩放因子
图片放大后模糊原图分辨率不够使用高分辨率图片,放大到接近原图时切换大图
双击复位不起作用忘记在 dblclick 里重置 translate 和 scale重置三个状态值并调用 updateTransform

最后分享两个实战心得

滚轮缩放这个功能,代码量不大,但很考验你对浏览器坐标体系和 CSS 变换的理解。我在实际项目里反复踩过不少坑,最深的体会是:把 transform-origin 固定为 0 0,用 translate 控制位置,才是可维护的长期方案。单纯依赖动态 origin 虽然直观,但后续加拖拽、加边界、加动画都会很难受。

第二个心得是,监听器一定要记得在组件卸载时移除。尤其是单页应用里,如果页面切换后旧 Viewer 还挂着 wheel 事件,用户滚动页面就会被意外拦截。封装成一个 class 或自定义指令,在销毁方法里 removeEventListener,能省掉很多线上投诉。

如果你看完这篇,能自己动手把完整源码跑起来,再改成双击复位、限制最大缩放、输出微信小程序版,那这个知识点就彻底变成你的了。代码放这里了,放心拿去抄,踩坑记录也都在上面了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 22:34:25

Docker网络原理与故障排查:从端口映射到跨主机组网

各位老哥&#xff0c;今天想跟大伙聊聊 Docker 网络这块儿。我最早接触 Docker 的时候&#xff0c;以为容器网络就是"跑起来就能访问"&#xff0c;结果第一次部署应用就被上了一课&#xff1a;容器起来了&#xff0c;MySQL 本机连不上&#xff0c;两个容器之间互相 p…

作者头像 李华
网站建设 2026/9/28 22:32:20

基本存储、存储堆栈与NAS的维度错位与选型指南

1. 三个名词的维度错位&#xff1a;它们为什么总被放在一起比较很多刚接触存储的人第一次看到这个标题都会愣一下&#xff1a;这三个概念真的能放在一起对比吗&#xff1f;说实话&#xff0c;我第一次被客户问到“基本存储和网络附加存储到底哪个好”的时候&#xff0c;也差点被…

作者头像 李华
网站建设 2026/9/28 22:32:06

AMC1306隔离Sigma-Delta电流采样与SDFM配置实战指南

1. 从一块AMC1306说起&#xff1a;为什么电机控制里电流采样这么难搞搞电机控制的人都有一个共识&#xff1a;算法写得再漂亮&#xff0c;电流采样一塌糊涂&#xff0c;整个系统就是空中楼阁。我做了七八年电机驱动&#xff0c;从最早的 shunt 电阻加运放方案&#xff0c;到后来…

作者头像 李华
网站建设 2026/9/28 22:31:42

Flutter鸿蒙迁移实战:Button交互适配与防连点方案

上个月我把一套打磨了两年的 Flutter 电商项目往鸿蒙&#xff08;HarmonyOS NEXT&#xff09;迁移&#xff0c;UI 层第一个让我头疼的&#xff0c;居然不是复杂的首页瀑布流&#xff0c;而是整个项目里最不起眼的 Button。同一个页面、同一套代码&#xff0c;Android 上点击反馈…

作者头像 李华
网站建设 2026/9/28 22:31:39

Windows版本查询全攻略:产品名、版本号、构建号一次讲清

前几天一个朋友在群里问&#xff1a;“Windows 版本查询到底怎么查&#xff1f;”结果群里立刻冒出来三四套答案&#xff1a;有人说右键“此电脑”选属性&#xff0c;有人说运行 winver&#xff0c;有人说敲 systeminfo&#xff0c;还有人斩钉截铁地表示必须用命令行。这些答案…

作者头像 李华
网站建设 2026/9/28 22:31:31

CLI-Anything深度实践:从文件管理到AI协作的终端效率革命

最近“CLI-Anything”这个词频繁出现在技术社区和热搜里。它到底会收敛成某个具体开源项目&#xff0c;还是演变成一种泛化的方法论&#xff0c;目前还没有定论。但对我来说&#xff0c;这个词恰好精准地概括了我过去大半年的工作方式&#xff1a;把所有能在终端里完成的事情&a…

作者头像 李华