react 打造页面可视化搭建 2 - 画布canvas放大缩小与坐标轴 持续更新

3,184 阅读3分钟

效果

缩放与布局自适应

问题:

  1. 画布缩放超过cantainer时需要可以滚动
  2. cantainer自适应的话宽高随时在变 画布是针对整个布局通过transform 缩放,则需要动态设置其left 和top保证画布一直对于container位置不变
<div className="layout">
    <div className="left">left</div>
    <div className="center"> 
      <div className="canvas-container">  
        <div className="canvas">canvas</div>
      </div>
    </div>
    <div className="right">right</div>
  </div>
.layout {
  display: flex;
  justify-content: space-between;
  width: 100%;
  height: 100%;
}
.left,
.right {
  width: 100px;
  height: "100%";
  box-sizing: border-box;
  border: 1px solid red;
}
.center {
  flex: 1;
  overflow: auto;
}
  1. center 宽高自适应 flex: 1 子元素超过center时 可以滚动 scroll:auto
  2. canvas-container 宽高根据缩放值放大缩小
    • 获取父元素center的 width height
    • canvas-container 的 width = width * scale / height = height * scale , 当scale > 1 时候 center 就会被撑开
    • 监听屏幕 resize center大小 改变时 改变 center的 width height
  3. canvas 整体对其缩放
    • 使用 tranform: scale(number) 改变canvas的大小
    • scale会使元素以中心点向两边扩散   需设置left top调整其对于canvas-container 位置不变 left = (scale - 1) / 2 * width top = (scale - 1) / 2 * height

缩放与canvas

效果图

问题

  1. canvas在缩放的过程中 清晰度也会变化 所以需要动态调整画布的width height 还有线条的宽度
  2. 画布的网格大小格子边长的动态设置
  3. 坐标轴设置 (todo 移动时候有辅助线)

代码 不用细讲 感觉贴代码就可以看的很明白了

const CanvasTable: React.FC<IProps> = ({ size, ...props }) => {
  const canvasRef = useRef(null)

  useLayoutEffect(() => {
    if (canvasRef && canvasRef.current && !_.isEmpty(size)) {
      const boldWidth = props.boldWidth
      const normalWidth = props.normalWidth
      const canvas = canvasRef.current as any
      const scale = Number(props.scale)
      canvas.width = size!.width * scale
      canvas.height = size!.height * scale
      const context = canvas.getContext("2d")

      if (context) {
        if (boldWidth) {
          const boldW = boldWidth * scale
          const boldH = boldWidth * scale
          context.lineWidth = 0.5 * scale
          context.strokeStyle = "#555"
          // 绘制粗的线条
          for (let x = 1; x < canvas.width / boldW; x++) {
            context.beginPath()
            context.moveTo(boldW * x, 0)
            context.lineTo(boldW * x, canvas.height)
            context.stroke()
          }
          for (let y = 1; y < canvas.height / boldH; y++) {
            context.beginPath()
            context.moveTo(0, boldH * y)
            context.lineTo(canvas.width, boldH * y)
            context.stroke()
          }
        }

        if (normalWidth) {
          // 绘制细的线条
          const normalW = normalWidth * scale
          const normalH = normalWidth * scale
          context.lineWidth = 0.3 * scale
          context.strokeStyle = "#777"
          for (let x = 1; x < canvas.width / normalW; x++) {
            context.beginPath()
            context.moveTo(normalW * x, 0)
            context.lineTo(normalW * x, canvas.height)
            context.stroke()
          }
          for (let y = 1; y < canvas.height / normalH; y++) {
            context.beginPath()
            context.moveTo(0, normalH * y)
            context.lineTo(canvas.width, normalH * y)
            context.stroke()
          }
        }
      }
    }
    return () => {}
  }, [props.scale, props.boldWidth, props.normalWidth, size])

  if (_.isEmpty(size)) {
    return null
  }
  return (
    <canvas
      style={{
        background: "#fff",
        width: `${Number(props.scale) * size!.width}`,
        height: `${Number(props.scale) * size!.height}`,
      }}
      ref={canvasRef}
      className={props.className}
    >
      {props.children}
    </canvas>
  )
}

坐标轴 效果图

做网格的目的- 自动吸附

在拖拽的过程当中,需要一个刻度来对照移动的偏移量。设置开关,在用户拖拽编辑布局时候开启自动对齐画布线条,自动吸附。可以大大增加对齐效率,不用手动设置元素位置。
代码也很简单 公示一下

const [, drop] = useDrop({
    accept: [ItemTypes.EDIT_SHAPE],
    drop(item: DragItem, monitor) {
      const delta = monitor.getDifferenceFromInitialOffset() as XYCoord

      // 计算scale 偏移量
      const { id } = item
      const originStyle = nodes[id].props.style || {}
      let left = Math.round(
        parseInt(originStyle.left || "0") + delta.x / Number(scale)
      )
      let top = Math.round(
        parseInt(originStyle.top || "0") + delta.y / Number(scale)
      )
      const { snapToGridAfterDrop, boldWidth, normalWidth } = props

      // 自动吸附
      if (snapToGridAfterDrop) {
        const dist = normalWidth || boldWidth
        // 有小格子先计算小格子
        // 没有小格子就算大格子
        left = Math.floor(left / dist) * dist
        top = Math.floor(top / dist) * dist
      }
      updateStyle({
        style: { left: `${left}px`, top: `${top}px` },
        id: item.id,
      })
      return undefined
    },
  })

跳着写这系列博文的原因是 我现在写到这里了 正好记录 这样比较简单 勿吐槽 哈哈

欢迎各位小伙伴点赞+关注呀!!!!笔芯❤️