canvas绘制拓扑图

canvas坐标系统默认为左上角为原点,横轴x轴,向右为正,纵轴为y轴,向下为正的窗口坐标系统,可以对坐标系统坐标变换,平移、缩放、旋转、自定义变换方式等。

mousemove、mouseup和mousedown等事件获取到的是默认窗口坐标系统的事件位置。在获取绘画内容的位置时,需要转换后的坐标系统和默认窗口坐标系统转换计算。

canvas对于画笔样式使用save,restore栈的方式记录。

拓补图支持适配

通过初始化一个_w和_h的值,_w = 375, _h = 318,_unit = 1。监听页面的大小canvas的大小跟随变动,计算_unit的值。

js 复制代码
let _unit = 1;
function updateCanvas() {
  if (!ctx) return;
  const _w = 375;
  const _h = 318;
  if (canvas_w / _w > canvas_h / _h) {
    _unit = canvas_h / _h;
  } else {
    _unit = canvas_w / _w;
  }
  ctx.clearRect(0, 0, canvas_w, canvas_h);
  ctx.scale(_unit, _unit);
 
  ctx && draw(ctx);
}
鼠标在节点上展示手的形状

监听mousemove事件,如果鼠标在节点上显示手状。通过事件e.clientX和e.clientY的值,与节点在坐标轴上的转换值对比。

js 复制代码
function bindEvent() {
  canvas.value!.addEventListener('mousemove',(e: any) => {
    let mouseX = e.clientX;
    let mousY = e.clientY;
    checkMousePos(mouseX, mousY);
  })
}
function checkMousePos(mouseX: number, mouseY: number) {
  for (let i = 0 ; i < imgList.length; i++) {
    let canvasBox = canvas.value!.getBoundingClientRect();
   
    let minx = (imgList[i].x - imgWidth / 2 ) * _unit + canvas_w / 2 + canvasBox.left;
    let maxx = (imgList[i].x  + imgWidth / 2) * _unit + canvas_w / 2 + canvasBox.left;
    let miny = (imgList[i].y - imgWidth / 2)  * _unit + canvas_h / 2 + canvasBox.top ;
    let maxy = (imgList[i].y  + imgWidth / 2) * _unit + canvas_h / 2  + canvasBox.top;
    
    if (mouseX > minx && mouseX < maxx && mouseY > miny && mouseY < maxy) {
      canvas.value &&(canvas.value.style.cursor = 'pointer');
      break
    } else {
      canvas.value &&(canvas.value.style.cursor = 'default');
    }
  }
}
鼠标在节点展示tooltip

原理和节点展示手状相同。

js 复制代码
function checkMousePos(mouseX: number, mouseY: number) {
  for (let i = 0 ; i < imgList.length; i++) {
 
     //....
    
        if (mouseX > minx && mouseX < maxx && mouseY > miny && mouseY < maxy) 
    {
     let x = imgList[i].x + (mouseX - minx);
     let y = imgList[i].y + (mouseY - miny);
      drawToolTip('测试',x, y);
      break
    }
  }
}

vue组件代码:

js 复制代码
    <template>
        <div class="canvas" v-show="true" ref="canvasBox">
          <canvas id="canvas" ref="canvas"></canvas>
        </div>
    </template>
ts 复制代码
    <script>
    import { h, onMounted, onUnmounted, ref } from 'vue';
    
    const canvas = ref<HTMLCanvasElement | null>(null);
    const canvasBox = ref<HTMLElement | null>(null);
    let ctx: CanvasRenderingContext2D | null;
    let canvas_w: number = 0,
      canvas_h: number = 0;
    const imgWidth = 36;
    const imgList: imgList[] = [];
    
    function initCanvas() {
      ctx = canvas.value!.getContext('2d');
      setTimeout(() => {
        resize();
      });

      window.addEventListener('resize', debounceResize);
    }
    const debounceResize = debounce(resize, 500)
    function resize() {
      canvas_w = canvasBox.value!.offsetWidth;
      canvas_h = canvasBox.value!.offsetHeight;
      canvas.value!.width = canvas_w;
      canvas.value!.height = canvas_h;

      updateCanvas();
    }
    let _unit = 1;
    function updateCanvas() {
      if (!ctx) return;
      const _w = 375;
      const _h = 318;
      if (canvas_w / _w > canvas_h / _h) {
        _unit = canvas_h / _h;
      } else {
        _unit = canvas_w / _w;
      }
      ctx.clearRect(0, 0, canvas_w, canvas_h);
      ctx.translate((canvas_w) / 2, (canvas_h) / 2);
      ctx.scale(_unit, _unit);

      ctx && draw(ctx);
      bindEvent();
    }
    function bindEvent() {
      canvas.value!.addEventListener('mousemove',(e: any) => {
        let mouseX = e.clientX;
        let mousY = e.clientY;
        checkMousePos(mouseX, mousY);
      })
    }
    function checkMousePos(mouseX: number, mouseY: number) {
      for (let i = 0 ; i < imgList.length; i++) {
        let canvasBox = canvas.value!.getBoundingClientRect();

        let minx = (imgList[i].x - imgWidth / 2 ) * _unit + canvas_w / 2 + canvasBox.left;
        let maxx = (imgList[i].x  + imgWidth / 2) * _unit + canvas_w / 2 + canvasBox.left;
        let miny = (imgList[i].y - imgWidth / 2)  * _unit + canvas_h / 2 + canvasBox.top ;
        let maxy = (imgList[i].y  + imgWidth / 2) * _unit + canvas_h / 2  + canvasBox.top;

        if (mouseX > minx && mouseX < maxx && mouseY > miny && mouseY < maxy) {
          canvas.value &&(canvas.value.style.cursor = 'pointer');
          // showErrorDetails();
          let x = imgList[i].x + (mouseX - minx);
          let y = imgList[i].y + (mouseY - miny);
          drawToolTip('测试',x, y);
          break
        } else {
          canvas.value &&(canvas.value.style.cursor = 'default');
        }
      }
    }
    function drawToolTip(txtLoc:string, x:number, y:number) {
      if (!ctx) return;
        ctx.save();

        var padding = 3;
        var font = "16px arial";
        ctx.font = font;
        ctx.textBaseline = 'bottom';
        ctx.fillStyle = 'yellow';

        //绘制ToolTip背景
        var width = ctx.measureText(txtLoc).width;
        var height = parseInt(font, 10);
        ctx.fillRect(x, y-height, width+padding*2, height+padding*2);

        //绘制ToolTip文字
        ctx.fillStyle = '#000';
        ctx.fillText(txtLoc, x+padding, y+padding);

        ctx.restore();
    }
    // function showErrorDetails() {
    //   isShowErrorDetails.value = true;
    // }
    function draw(ctx: CanvasRenderingContext2D) {
      ctx.save();
      ctx.fillStyle = 'rgb(16, 16, 20)';
      ctx.arc(0, 0, imgWidth / 2 + 3, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
      imgList.forEach((el: imgList) => {
        ctx.save();
        loadImage(ctx, el);
        ctx.fillStyle = '#fff';
        ctx.font = '11px serif';
        ctx.fillText(
          el.name,
          el.x - ctx.measureText(el.name).width / 2,
          imgWidth / 2 + el.y + 16,
        );
        ctx.restore();
      });
    }
    function loadImage(ctx: CanvasRenderingContext2D, imageInfo: imgList) {
      const img = new Image();
      img.src = imageInfo.state ? imageInfo.src : imageInfo.src_error;
      img.onload = () => {
        ctx.drawImage(
          img,
          imageInfo.x - imgWidth / 2,
          imageInfo.y - imgWidth / 2,
          imgWidth,
          imgWidth,
        );

        ctx.save();
        ctx.beginPath();
        if (imageInfo.id !== 'network') {
          ctx.globalCompositeOperation = 'destination-over';
          ctx.strokeStyle = '#fff';

          if (imageInfo.state) {
            ctx.strokeStyle = '#1296DB';
          } else {
            ctx.strokeStyle = '#DF0B01';
          }
          ctx.lineWidth = 2;
          const info = { x: 0, y: 0 };
          if (imageInfo.x > 0) {
            info.x = -1;
          } else {
            info.x = 1;
          }

          if (imageInfo.y > 0) {
            info.y = -1;
          } else {
            info.y = 1;
          }

          ctx.moveTo(0, 0);
          ctx.lineTo(imageInfo.x, imageInfo.y + (info.y * imgWidth) / 2);
          ctx.stroke();
        }
        ctx.restore();
      };
    }

    onMounted(() => {
      initCanvas();
    });
    onUnmounted(() => {
      controller?.abort();
      window.removeEventListener('resize', resize);
    });
    </script>
相关推荐
wayhome在哪3 小时前
用 fabric.js 搞定电子签名拖拽合成图片
javascript·产品·canvas
德育处主任4 小时前
p5.js 掌握圆锥体 cone
前端·数据可视化·canvas
德育处主任1 天前
p5.js 3D 形状 "预制工厂"——buildGeometry ()
前端·javascript·canvas
德育处主任3 天前
p5.js 3D盒子的基础用法
前端·数据可视化·canvas
掘金安东尼3 天前
2分钟创建一个“不依赖任何外部库”的粒子动画背景
前端·面试·canvas
百万蹄蹄向前冲4 天前
让AI写2D格斗游戏,坏了我成测试了
前端·canvas·trae
用户2519162427116 天前
Canvas之画图板
前端·javascript·canvas
FogLetter9 天前
玩转Canvas:从静态图像到动态动画的奇妙之旅
前端·canvas
用户25191624271110 天前
Canvas之贪吃蛇
前端·javascript·canvas
用户25191624271110 天前
Canvas之粒子烟花
前端·javascript·canvas