background-layer.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import { Layer, observeEntity, PlaygroundConfigEntity, SCALE_WIDTH } from '@flowgram.ai/core';
  2. import { domUtils } from '@flowgram.ai/utils';
  3. interface BackgroundScaleUnit {
  4. realSize: number;
  5. renderSize: number;
  6. zoom: number;
  7. }
  8. const PATTERN_PREFIX = 'gedit-background-pattern-';
  9. const RENDER_SIZE = 20;
  10. const DOT_SIZE = 1;
  11. let id = 0;
  12. export interface BackgroundLayerOptions {
  13. // 预留配置项目
  14. }
  15. /**
  16. * dot 网格背景
  17. */
  18. export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
  19. static type = 'WorkflowBackgroundLayer';
  20. @observeEntity(PlaygroundConfigEntity)
  21. protected playgroundConfigEntity: PlaygroundConfigEntity;
  22. private _patternId = `${PATTERN_PREFIX}${id++}`;
  23. node = domUtils.createDivWithClass('gedit-flow-background-layer');
  24. grid: HTMLElement = document.createElement('div');
  25. /**
  26. * 当前缩放比
  27. */
  28. get zoom(): number {
  29. return this.config.finalScale;
  30. }
  31. onReady() {
  32. const { firstChild } = this.pipelineNode;
  33. // 背景插入到最下边
  34. this.pipelineNode.insertBefore(this.node, firstChild);
  35. // 初始化设置最大 200% 最小 10% 缩放
  36. this.playgroundConfigEntity.updateConfig({
  37. minZoom: 0.1,
  38. maxZoom: 2,
  39. });
  40. // 确保点的位置在线条的下方
  41. this.grid.style.zIndex = '-1';
  42. this.grid.style.position = 'relative';
  43. this.node.appendChild(this.grid);
  44. this.grid.className = 'gedit-grid-svg';
  45. }
  46. /**
  47. * 最小单元格大小
  48. */
  49. getScaleUnit(): BackgroundScaleUnit {
  50. const { zoom } = this;
  51. return {
  52. realSize: RENDER_SIZE, // 一个单元格代表的真实大小
  53. renderSize: Math.round(RENDER_SIZE * zoom * 100) / 100, // 一个单元格渲染的大小值
  54. zoom, // 缩放比
  55. };
  56. }
  57. /**
  58. * 绘制
  59. */
  60. autorun(): void {
  61. const playgroundConfig = this.playgroundConfigEntity.config;
  62. const scaleUnit = this.getScaleUnit();
  63. const mod = scaleUnit.renderSize * 10;
  64. const viewBoxWidth = playgroundConfig.width + mod * 2;
  65. const viewBoxHeight = playgroundConfig.height + mod * 2;
  66. const { scrollX } = playgroundConfig;
  67. const { scrollY } = playgroundConfig;
  68. const scrollXDelta = this.getScrollDelta(scrollX, mod);
  69. const scrollYDelta = this.getScrollDelta(scrollY, mod);
  70. domUtils.setStyle(this.node, {
  71. left: scrollX - SCALE_WIDTH,
  72. top: scrollY - SCALE_WIDTH,
  73. });
  74. this.drawGrid(scaleUnit);
  75. // 设置网格
  76. this.setSVGStyle(this.grid, {
  77. width: viewBoxWidth,
  78. height: viewBoxHeight,
  79. left: SCALE_WIDTH - scrollXDelta - mod,
  80. top: SCALE_WIDTH - scrollYDelta - mod,
  81. });
  82. }
  83. /**
  84. * 绘制网格
  85. */
  86. protected drawGrid(unit: BackgroundScaleUnit): void {
  87. const minor = unit.renderSize;
  88. if (!this.grid) {
  89. return;
  90. }
  91. const patternSize = DOT_SIZE * this.zoom;
  92. const newContent = `
  93. <svg width="100%" height="100%">
  94. <pattern id="${this._patternId}" width="${minor}" height="${minor}" patternUnits="userSpaceOnUse">
  95. <circle
  96. cx="${patternSize}"
  97. cy="${patternSize}"
  98. r="${patternSize}"
  99. stroke="#eceeef"
  100. fill-opacity="0.5"
  101. />
  102. </pattern>
  103. <rect width="100%" height="100%" fill="url(#${this._patternId})"/>
  104. </svg>`;
  105. this.grid.innerHTML = newContent;
  106. }
  107. protected setSVGStyle(
  108. svgElement: HTMLElement | undefined,
  109. style: { width: number; height: number; left: number; top: number },
  110. ): void {
  111. if (!svgElement) {
  112. return;
  113. }
  114. svgElement.style.width = `${style.width}px`;
  115. svgElement.style.height = `${style.height}px`;
  116. svgElement.style.left = `${style.left}px`;
  117. svgElement.style.top = `${style.top}px`;
  118. }
  119. /**
  120. * 获取相对滚动距离
  121. * @param realScroll
  122. * @param mod
  123. */
  124. protected getScrollDelta(realScroll: number, mod: number): number {
  125. // 正向滚动不用补差
  126. if (realScroll >= 0) {
  127. return realScroll % mod;
  128. }
  129. return mod - (Math.abs(realScroll) % mod);
  130. }
  131. }