react-okr-tree Logoreact-okr-tree
指南

画布缩放:OkrTreeViewport

缩放与平移的交互契约、受控 zoom / offset、工具栏定制与 exportImage 导出 PNG / SVG。

大树在固定视口里放不下时,用 <OkrTreeViewport> 把树包起来就有缩放与平移。它只做外层变换(transform: translate() scale()),不侵入树本体,也不改变树的任何 API——里面的 OkrTree / OkrTreeGroup 照常配置。

'use client'

import { useMemo, useRef } from 'react'
import {
  OkrTree,
  OkrTreeViewport,
  type OkrTreeViewportHandle,
  type TreeNodeData,
} from 'react-okr-tree'
import 'react-okr-tree/style.css'

export default function App() {
  const vp = useRef<OkrTreeViewportHandle>(null)
  const data = useMemo<TreeNodeData[]>(
    () => [
      {
        id: 1,
        label: '总部',
        children: [
          { id: 2, label: '研发部', children: [{ id: 5, label: '前端组' }] },
          { id: 3, label: '销售部' },
        ],
      },
    ],
    []
  )

  return (
    <>
      <button type="button" onClick={() => void vp.current?.exportImage({ type: 'png', scale: 2 })}>
        导出 PNG
      </button>
      <button type="button" onClick={() => void vp.current?.centerNode(5)}>
        居中到 id=5
      </button>
      <OkrTreeViewport ref={vp} toolbar minZoom={0.2} maxZoom={4} style={{ height: 480 }}>
        <OkrTree data={data} nodeKey="id" direction="horizontal" showCollapsable />
      </OkrTreeViewport>
    </>
  )
}

props

prop说明默认值
minZoom / maxZoom缩放范围,所有缩放路径都受钳制0.2 / 4
zoomStep每次 zoomIn / zoomOut / 滚轮一格的缩放系数(乘除,不是加减)1.2
zoom + onZoomChange受控缩放;未传 zoom 时内部维护。与树的受控策略一致:判据是 undefined,只传值不传回调即锁定
offset + onOffsetChange受控平移偏移 { x, y }
wheelBehaviorctrl-zoom(默认,按住 Ctrl / ⌘ 才缩放,不劫持页面滚动)/ zoom(滚轮始终缩放)/ scroll(从不缩放)ctrl-zoom
toolbar显示内置工具栏false
renderToolbar自定义工具栏内容(对应源项目 #toolbar 插槽);给了它就一定显示工具栏区域,不必再开 toolbar
children / className / style画布内容;根容器类名与样式(高度用 --okr-viewport-heightstyle={{ height }} 设)

方法

方法说明
zoomIn() / zoomOut()以视口中心为锚放大 / 缩小(受 minZoom / maxZoom 钳制)
reset()复位到缩放 1、偏移 0;双击画布同样触发
fitToScreen(padding?)适应窗口:内容完整可见并居中,padding 默认 20(四周各留 20px)
centerNode(data)先展开目标节点的祖先,再把视口中心对准它。参数接受 key / data 对象 / TreeNode,返回 Promise<boolean>
exportImage(options?)导出画布内容为 PNG / SVG 并触发下载,返回 dataURL
getZoom() / getOffset()读当前缩放与偏移

交互契约

这几条是实测出来的行为,改任何一条都会让人觉得「不像一个画布」:

  • 滚轮缩放以指针为锚点,不是以视口中心——光标指着哪个节点,缩放后它还在原地。
  • 拖拽平移有 3px 阈值:位移没超过 3px 不算平移;一旦判定为平移,结束时吞掉随后那一次 click,否则拖完抬手就会误触 onNodeClick
  • 双击复位(缩放 1、偏移 0)。
  • 触控双指捏合用 Pointer Events 统一处理(鼠标与触控同一条路径):第二指点下时从平移切到捏合,抬起回落单指时重新锚定平移起点,不会跳一下。
  • wheelBehavior="scroll"完全不 preventDefault,滚轮交还给页面——嵌入式展示场景需要这个,别让用户在画布上滚不动页面。
  • 平移过程中根容器带 is-panning 类(cursor: grabbing),touch-action: none 保证移动端手势不被浏览器吃掉。

工具栏

内置工具栏内容依次是 / {percent}% / / 重置 / 适应窗口,两个缩放按钮带 aria-label="缩小" / "放大",工具栏区域自己 stopPropagation 双击——在工具栏上连点不会触发画布复位。

自定义用 renderToolbar,作用域参数是 { zoom, zoomIn, zoomOut, reset, fit }

<OkrTreeViewport
  renderToolbar={({ zoom, zoomIn, zoomOut, reset, fit }) => (
    <div style={{ display: 'flex', gap: 8 }}>
      <button type="button" onClick={zoomOut}>

      </button>
      <span>{Math.round(zoom * 100)}%</span>
      <button type="button" onClick={zoomIn}>

      </button>
      <button type="button" onClick={reset}>
        重置
      </button>
      <button type="button" onClick={() => fit(40)}>
        适应窗口
      </button>
    </div>
  )}
>
  <OkrTree data={data} nodeKey="id" direction="horizontal" />
</OkrTreeViewport>

zoom 是每次渲染时的当前值,直接读就行,不需要 useState——受控与否由外层 zoom / onZoomChange 决定。

导出图片

exportImage({ type, scale, background, toPng, toSvg })

选项说明默认
type'png' | 'svg''png'
scale像素密度,映射为 html-to-image 的 pixelRatio2
background背景色,映射为 backgroundColor(透明就不传)
toPng / toSvg直接传入渲染函数,签名与 html-to-image 一致;传了就不再动态导入

导出成功后会建一个 <a download="okr-tree-{时间戳}.{png|svg}"> 触发下载,Promise 以 dataURL 结束。画布还没挂载就调用会抛 exportImage: 画布尚未挂载

html-to-image 是可选 peer,不在 dependencies 里

默认路径是运行时动态 import('html-to-image')。没装它时 exportImage() 抛带安装指引的错(npm i html-to-image)。需要导出能力才装,^1.11.0

某些打包器 / module federation 场景下按裸包名动态导入解析不可靠,此时用 toPng / toSvg 把函数传进来,绕开动态导入:

import { toPng } from 'html-to-image'

vp.current?.exportImage({ type: 'png', scale: 2, toPng })

centerNode 依赖树注册的 getNodeElexpandNode(通过 Context 登记),所以一个画布里放多棵树也能定位;exportImage 截的是画布内容层,OkrTreeGroup 放在画布内也能整组导出。

相关变量

画布外观另有 6 个变量,全部可写在任意祖先:--okr-viewport-height(默认 420px)、--okr-viewport-bg--okr-viewport-border--okr-viewport-radius(默认 4px)、--okr-viewport-toolbar-bg--okr-viewport-toolbar-shadow。见主题与样式定制

clampZoom / computeFit / renderToDataUrl / loadHtmlToImage 这几个内部纯函数也从包入口导出(源项目同样如此),需要自己实现「缩放读数条」或换一套下载交互时可以直接用。

本页目录