tldraw 打造“悬浮窗口“效果:利用 tick 事件与 setCamera 让画布“钉“在桌面上

发布时间:2026/11/5 10:20:44

tldraw 打造“悬浮窗口“效果:利用 tick 事件与 setCamera 让画布“钉“在桌面上 tldraw 打造悬浮窗口效果利用 tick 事件与 setCamera 让画布钉在桌面上【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. Worlds best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw这篇指南围绕 tldraw 官方示例库中的 floaty-window悬浮窗口示例展开通过读取浏览器窗口的屏幕坐标、并在编辑器每个tick帧用editor.setCamera()做反向补偿实现画布像被钉在桌面、窗口从它上面滑过的视觉效果。读完你将掌握 tldraw 的tick事件订阅时机、相机camera坐标体系与setCamera的底层行为并能把这一轮询式视口同步思路复用到其它需要屏幕坐标驱动相机的场景。效果是什么窗口在动画布纹丝不动拖住浏览器窗口在桌面上移动你会看到 tldraw 画布里的内容并没有跟着窗口一起走——相反它像被钉在了屏幕上窗口在画布上方滑过下面露出的内容始终是同一片区域。这种反直觉的交互 2023 年底曾在社交媒体上流行过一阵子。实现的原理非常朴素窗口移动了多少相机就反向补偿多少。示例在 FloatyExample.tsx 的注释里把这个过程概括为Moving the browser window across the screen pans the camera by the same amount, so the canvas looks like its pinned to the desktop while the window slides over it.由于 README.md 记录的经验试玩时请直接拖动浏览器窗口标题栏——窗口处于最大化状态时该效果不会生效此时screenX/screenY不会随拖动变化。另外注意效果在缩放比例 zoom 为 1 时是精确的缩放后会有偏移原因见下文相机坐标一节。两个基础概念屏幕坐标与 tldraw 相机坐标动手写代码前先分清两套坐标系这是整个技巧成立的关键。window.screenX/window.screenY浏览器窗口左上角在物理屏幕坐标系中的位置单位是 CSS 像素。窗口被拖动时会实时变化最大化时恒定不变且在多显示器场景下可以是负值。tldraw 的camera{ x, y, z }三元组其中x、y表示页面坐标系原点在视口viewport中的偏移z是缩放比。它决定你把世界上的哪一块显示在屏幕上。从源码结构看tldraw 把相机建模为 store 中的一条普通记录id 为camera的instance记录。当你希望屏幕坐标不变时最直接的数学关系就是让相机x、y等于窗口屏幕坐标的负值——窗口向右移 100px相机x就减去 100px画布内容自然保持原位。这正是示例中editor.setCamera({ x: -x, y: -y })的含义页面空间坐标与屏幕坐标在 zoom1 时严格一一对应所以取负是精确的。完整示例代码不到四十行整个示例挂在 FloatyExample.tsx核心逻辑全部由一个渲染为空的 hook 组件完成import { useEffect } from react import { Tldraw, useEditor } from tldraw import tldraw/tldraw.css export default function FloatyExample() { return ( div classNametldraw__editor Tldraw persistenceKeytldraw_floaty_example FloatyWindow / /Tldraw /div ) } function FloatyWindow() { const editor useEditor() useEffect(() { let x window.screenX let y window.screenY function updateCamera() { if (window.screenX x window.screenY y) return x window.screenX y window.screenY editor.setCamera({ x: -x, y: -y }) } editor.on(tick, updateCamera) return () { editor.off(tick, updateCamera) } }, [editor]) return null }值得注意的工程细节FloatyWindow /作为Tldraw的子组件渲染因此内部可以直接调用useEditor()拿到已挂载的editor实例而组件本身return null不产生任何 DOMTldraw上使用persistenceKeytldraw_floaty_example让该示例与其它示例在本地存储中的状态互不干扰依赖数组只写[editor]编辑器实例在整个生命周期内稳定effect 只执行一次注册、一次清理。逐段拆解为什么这样写能钉住画布示例文件底部附有一段带编号的 guide这里按它的三个要点展开。[1] 子组件 useEffect在编辑器就绪后挂接逻辑任何渲染在Tldraw内部的组件都能调用useEditor()。这个 hook 组件在编辑器已经存在之后运行自己的 effect并且不渲染任何内容。这是一个非常实用的 tldraw 模式需要给编辑器加监听器、工具或副作用逻辑时不必侵入Tldraw内部包一个隐形组件即可。[2] 检测窗口位移反向设置相机每次回调开始时先把当前屏幕坐标与上次记录的坐标比较没有变化就直接return避免对相机做无意义的写入。只有检测到位移才更新缓存并调用editor.setCamera({ x: -x, y: -y })取负的原因注释里写得很清楚When they change, the camera is set to the negative of that offset so page space stays fixed relative to the screen. Camera coordinates are page-space, so this is exact at zoom 1.——相机坐标是页面空间坐标所以 zoom1 时这是精确补偿。[3] 为什么用 tick 轮询而不是监听事件浏览器没有提供窗口移动的 DOM 事件window.onresize只在尺寸变化时触发拖动过程中位置变化并不保证触发它因此最直接的兜底方案是高频轮询。示例选择订阅编辑器自身的tick事件editor.on(tick, updateCamera) return () { editor.off(tick, updateCamera) }tick在 tldraw 中默认由 TickManager.ts 驱动它内部以requestAnimationFrame建立帧循环每一帧依次emit(frame, elapsed)与emit(tick, elapsed)elapsed 是距上一帧的毫秒数。也就是说示例在每帧约 60fps都会检查一次窗口位置这个频率足够让窗口滑动、画布静止看起来完全平滑。同时在 effect 的 cleanup 里editor.off(tick, updateCamera)取消订阅——组件卸载后轮询随即停止不会造成监听器泄漏。顺带一提tick并非示例专用通道tldraw 内部大量帧驱动行为如_flushEventsForTick的事件批处理、_animateViewport的相机动画都挂在tick上事件参数类型在 emit-types.ts 中定义为tick: [number]回调会收到距上一帧的毫秒数。深入底层setCamera 到底做了什么setCamera({ x: -x, y: -y })表面只是一行调用底层链路值得展开便于你理解它为何每帧调用也无妨。入口与校验在 Editor.ts 中setCamera走的是移动视口公共路径setCameraOptions同样会以setCamera(getCamera())收尾来即时应用新约束。在写入前会经过getConstrainedCameraEditor.ts 中约 L3365 起当设置了cameraOptions缩放范围、平移约束等时请求的坐标会先被钳制到合法区间。无变化即跳过最终落地的_setCamera首先比较新旧{x, y, z}若三个分量都没变就提前返回Editor.ts 约 L3543。这正好呼应示例中先判断位移、无位移不调用的写法——即便你漏了那层判断每帧重复调用相同坐标也几乎是零成本。事务化写入且不进历史有实际位移时_setCamera通过transact包裹、以{ history: ignore }调用store.put把新相机记录写入 storeEditor.ts 约 L3547-L3554。相机移动因此不会污染撤销/重做历史也不会像普通形状操作那样进入 undo 栈。指针坐标重算相机变化后同一屏幕点对应的页面点随之改变所以_setCamera还会比较并更新当前指针的 page 坐标必要时重新派发指针事件保证拖动等交互在手势进行中也始终正确Editor.ts 约 L3558-L3568。边界条件与局限把示例拿到手跑一遍方法见下节之前先记下三个已知边界这既是原文档的提示也由源码行为佐证最大化窗口无效最大化时screenX/screenY固定不变示例的 early-return 分支会一直命中画布自然也不会补偿。文档与源码注释都明确提示了这一点。仅 zoom1 精确相机坐标是页面空间坐标位移量与屏幕像素的换算系数随z变化。示例只设x/y不动z所以在 zoom ≠ 1 时窗口移动量折算到页面空间的像素数会乘上缩放因子效果会出现漂移。需要全缩放级别精确可自行在setCamera中除以当前z。负坐标是合法的多显示器布局下副屏在左侧时screenX可能为负setCamera({ x: -x })会得到一个正数坐标属于正常情况不需要特判。在本地运行与试玩该示例位于 apps/examplestldraw 的官方示例应用。仓库内示例的编写约定可参考 writing-examples.md。在仓库根目录安装依赖后运行yarn # 启动 examples 开发服务视仓库脚本配置而定一般为 dev/examples 相关命令然后在示例列表的 UI 分类下找到名为Floaty window的条目该 README 前置元数据中title: Floaty window、priority: 30、component: ./FloatyExample.tsx打开后拖动整个浏览器窗口观察画布静止、窗口滑动。若要在调试中理解帧循环可参考 Editor.test.ts 的做法——测试中直接testEditor.emit(tick, 16)手动推一个 tick16ms来驱动依赖帧的批处理逻辑。小结一个可复用的屏幕坐标 → 相机范式floaty-window 示例虽然只有几十行却浓缩了三个可迁移的 tldraw 使用范式渲染为空的子组件 useEditor()useEffect在编辑器生命周期内注入逻辑而不侵入编辑器本体tick事件作为高频轮询时钟任何浏览器没有提供事件、但需要持续同步的状态本例是窗口位置都可以挂在这个每帧触发的通道上并记得在 cleanup 中off相机坐标 页面空间坐标凡是让画面钉住屏幕上某点的需求本质都是计算相机偏移量并调用setCamera底层事务化写入、不入 undo 历史、无位移即跳过等实现细节保证了这种每帧调用是安全高效的。理解了这条链路你就能把窗口滑过画布换成任何你想要的变体——例如把画布内容绑定到某个物理窗口区域、实现多窗口拼接画布或是在 Electron 应用中让编辑器跟随窗体的位置。【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. Worlds best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/11/2 16:57:16

线程池执行流程详解:从核心参数到源码分析

最近被好几个群里的同学问到线程池的执行流程,这个问题确实值得好好讲一讲。作为Java并发编程里出场率最高的组件之一,线程池可以说是一个“你天天用,但未必真懂它”的存在。无论是你把任务直接丢给ExecutorService,还是在Spring里…

2026/11/5 10:18:46

机器学习数据标注中的认知偏差防控与工程实践

1. 数据标注中的认知偏差问题剖析数据标注作为机器学习项目的基础环节,其质量直接影响模型性能。我在参与医疗影像标注项目时,曾遇到标注员将良性肿瘤误标为恶性肿瘤的情况,导致模型召回率虚高。这种系统性偏差往往源于人类认知的固有模式。认…

2026/11/5 10:18:46

VLAN端口类型实验:Access与Trunk数据帧处理详解

1. 实验背景与目标 作为一名网络工程师,我经常需要在实验室环境中验证各种网络协议和配置的实际效果。这次我决定深入研究VLAN端口类型对数据帧处理的影响,因为这是实际网络部署中最基础也最容易出错的部分。 VLAN(虚拟局域网)是…

2026/11/5 10:18:46

VS Code+Docker搭建MySQL开发环境全指南

1. 开发环境配置思路 在VS Code中通过Docker连接MySQL数据库,本质上是在本地搭建一个轻量级的开发环境。这种组合方案特别适合需要频繁切换项目或测试环境的开发者,既能保持本机环境干净,又能快速部署不同版本的数据库服务。 我推荐这个方案…

2026/11/5 10:18:46

音视频转文字工具:技术原理与效率提升实践

1. 为什么我们需要音视频转文字工具?在信息爆炸的时代,音视频内容正以惊人的速度增长。根据行业统计,普通职场人每天平均要处理3小时以上的会议录音、培训视频和电话沟通内容。传统的手动听写方式,不仅效率低下(每分钟…

2026/11/5 10:18:46

B2B智能营销平台架构设计与工业场景实践

1. 项目背景与核心价值 这个智能数字营销平台项目来自一家年营收超10亿的B2B企业,他们主要面向制造业客户提供工业自动化解决方案。传统销售团队每年要拨打数万通电话,但转化率不足2%。我们团队接手后,用9个月时间搭建了一套完整的智能营销系…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑