Konva 从入门到实践 - day1

第 1 天 实操步骤。


准备工作

  • 一个现代浏览器(Chrome/Edge)
  • 一个代码编辑器(VS Code 或记事本均可)
  • 几张设备图标图片(PNG/SVG),暂时可以用占位图代替

为了方便测试,我假设你的图标文件名为 ddj.pngssx2.png,放在与 HTML 同级的 images/ 文件夹下。

如果你手头没有对应图片,可以先使用任意 50x40 尺寸的纯色图片替代。


步骤 1:创建 HTML 文件并引入 Konva

新建一个 index.html,用 CDN 引入 Konva(你也可以下载到本地)。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>WCS 设备布局 - Day1</title>
  <style>
    body {
      margin: 0;
      padding: 20px;
      background: #f0f2f5;
      font-family: sans-serif;
    }
    #container {
      border: 1px solid #ccc;
      background: #fff;
      width: 800px;
      height: 600px;
    }
  </style>
</head>
<body>
  <h2>仓库设备布局 (静态渲染)</h2>
  <div id="container"></div>

  <!-- 引入 Konva -->
  <script src="https://unpkg.com/konva@9/konva.min.js"></script>
  <script>
    // 我们的代码将写在这里
  </script>
</body>
</html>

步骤 2:准备设备布局数据

把我们之前看到的 JSON 整理成一个 JavaScript 对象,放在 <script> 里。

为了让数据更纯粹,我移除了第二个元素里嵌套的 value 业务字段,只保留一个 selected 标志(如果你需要它)。

javascript 复制代码
const layoutData = {
  description: null,
  name: null,
  layout: [
    {
      id: "1782803001807",
      deviceCode: "stacker",
      imgName: "ddj",
      left: 480,
      top: 275,
      width: 50,
      height: 40,
      angle: 0,
      moveLength: 200,
      plcMax: null,
      plcMin: null,
      selected: false
    },
    {
      id: "1782803143726",
      deviceCode: "conveyor",  // 假设这是一个输送线设备
      imgName: "ssx2",
      left: 540,
      top: 240,
      width: 50,
      height: 40,
      angle: 0,
      moveLength: null,
      plcMax: null,
      plcMin: null,
      selected: false
    }
  ]
};

步骤 3:创建 Konva 画布和图层

javascript 复制代码
// 创建舞台
const stage = new Konva.Stage({
  container: 'container',   // 对应 div 的 id
  width: 800,
  height: 600
});

// 创建一个图层
const layer = new Konva.Layer();
stage.add(layer);

步骤 4:编写图片加载与节点创建函数

由于图片加载是异步的,我们需要等待所有图片准备好后再统一绘制。

javascript 复制代码
// 辅助函数:根据设备配置创建 Konva.Image 节点
function createDeviceNode(device) {
  return new Promise((resolve, reject) => {
    const img = new window.Image();
    img.crossOrigin = "anonymous";  // 如果图片在别的域,根据需要设置
    img.onload = () => {
      const imageNode = new Konva.Image({
        id: device.id,
        image: img,
        x: device.left,
        y: device.top,
        width: device.width,
        height: device.height,
        rotation: device.angle,       // Konva 默认旋转中心是图片左上角
        // 如果需要绕中心旋转,可设置 offsetX/offsetY,但这里角度为0,暂不需要
        // 把业务数据也挂到自定义属性上,供后续使用
        deviceCode: device.deviceCode,
        moveLength: device.moveLength,
        selected: device.selected
      });
      resolve(imageNode);
    };
    img.onerror = () => {
      // 如果图片不存在,用一个矩形占位
      console.warn(`图片 ${device.imgName}.png 加载失败,使用占位矩形`);
      const rectNode = new Konva.Rect({
        id: device.id,
        x: device.left,
        y: device.top,
        width: device.width,
        height: device.height,
        fill: '#cccccc',
        stroke: '#333',
        strokeWidth: 1,
        rotation: device.angle
      });
      resolve(rectNode);
    };
    // 假设图片放在 images 文件夹下
    img.src = `images/${device.imgName}.png`;
  });
}

步骤 5:遍历数据并批量渲染

javascript 复制代码
async function renderLayout() {
  // 并发创建所有节点
  const nodes = await Promise.all(
    layoutData.layout.map(device => createDeviceNode(device))
  );

  // 将所有节点添加到图层
  nodes.forEach(node => layer.add(node));

  // 一次性绘制
  layer.batchDraw();
}

// 启动渲染
renderLayout().then(() => {
  console.log('设备布局渲染完成');
  console.log('可通过 stage.findOne("#id") 查找节点');
});

步骤 6:测试与验证

  1. 在项目根目录创建 images 文件夹,放入 ddj.pngssx2.png(你可以先随便找两张图片,大小尽量接近 50x40)。
  2. 用浏览器打开 index.html
  3. 你应该能看到两个设备图标出现在画布右侧(对应 JSON 中的坐标)。
  4. 按 F12 打开控制台,如果图片加载失败,会看到警告,并且画布上会出现灰色矩形作为占位符。

完整代码(可直接运行)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>WCS 设备布局 - Day1 静态渲染</title>
  <style>
    body {
      margin: 0;
      padding: 20px;
      background: #f0f2f5;
      font-family: sans-serif;
    }
    #container {
      border: 1px solid #ccc;
      background: #fff;
      width: 800px;
      height: 600px;
    }
    .info {
      margin-top: 10px;
      font-size: 14px;
      color: #666;
    }
  </style>
</head>
<body>
  <h2>仓库设备布局</h2>
  <div id="container"></div>
  <div class="info">静态渲染完成。打开控制台可查看节点信息。</div>

  <script src="https://unpkg.com/konva@9/konva.min.js"></script>
  <script>
    // 设备布局数据(来自你的 JSON)
    const layoutData = {
      description: null,
      name: null,
      layout: [
        {
          id: "1782803001807",
          deviceCode: "stacker",
          imgName: "ddj",
          left: 480,
          top: 275,
          width: 50,
          height: 40,
          angle: 0,
          moveLength: 200,
          plcMax: null,
          plcMin: null,
          selected: false
        },
        {
          id: "1782803143726",
          deviceCode: "conveyor",
          imgName: "ssx2",
          left: 540,
          top: 240,
          width: 50,
          height: 40,
          angle: 0,
          moveLength: null,
          plcMax: null,
          plcMin: null,
          selected: false
        }
      ]
    };

    // 创建舞台和图层
    const stage = new Konva.Stage({
      container: 'container',
      width: 800,
      height: 600
    });
    const layer = new Konva.Layer();
    stage.add(layer);

    // 根据设备配置创建图像节点的异步函数
    function createDeviceNode(device) {
      return new Promise((resolve) => {
        const img = new window.Image();
        img.onload = () => {
          const imageNode = new Konva.Image({
            id: device.id,
            image: img,
            x: device.left,
            y: device.top,
            width: device.width,
            height: device.height,
            rotation: device.angle,
            // 附加自定义属性
            deviceCode: device.deviceCode,
            moveLength: device.moveLength,
            selected: device.selected
          });
          resolve(imageNode);
        };
        img.onerror = () => {
          console.warn(`图片 ${device.imgName}.png 加载失败,使用占位矩形`);
          const rectNode = new Konva.Rect({
            id: device.id,
            x: device.left,
            y: device.top,
            width: device.width,
            height: device.height,
            fill: '#cccccc',
            stroke: '#333',
            strokeWidth: 1,
            rotation: device.angle,
            deviceCode: device.deviceCode,
            moveLength: device.moveLength,
            selected: device.selected
          });
          resolve(rectNode);
        };
        img.src = `images/${device.imgName}.png`;
      });
    }

    // 渲染所有设备
    async function renderLayout() {
      const nodes = await Promise.all(
        layoutData.layout.map(device => createDeviceNode(device))
      );
      nodes.forEach(node => layer.add(node));
      layer.batchDraw();
      console.log('所有设备节点已添加到图层并绘制');
      console.log('示例:查找堆垛机节点', stage.findOne('#1782803001807'));
    }

    renderLayout();
  </script>
</body>
</html>

第 1 天总结

完成以上步骤后,你已经掌握了:

  • Konva 的 Stage → Layer → Node 结构
  • 如何从 JSON 数据动态创建 图片节点
  • 如何处理异步图片加载
  • x, y, rotation 属性的直接映射
  • 为节点附加自定义业务属性(如 deviceCodemoveLength),为后面的交互做准备

明天我们将在这些节点上添加拖拽和选中高亮 ,让画面可编辑。

如果你在运行过程中遇到任何报错,直接把错误信息发来,我帮你定位。

相关推荐
火星校尉1 小时前
一场数据基建与消费场景的跨界实验
java·前端·数据库·python·php
risc1234562 小时前
Lucene80DocValuesConsumer 五种类型源码阅读顺序
java·服务器·前端
小米渣的逆袭2 小时前
Chrome Extension Script World(ISOLATED / MAIN)原理与适用场景
前端·javascript·chrome
微信开发api-视频号协议2 小时前
Codex++安全边界探秘:从模型能力到风险防御
前端·安全·微信·企业微信
想你依然心痛2 小时前
AtomCode 在前端开发中的实战体验:React + TypeScript 项目开发实录
前端·react.js·typescript
疯狂的魔鬼3 小时前
精确计算容器剩余视口高度:useAutoContainerFullHeight 的工程实践
前端·css·typescript
用户059540174463 小时前
用了 3 个月 ChatGPT,才发现它一直在遗忘——用 Playwright 自动化验证记忆存储一致性
前端·css
玄玄子3 小时前
xss前端解决方案
前端·浏览器·xss
林希_Rachel_傻希希3 小时前
web性能优化之——AI总结视频
前端·javascript·面试