canvas实战系列一(Konva实现基本流程图)

一、建设背景

    由于一个项目上需要实现在系统中展现整体流程图,并可以根据不同颜色显示不同节点的完成状态,且节点可以点击进去查看节点详情。具体流程图如下:

二、实现思路

    一开始是想通过原始canvas,封装画圆圈,画矩形,画箭头等方法,然后建立坐标系去实现,后来经过实践,发现图形绘制是可以做到,但是事件处理比较棘手,最后还是放弃使用原生canvas,选择使用canvas库来实现,经过评估选择,最后选择Konva.js库。Konva.js 是一个用于HTML5 Canvas的强大JavaScript库,它使得在网页上创建交互式Canvas应用变得更加简单。它支持各种基础元素的创建,且支持拖拽以及各种鼠标事件的响应处理。

三、Konva.js 基本使用

①、引入Konva.js库:首先,你需要在你的HTML文件中引入Konva.js库。你可以从官方网站下载Konva.js,也可以通过链接引入。

 <script src="https://unpkg.com/konva@4.0.18/konva.min.js"></script>

②、创建Canvas容器:在HTML文件中创建一个容器用于放置Konva.js绘制的图形。

 <div id="container"></div>

③、编写JavaScript代码:使用Konva.js创建和管理图形对象。

      var width = window.innerWidth;
      var height = window.innerHeight;

      var stage = new Konva.Stage({
        container: 'container',
        width: width,
        height: height
      });

      var layer = new Konva.Layer();

      var rect1 = new Konva.Rect({
        x: 20,
        y: 20,
        width: 100,
        height: 50,
        fill: 'green',
        stroke: 'black',
        strokeWidth: 4
      });
      // add the shape to the layer
      layer.add(rect1);

      stage.add(layer);

四、具体实现

①、创建html容器及样式

<style>
  body {
    margin: 0;
    box-sizing: border-box;
    width: 100vw;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .parentBox {
    width: 80%;
    height: 80vh;
    border: 2px solid #aaa;
  }
</style>
 <div class="parentBox" id="parentBox">
   <div id="container"></div>
 </div>

②、封装节点和节点名称方法(可以定义不同颜色参数,文本,以及文本换行行数,文字上下) 

function titleCircle(clayer, x, y, fillColor, title, textWidth, isup, rows) {
  var complexCircle = new Konva.Circle({
    x: x,
    y: y,
    radius: cwidth,
    fill: fillColor,
    stroke: 'black',
    strokeWidth: 1
  })
  complexCircle.name(title)
  var textX = x - textWidth / 2
  var textY = y + 12
  if (isup) {
    textY = y - 14 * (rows + 1)
  }
  var complexText = new Konva.Text({
    x: textX,
    y: textY,
    text: title,
    fontSize: 10,
    fontFamily: 'sans-serif',
    lineHeight: 1.2,
    fill: '#000',
    width: textWidth,
    padding: 4,
    align: 'center'
  })

  clayer.add(complexText).add(complexCircle);
}

③、建立坐标系,绘制元素

var cwidth = 10 //圆点半径
var x1_1 = 250, y1_1 = 150  //以项目获取节点为初始节点
  var y2_1 = y1_1 + 400,
      y3_1 = y2_1 + 400,
      y4_1 = y3_1 + 400;  //定义其他四个y坐标
//定义第一行的所有元素x坐标
var x1_2 = x1_1 + 100,
  x1_3 = x1_2 + 100,
  x1_4 = x1_3 + 100,
  x1_5 = x1_4 + 100,
  x1_6 = x1_5 + 400,
  x1_7 = x1_6 + 200,
  x1_8 = x1_7 + 300,
  x1_9 = x1_8 + 300,
  x1_10 = x1_9 + 550,
  x1_11 = x1_10 + 550,
  x1_12 = x1_11 + 200,
  x1_13 = x1_12 + 100,
  x1_14 = x1_13 + 200,
  x1_15 = x1_14 + 300,
  x1_16 = x1_15 + 100,
  x1_17 = x1_16 + 600,
  x1_18 = x1_17 + 200
//定义整个画布容器
var width = window.innerWidth - 20
var height = window.innerHeight - 20
let parentBox = document.getElementById('parentBox')
let parentBoxHeight = parentBox.clientHeight
let parentBoxWidth = parentBox.clientWidth
var stage = new Konva.Stage({
  container: 'container',
  width: parentBoxWidth,
  height: parentBoxHeight
})
//基础层:分界线及标题
var layer0 = new Konva.Layer()
var rect1 = new Konva.Rect({
  x: x1_1 - 3,
  y: 40,
  width: 6,
  height: 1460,
  fill: '#aaa'
})
var rect2 = new Konva.Rect({
  x: x1_7 - 3,
  y: 40,
  width: 6,
  height: 1460,
  fill: '#aaa'
})
var rect3 = new Konva.Rect({
  x: x1_11 - 103,
  y: 40,
  width: 6,
  height: 1460,
  fill: '#aaa'
})
var rect4 = new Konva.Rect({
  x: x1_16 - 3,
  y: 40,
  width: 6,
  height: 1460,
  fill: '#aaa'
})
var rect5 = new Konva.Rect({
  x: x1_18 - 53,
  y: 40,
  width: 6,
  height: 1460,
  fill: '#aaa'
})
var title1 = new Konva.Text({
  x: x1_1 - 35,
  y: 15,
  text: '项目获取',
  fontSize: 16,
  fontFamily: 'sans-serif',
  fill: '#333'
})
var title2 = new Konva.Text({
  x: x1_7 - 15,
  y: 15,
  text: '开工',
  fontSize: 16,
  fontFamily: 'sans-serif',
  fill: '#333'
})
var title3 = new Konva.Text({
  x: x1_11 - 151,
  y: 15,
  text: '主体结构封顶',
  fontSize: 16,
  fontFamily: 'sans-serif',
  fill: '#333'
})
var title4 = new Konva.Text({
  x: x1_16 - 35,
  y: 15,
  text: '竣工备案',
  fontSize: 16,
  fontFamily: 'sans-serif',
  fill: '#333'
})
var title5 = new Konva.Text({
  x: x1_18 - 65,
  y: 15,
  text: '结算',
  fontSize: 16,
  fontFamily: 'sans-serif',
  fill: '#333'
})
layer0.add(rect1).add(rect2).add(rect3).add(rect4).add(rect5).add(title1).add(title2).add(title3).add(title4).add(title5)
//第一层:第一行元素绘制
var layer1 = new Konva.Layer()
//第一行标题按钮
var rect1_1 = new Konva.Rect({
  x: x1_1 - 150 - 70 / 2,
  y: y1_1 - 30 / 2,
  width: 70,
  height: 30,
  fill: '#fff',
  stroke: '#777',
  strokeWidth: 2
})
var title1_1 = new Konva.Text({
  x: x1_1 - 150 - 16,
  y: y1_1 - 6,
  text: '整 体',
  fontSize: 16,
  fontFamily: 'sans-serif',
  fill: '#333'
})
layer1.add(rect1_1).add(title1_1)
//第一行圆圈
titleCircle(layer1, x1_1, y1_1, '#fff', '项目获取', 80, false, 1)
titleCircle(layer1, x1_2, y1_1, '#fff', '组建项目团队\n(项目启动)', 80, true, 2)
titleCircle(layer1, x1_3, y1_1, '#fff', '建立平台项目\n基本信息', 80, false, 2)
titleCircle(layer1, x1_4, y1_1, '#fff', '取得建设工程\n规划许可证', 80, false, 2)
titleCircle(layer1, x1_5, y1_1, '#ff6d3e', '项目前期评审会\n(启动会)', 86, true, 2)
titleCircle(layer1, x1_6, y1_1, '#3ccdff', '项目交底会', 80, false, 1)
titleCircle(layer1, x1_7, y1_1, '#fff', '取得桩基施\n工许可证', 80, true, 2)
titleCircle(layer1, x1_8, y1_1, '#3ccdff', '优化协调会', 80, false, 1)
titleCircle(layer1, x1_9, y1_1, '#fff', '取得施工许可证', 80, true, 1)
titleCircle(layer1, x1_10, y1_1, '#67ff36', '中期检查一\n(正负零)', 80, true, 2)
titleCircle(layer1, x1_11, y1_1, '#67ff36', '中期检查二\n(结构封顶)', 80, false, 2)
titleCircle(layer1, x1_12, y1_1, '#fff', '项目建安工程款支\n付金额累计达到合同\n金额60%', 110, false, 3)
titleCircle(layer1, x1_13, y1_1, '#3ccdff', '验收启动会', 80, false, 1)
titleCircle(layer1, x1_14, y1_1, '#fff', '五方主体验收', 80, true, 1)
titleCircle(layer1, x1_15, y1_1, '#fff', '消防验收', 80, false, 1)
titleCircle(layer1, x1_16, y1_1, '#fff', '竣备', 80, false, 1)
titleCircle(layer1, x1_17, y1_1, '#fff', '交付', 80, false, 1)
titleCircle(layer1, x1_18, y1_1, '#fff', '项目复盘\n(总结)', 80, true, 2)
//第一行箭头
var arrow1_0 = new Konva.Arrow({
  points: [x1_1 - 115, y1_1, x1_1 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_1 = new Konva.Arrow({
  points: [x1_1 + cwidth, y1_1, x1_2 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_2 = new Konva.Arrow({
  points: [x1_2 + cwidth, y1_1, x1_3 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_3 = new Konva.Arrow({
  points: [x1_3 + cwidth, y1_1, x1_4 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_4 = new Konva.Arrow({
  points: [x1_4 + cwidth, y1_1, x1_5 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_5 = new Konva.Arrow({
  points: [x1_5 + cwidth, y1_1, x1_6 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_6 = new Konva.Arrow({
  points: [x1_6 + cwidth, y1_1, x1_7 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_7 = new Konva.Arrow({
  points: [x1_7 + cwidth, y1_1, x1_8 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_8 = new Konva.Arrow({
  points: [x1_8 + cwidth, y1_1, x1_9 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_9 = new Konva.Arrow({
  points: [x1_9 + cwidth, y1_1, x1_10 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_10 = new Konva.Arrow({
  points: [x1_10 + cwidth, y1_1, x1_11 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_11 = new Konva.Arrow({
  points: [x1_11 + cwidth, y1_1, x1_12 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_12 = new Konva.Arrow({
  points: [x1_12 + cwidth, y1_1, x1_13 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_13 = new Konva.Arrow({
  points: [x1_13 + cwidth, y1_1, x1_14 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_14 = new Konva.Arrow({
  points: [x1_14 + cwidth, y1_1, x1_15 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_15 = new Konva.Arrow({
  points: [x1_15 + cwidth, y1_1, x1_16 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_16 = new Konva.Arrow({
  points: [x1_16 + cwidth, y1_1, x1_17 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})
var arrow1_17 = new Konva.Arrow({
  points: [x1_17 + cwidth, y1_1, x1_18 - cwidth - 2, y1_1],
  fill: 'black',
  stroke: 'black',
  strokeWidth: 1
})

layer1.add(arrow1_0).add(arrow1_1).add(arrow1_2).add(arrow1_3).add(arrow1_4)
  .add(arrow1_5).add(arrow1_6).add(arrow1_7).add(arrow1_8).add(arrow1_9)
  .add(arrow1_10).add(arrow1_11).add(arrow1_12).add(arrow1_13).add(arrow1_14)
  .add(arrow1_15).add(arrow1_16).add(arrow1_17);
//第二行元素绘制
var layer2 = new Konva.Layer();
//......
//第三行元素绘制
var layer3 = new Konva.Layer();
//......
//第四行元素绘制
var layer4 = new Konva.Layer();
//......
//第五行元素绘制
var layer5 = new Konva.Layer();
//......
stage.add(layer0).add(layer1).add(layer2).add(layer3).add(layer4).add(layer5)
stage.draw();

④、添加鼠标事件

 titleCircle 方法中添加提示层以及点击事件

var tooltip = new Konva.Label({
  x: x,
  y: y - 10,
  opacity: 0.75,
  visible: false
})

tooltip.add(
  new Konva.Tag({
    fill: 'black',
    pointerDirection: 'down',
    pointerWidth: 10,
    pointerHeight: 10,
    lineJoin: 'round',
    shadowColor: 'black',
    shadowBlur: 10,
    shadowOffsetX: 6,
    shadowOffsetY: 6,
    shadowOpacity: 0.5,
    cornerRadius: 4
  })
)

tooltip.add(
  new Konva.Text({
    text: title,
    fontFamily: 'Calibri',
    fontSize: 16,
    padding: 8,
    fill: 'white'
  })
)

complexCircle.on('mouseenter', function () {
  // 添加阴影
  complexCircle.setShadowColor('black')
  complexCircle.setShadowBlur(5)
  complexCircle.setShadowOpacity(0.5)
  complexCircle.setShadowOffsetY(3)
  complexCircle.setShadowOffset(3)
  stage.container().style.cursor = 'pointer'
  tooltip.visible(true)
  clayer.draw()
})
complexCircle.on('mouseleave', function () {
  stage.container().style.cursor = 'default'
  complexCircle.setShadowColor()
  complexCircle.setShadowBlur(0)
  complexCircle.setShadowOpacity(0)
  complexCircle.setShadowOffsetY(0)
  complexCircle.setShadowOffset(0)
  tooltip.visible(false)
  clayer.draw()
})
complexCircle.on('click', function (evt) {
  alert('点击了节点:' + evt.target.name().replace('\n', ''))
})
clayer.add(complexText).add(complexCircle).add(tooltip)

⑤、添加拖拽缩放处理

var scaleBy = 1.3
stage.on('wheel', e => {
  e.evt.preventDefault()
  var oldScale = stage.scaleX()
  var pointer = stage.getPointerPosition()
  var mousePointTo = {
    x: (pointer.x - stage.x()) / oldScale,
    y: (pointer.y - stage.y()) / oldScale
  }
  var newScale = e.evt.deltaY > 0 ? oldScale / scaleBy : oldScale * scaleBy
  console.log(newScale)
  stage.scale({
    x: newScale,
    y: newScale
  })
  var newPos = {
    x: pointer.x - mousePointTo.x * newScale,
    y: pointer.y - mousePointTo.y * newScale
  }
  stage.position(newPos)
  stage.batchDraw()
})
// 设置缩放 适应父级页面大小
function setScale() {
  let ratio = 5000 / 1600 // 画布原始宽高比例
  // 以宽度为基准,计算出缩放比例,以适应屏幕宽度
  // let scaleX = parentBoxWidth / 5000
  // let scaleHeight = parentBoxWidth / ratio
  // let scaleY = scaleHeight / 1600
  // 以高度为基准,计算出缩放比例,以适应屏幕宽度
  let scaleY = parentBoxHeight / 1600
  let scaleWidth = parentBoxHeight * ratio
  let scaleX = scaleWidth / 5000
  stage.scale({ x: scaleX, y: scaleY })
  stage.batchDraw()
}
setScale()
window.addEventListener('resize', function () {
  parentBoxHeight = parentBox.clientHeight
  parentBoxWidth = parentBox.clientWidth
  stage.width(parentBoxWidth) // 设置宽度
  stage.height(parentBoxHeight) // 设置高度
  stage.draw()
  setScale()
})
stage.draggable(true)

五、实现效果

流程图实现

缩放拖动

鼠标经过节点自动提示

鼠标点击事件

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值