2026/10/9 22:30:12

两天 913★ 的“晾衣绳”是怎么想出来的:tendedero 交互点子全拆解

两天 913★ 的“晾衣绳”是怎么想出来的:tendedero 交互点子全拆解 两天 913★ 的“晾衣绳”是怎么想出来的tendedero 交互点子全拆解【免费下载链接】tendederoScreenshots, hung out to dry. A tiny native macOS app that hangs every screenshot on a line at the top of your screen.项目地址: https://gitcode.com/gh_mirrors/te/tendedero2026 年 10 月上旬一个叫 tendedero 的 macOS 小工具在 GitHub 上两天拿下 913 颗星挤进日榜头部。它做的事听起来小到离谱把截图挂到屏幕顶部的一条绳子上。但正是这个小让它在一众截图工具里显得格外扎眼——Snipaste 拼标注、PixPin 拼长截图、Screenshot-to-Code 拼 AI 转代码而 tendedero 只回答一个问题截图拍完之后去哪了这篇文章会从它的名字与隐喻说起拆开顶栏那条悬停即滑落的绳子的完整交互闭环再回到源码层看它如何用不到 20 个源文件、1.7 MB 的体积把不做管理、不做标注、不做历史的极简决策做成了让用户愿意打星的理由。隐喻即文档把理解成本降到零tendedero 在西语里就是晾衣绳。项目的自我描述只有一句话Screenshots, hung out to dry把截图晾起来。这不是一句文案而是整套交互的元模型。截图工具的既有范式是贴纸截图之后图片以浮动缩略图、贴图、剪贴板内容的形式存在。贴纸模型需要用户理解临时与持久的边界——这张图是粘在屏幕上还是进了剪贴板贴完会不会丢而晾衣绳模型是生活经验自带的绳子上的东西看得见、挂得住、随取随用收下来才属于自己丢进垃圾桶才算不要了。README 首页的题眼Out of sight. Within reach.看不见但够得着把这套语义翻译成了产品的价值主张截图不消失也不打扰你它就挂在屏幕边缘待命。这个隐喻不是口号而是被写进了代码的数据模型。在 Line.swift 里每一张截图被建模为一个Pegged结构体——字面意思就是被夹住的东西struct Pegged: Identifiable, Equatable { let id UUID() let url: URL var thumb: NSImage /// Every photo hangs a little crooked, like on a real line. let tilt Double.random(in: -2.5...2.5) var falling false var flying false }注意那个tilt每张图挂上去都会带一个 -2.5° 到 2.5° 的随机倾角。真实晾衣绳上的照片不会整整齐齐这个随机数就是隐喻在数据层的最小体现。文件本身的注释也毫不掩饰设计意图The files never move. The line is only a view onto them.文件从不移动绳子只是它们的一层视图。绳子是看待截图的视角不是存储截图的容器——这个抽象直接决定了后面所有极简决策。一条绳子的完整交互闭环晾衣绳隐喻解决的是认知问题但真正让产品成立的是那条绳子的物理行为。整套交互围绕屏幕顶部一条 210 pt 高的透明面板展开LinePanel.swift而核心交互循环只有三个动作悬停滑落、停留取用、离开收起。悬停滑落。默认状态下绳子藏在菜单栏上沿之外panelHeight 12的负偏移像是 Dock 自动隐藏的效果。鼠标顶到屏幕顶部边缘时AppDelegate 开始计时AppDelegate.swift 里revealDelay 0.25秒注释写得很坦白——短到感觉是即时的又长到让一次快速路过菜单栏不会误触。计时通过一个 30Hz 的Timer轮询光标位置startMouseTracking同时还会先判断目标屏幕是否被全屏应用占用。关键代码在tick()// Resting in the menu bar brings the line down on that screen. // Pushing against the top edge is part of it, and it also works // when another display sits above and the pointer never stops. if let screen screenUnderPointer, inMenuBar, !menuBarSuppressed, !FullScreen.isActive(on: screen) { let since hotZoneSince ?? now hotZoneSince since if now.timeIntervalSince(since) Self.revealDelay { ... reveal() } }细节藏在注释里把顶部边缘定义成菜单栏区域menuBarBand(of:)处理了隐藏菜单栏的边界情况意味着在多显示器纵向堆叠时光标顶着上沿也能触发。绳子从哪块屏滑出取决于光标在哪块屏——用NSScreen.screens.first { NSMouseInRect(mouse, $0.frame, false) }找到指针所在屏因为截图刚拍在哪儿人就在哪儿。停留取用。绳子滑下后点击即复制双击用 Preview 打开长按 0.45 秒唤起系统 Markup 编辑器右击弹上下文菜单拖拽则交给系统级拖放——GrabArea.swift 里的GrabView把每张照片桥接成真正的文件拖拽源。最有心思的是拖放语义的分流拖进 App 是复制一份原图留在绳上拖进文件夹是文件归你照片下绳拖到废纸篓则整张消失。同一个物理动作因为落点不同而语义不同NSDraggingSession的sourceOperationMaskFor用.copy/.move/.delete三个操作掩码实现了这套分流。复制成功时卡片下方还会浮起一个 Copied 玻璃胶囊提示PeggedView.swift1.2 秒后自动消退。离开收起。光标离开绳子区域 0.5 秒retractDelay绳子缩回屏幕顶部之外整个屏幕恢复原状。收起时用的是easeIn(duration: 0.22)滑出时是spring(response: 0.42, dampingFraction: 0.82)——收起更快更果断滑出更活两套曲线在 LineView.swift 的offset动画里分工明确。围绕这套主循环还有三个收尾细节值得注意。一是命中测试的透传面板本身ignoresMouseEvents true只有光标落在照片卡片上hitRects记录的偏好值时才把鼠标事件还给面板updateMousePassThrough保证绳子覆盖的整条屏幕区域对下层应用隐形。二是全屏应用的规避FullScreen.swift 通过调用窗口服务器的私有 CGS API 判断当前 Space 类型全屏视频或演示时绳子绝不出现——注释里写着视频或演示不该有晾衣绳横在顶上。三是菜单栏点击压制任何菜单栏区域的点击都会让绳子立刻收起menuBarSuppressed避免它盖住正在弹出的菜单。飞进绳子的截图和它背后的一整套接管逻辑如果仅仅是把截图文件夹里的图展示出来tendedero 不过是又一个看板。它真正的工程野心体现在两个动作里截图飞上绳子以及接管 macOS 的截图设置。截图飞行。macOS 在截图文件的扩展属性里记录了截图时的屏幕区域com.apple.metadata:kMDItemScreenCaptureGlobalRect。CaptureFlight.swift 用getxattr把这个矩形读出来然后新建一个覆盖全屏的无边框透明面板用 Core Animation 图层让截图从拍摄位置起飞0.65 秒内沿一条最高点 30 pt 的抛物线sin(.pi * k) * arc飞到绳上的落点途中同步完成缩小、倾斜、长出玻璃相框和夹子的形变——update()里对宽度、高度、圆角、inset 逐帧插值夹子clip.frame在落点时刻恰好咬住卡片顶部。降落后浮层淡出真实的 SwiftUI 卡片在其下方接手。也就是说用户看到的是截图从屏幕中央飞起来挂到绳上而不是一张图突然出现在绳子上。丢弃时也有对称的动画卡片从绳上坠落 520 pt、旋转、淡出同样由CaptureFlight.fall在全屏浮层上绘制避免被绳子的窄条裁切。这个飞行还有一个隐性的信息增益它顺带告诉用户截图拍完了、系统处理好了、图片在这儿把 macOS 自带浮动缩略图的那 5 秒等待压缩成了 0.65 秒的视觉确认。接管截图设置。首次启动时应用会弹出一个确认框AppDelegate.swift 的offerInbox问用户是否让 tendedero接管截图。一旦同意Inbox.swift 会做两件事把com.apple.screencapture域的show-thumbnail设为 false关掉浮动缩略图文件立即落盘而不是 5 秒后并把保存位置改成自己的 Application Support 子目录。用户的原始设置会被备份退出或关闭时原样还原。源码里甚至处理了两个极端情况macOS 27 把配置键从location改名成location-screenshot所以两个键都会写万一系统某次忽略了设置、截图还是落到桌面AppDelegate.swift 里的safetyWatcher会同时盯着桌面目录兜底把漏网之鱼也挂上绳子。再配合 ScreenshotWatcher.swift 的目录监听DispatchSource.makeFileSystemObjectSource监听.write/.rename/.delete延后 0.2 秒等 macOS 完成写临时文件再改名整条链路是截图落盘 → 目录事件 → 扫描识别 → 读取扩展属性定位拍摄区域 → 起飞动画 → 挂绳。注意 watcher 里onlyTaggedScreenshots的设计监听桌面时只认带kMDItemIsScreenCapture扩展属性标签的真截图避免把用户桌面上的普通图片误挂上绳而 tendedero 自己的目录里任何图片都算数——毕竟在它自己的地盘上图片都是它自己接管的截图。极简取舍不做标注、不做管理为何反而赢了tendedero 的能力清单短得反常点击复制、长按标注、双击打开、拖拽分发、右上角叉号丢弃外加一个⌃⌥T显示/隐藏的快捷键。没有图库、没有历史列表、没有标注工具栏常驻、没有账号同步。整个交互表只有七行README 的 A gesture for everything. 一节每张卡片的交互密度却极高——一个手势对应一个系统能力绝不重叠。这种克制在代码里体现为三层系统能力复用。长按标注没有自研编辑器而是通过NSSharingService调用系统的 Markup 扩展Markup.swift拿不到时优雅降级到 Preview。双击打开交给NSWorkspace拖放交给 AppKit 原生拖拽会话。tendedero 自己只做调度和视觉不做任何图片编辑功能。零网络零账号。README 的技术规格表里Network access 一栏写的是 None。整个代码库搜索不到任何网络请求数据持久化只有一行UserDefaultsLine.swift 里storeKey pegged存的是文件路径数组。截图文件从未被移动或复制进应用内部——save()存的是url.path重启后按路径重建绳子文件一直在用户的文件夹里。对截图这类敏感资产而言你的截图从不离开你的 MacREADME:Private by design.不是营销话术而是架构事实。从渲染层开始的真实性。极简不等于简陋绳子的物感来自一层层刻意叠加的细节绳子本身用三层描边画出中灰核心 高光 阴影两端用透明度渐隐制造从屏幕外延伸进来的错觉LineView.swift 的Rope每张照片随机倾斜每 7~16 秒会有一阵风让卡片轻轻晃动Line.swift 的scheduleGust注释直言这个细节让它像个物件而不是控件按住卡片会缓慢压扁到 0.95 倍营造长按在蓄力的感觉卡片玻璃相框用系统ultraThinMaterial加顶部高光描边注释还特意说明不做折射让背景保持锐利。值得注意的是README 里的所有视觉素材——hero 图、demo 动图、bento 交互图——都是scripts/make-readme-art.swift用代码渲染出来的连应用图标都是scripts/make-icon.swift画的。一个截图工具它的宣传素材全是程序生成的这本身就是用代码解决问题气质的延伸。回到那个问题913 颗星买的是什么把时间轴拉回 2026 年 10 月 7 日仓库最近一次提交到 10 月 8 日登上日榜913 颗星的增长窗口很短短到不可能是功能积累的产物只能是第一次见面就被记住的结果。人们记住 tendedero不是因为某个杀手功能而是因为一个反直觉的产品决策在截图工具都在做加法的时代它选择了一个隐喻、一条绳子和一套手势然后停下。晾衣绳隐喻把理解成本降到了零——用户不需要读教程、不需要学图库概念晾衣绳上挂着照片这件事任何人都秒懂顶栏一条线 悬停滑落 离开收起构成了一个不需要状态栏图标点击、不需要快捷键记忆的完整闭环而不做标注、不做管理、不做同步的极简取舍反而让每个保留下的交互都足够深拖到不同目的地有不同的语义。README 里那句Same shortcuts. Same muscle memory. Just less mess.快捷键照旧、肌肉记忆照旧只是更干净点出了它的位置它不是替代 Snipaste 的编辑器而是替 macOS 截图流程收拾残局的最后一步。源码目录里 14 个 Swift 文件、1.7 MB 的成品、MIT 许可——所有这些都指向同一件事好的交互点子不一定复杂。一个能自我解释的名字加上一套自洽的物理隐喻有时候就足够让上千人愿意在自己的屏幕上给这条晾衣绳留一条缝。【免费下载链接】tendederoScreenshots, hung out to dry. A tiny native macOS app that hangs every screenshot on a line at the top of your screen.项目地址: https://gitcode.com/gh_mirrors/te/tendedero创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考