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

发布时间:2026/9/9 20:30:23

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/9/9 20:30:23

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

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

2026/9/9 22:25:38

蒙特卡罗随机纤维生成插件:复合材料RVE建模的实用方案

1. 项目究竟在解决什么问题做复合材料仿真的人应该都遇到过这个困境:想要建立纤维增强复合材料的微观模型,最让人头疼的往往不是有限元求解部分,而是前处理阶段怎么把大量随机分布的纤维“塞”进一个代表性体积单元里。尤其是单向复合材料&am…

2026/9/9 22:25:38

主动调Q固体激光器Matlab仿真:速率方程建模与参数优化指南

简介:主动调Q固体激光器MATLAB仿真文件面向激光物理与光电子方向的初学者及科研人员,用于理解四能级系统粒子数反转、受激辐射及声光调Q的脉冲产生机制。整套资源共2个文件,均为.m脚本,压缩包仅919B,轻量便于快速运行与…

2026/9/9 22:25:38

Vite 插件如何用 transformIndexHtml 转换 HTML?

Vite 插件如何用 transformIndexHtml 转换 HTML? 【免费下载链接】vite Next generation frontend tooling. Its fast! 项目地址: https://gitcode.com/GitHub_Trending/vi/vite 如果你的需求是"在构建或开发过程中程序化地改写 HTML 入口文件"——…

2026/9/9 22:25:38

STM32F407四路电机编码器驱动与PID角度控制实战

简介:面向需要同时管理多路步进电机的嵌入式开发者,这套基于STM32F407VET6的驱动工程实现了四路电机的速度与方向控制,并通过编码器反馈完成闭环角度精确定位,适用于机器人、自动化设备等运动控制场景。压缩包共171个文件&#xf…

2026/9/9 22:20:37

基于灰狼算法的PID参数整定:原理、Matlab实现与仿真分析

拿到“基于灰狼算法的PID参数整定”这个题目时,很多人第一反应都是:灰狼算法和PID,这不是两个世界的东西吗?一个来自仿生智能优化,一个是控制理论里的常青树。但恰恰是这种组合,在课程设计、毕业设计、研究…

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/8 7:15:15

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/9 0:00:48

MHS模型硬件标准:让大模型像调用软件一样控制物理设备

让Claude真正看着显微镜说“这个细胞形态不太对”,或者让大模型自己调一版机械臂的运动轨迹,这事儿听上去已经很接近科幻片了。但你真上手试一次就会发现,模型不缺智商,缺的是一个能插进显微镜、机械臂、激光控制器里的“通用插座…

2026/9/9 0:00:48

AI五大核心方向详解:从机器学习到大模型,零基础转行选哪条?

会有人告诉我,他想转行学AI,但打开招聘网站一看直接傻眼:机器学习、深度学习、自然语言处理、计算机视觉、大模型应用……满屏都是这些词,好像每个都会一点,又好像每个都离自己很远。还有人上来就问“学Python还是学Ja…

2026/9/9 0:00:49

从50行最小循环到生产级AI引擎:工程化改造全解析

直接说干货。这一章我写的不是那种"hello world跑通某个模型"的教程,而是把AI引擎当做一个真正要上线、要被人调用、要扛流量的系统来聊。从最初只有50行的最小循环,到能够承载生产流量的AI引擎,中间差的不是代码量,而是…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

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

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

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