canvas+fabric实现时间刻度尺+长方形数据展示

前言

我们前面实现了时间刻度尺,现在在时间刻度尺里面画一个长方形,长方形里面有数据展示。

效果

实现

1.先实现时间刻度尺

2.鼠标移动、按下事件监听并画出对应效果

3.在刻度尺里面画对应的长方形数据展示

javascript 复制代码
<template>
  <div>
      <canvas id="rulerCanvas" width="1200" height="400"></canvas>

      <div style="margin-top: 20px">
          <el-button type="primary" @click="test0">添加空标记</el-button>
          <el-button type="primary" @click="test1">添加锅</el-button>
          <el-button type="primary" @click="test2">添加用料</el-button>
          <el-button type="primary" @click="test3">添加菜盒</el-button>
          <el-button type="primary" @click="test4">添加结束录制</el-button>
          <el-button type="primary" @click="test5" plain>清空全部</el-button>
      </div>

      <div style="margin-top: 20px">
          <span style="margin-right: 6px">开启鼠标弹框</span><el-switch v-model="value1" />
      </div>

      <el-dialog v-model="dialogVisible" title="添加调料" width="500">
          <div>
              <el-input v-model="input1" style="width: 240px" />
          </div>
          <div style="margin-top: 10px">
              <el-input v-model="input2" style="width: 240px" />
          </div>
          <template #footer>
              <div>
                  <el-button @click="dialogVisible = false">取消</el-button>
                  <el-button type="primary" @click="dialogConfirm">确认</el-button>
              </div>
          </template>
      </el-dialog>
  </div>
</template>
<script setup>
import * as fabric  from 'fabric';
import {ref, onMounted} from 'vue';

const value1 = ref(false);
const dialogVisible = ref(false);
const input1 = ref("测试商品名称");
const input2 = ref("");
const dialogConfirm = () => {
  dialogVisible.value = false;
  console.log("====添加====")
};

let left = 40;

const test0 = () => {
  draw0();
};
const test1 = () => {
  draw1()
};
const test2 = () => {
  draw();
};
const test3 = () => {
  draw();
};
const test4 = () => {
  draw4();
};
const test5 = () => {
  console.log("清空全部");
  // canvas.value.clear();
};

const canvas = ref(null);

onMounted(() => {
  drawRuler();
});


// const onClickItem = ref(false);

const onMouseDown = (options) => {
  if (value1.value) {
    console.log("====onMouseMove====", options);
    let startNumber = options.pointer.x - 40;
    let timeNumber = parseInt(startNumber / 20);
    input2.value = timeToStr(timeNumber);
    dialogVisible.value = true;
  }
};

let movDummyLine = null;
let movDummyLineText = null;


let group = null;
let dummyLineText = null;

// 空标记
const draw0 = () => {

  group = new fabric.Group([], {
    left: left,
    top: 40,
    width: 150,
    height: 400,
    selectable: true,
    lockMovementY: true,
    // 添加以下属性来隐藏边界框和控制点
    hasBorders: false,
    hasControls: false,
    hasRotatingPoint: false,
  });

  left = left + 150 + 20;

  group.on('selected', (e) => {
    console.log('组被选中', e);
  });
  // 为组添加移动事件监听器
  group.on('moving', () => {
    if (group.left >= 40) {
      let startNumber = group.left - 40 + 20;
      let timeNumber = parseInt(startNumber / 20);
      dummyLineText.set('text', timeToStr(timeNumber));
    }
  });


  const rect = new fabric.Rect({
    left: 0,
    top: 0,
    width: 150,
    height: 400,
    fill: '#d8f2ed',
    selectable: false,
    lockMovementY: true,
  }).set({left: group.left - 3, top: group.top});
  group.add(rect);

  // 添加虚线
  const dummyLine = new fabric.Line([0, 800, 1, 0], {
    stroke: 'red',
    strokeDashArray: [5, 5],
    strokeWidth: 1,
    selectable: false,
  }).set({ left: group.left, top: group.top});
  group.add(dummyLine);


  const line = new fabric.Line([0, 800, 2, 0], {
    stroke: '#3cbca3',
    strokeWidth: 2,
    selectable: true,
    lockMovementY: true,
  }).set({ left: group.left, top: group.top });
  group.add(line);

  const titleText = new fabric.Text("空标记", {
    fontSize: 12,
    fill: 'black',
    selectable: false,
    textAlign: 'center',
  }).set({ left: group.left + 4, top: group.top + 4 });
  group.add(titleText);

  // 刻度时间
  dummyLineText = new fabric.Text("00:00", {
    fontSize: 12,
    fill: 'black',
    selectable: false,
    textAlign: 'center',
  }).set({ left: group.left - 10, top: group.top - 20});
  group.add(dummyLineText);

  // 将组添加到画布上
  canvas.value.add(group);

  // 激活组的选择功能(选中该分组)
  // canvas.value.setActiveObject(group);
};

// 锅设置
const draw1 = () => {

  group = new fabric.Group([], {
    left: left,
    top: 220,
    selectable: true,
    lockMovementY: true,
    // 添加以下属性来隐藏边界框和控制点
    hasBorders: false,
    hasControls: false,
    hasRotatingPoint: false,
  });

  left = left + 150 + 20;

  group.on('selected', (e) => {
    console.log('组被选中', e);
  });
  // 为组添加移动事件监听器
  group.on('moving', () => {
    if (group.left >= 40) {
      let startNumber = group.left - 40 + 20;
      let timeNumber = parseInt(startNumber / 20);
      dummyLineText.set('text', timeToStr(timeNumber));
    }
  });



  const rect = new fabric.Rect({
    left: 0,
    top: 0,
    width: 150,
    height: 180,
    fill: '#fff0e7',
    selectable: false,
    lockMovementY: true,
  }).set({left: group.left - 3, top: group.top});
  group.add(rect);

  const line = new fabric.Line([0, 180, 2, 0], {
    stroke: '#fc9153',
    strokeWidth: 2,
    selectable: true,
    lockMovementY: true,
  }).set({ left: group.left, top: group.top });
  group.add(line);

  // 添加虚线
  const dummyLine = new fabric.Line([0, 800, 1, 0], {
    stroke: 'red',
    strokeDashArray: [5, 5],
    strokeWidth: 1,
    selectable: false,
  }).set({ left: group.left, top: group.top});
  group.add(dummyLine);

  const titleText = new fabric.Text("锅设置为xxxx", {
    fontSize: 12,
    fill: 'black',
    selectable: false,
    textAlign: 'center',
  }).set({ left: group.left + 4, top: group.top + 4 });
  group.add(titleText);

  // 创建灰色背景的小矩形
  const textBackground = new fabric.Rect({
    left: group.left + 10,
    top: group.top + 20,
    width: 70,
    height: 35,
    fill: '#fbfbfb',
    selectable: false,
    rx: 4,
  });
  group.add(textBackground);

  const text = new fabric.Text('测试文字', {
    left: group.left + 10,
    top: group.top + 20,
    fontSize: 12,
    fill: 'black',
    textAlign: 'center',
    selectable: false,
  });
  text.set({
    top: text.top + textBackground.height / 2 - text.height / 2,
    left: text.left + textBackground.width / 2 - text.width / 2,
  });
  group.add(text);

  // 刻度时间
  dummyLineText = new fabric.Text("00:00", {
    fontSize: 12,
    fill: 'black',
    selectable: false,
    textAlign: 'center',
  }).set({ left: group.left - 10, top: group.top - 20});
  group.add(dummyLineText);

  // 将组添加到画布上
  canvas.value.add(group);

  // 激活组的选择功能(选中该分组)
  // canvas.value.setActiveObject(group);
};

// 用料、菜盒
const draw = () => {

  group = new fabric.Group([], {
    left: left,
    top: 40,
    width: 150,
    height: 400,
    selectable: true,
    lockMovementY: true,
    // 添加以下属性来隐藏边界框和控制点
    hasBorders: false,
    hasControls: false,
    hasRotatingPoint: false,
  });

  left = left + 150 + 20;

  group.on('selected', (e) => {
    console.log('组被选中', e);
  });
  // 为组添加移动事件监听器
  group.on('moving', () => {
    if (group.left >= 40) {
      let startNumber = group.left - 40 + 20;
      let timeNumber = parseInt(startNumber / 20);
      dummyLineText.set('text', timeToStr(timeNumber));
    }
  });


  const rect = new fabric.Rect({
    left: 0,
    top: 0,
    width: 150,
    height: 400,
    fill: '#e1efff',
    selectable: false,
    lockMovementY: true,
  }).set({left: group.left - 3, top: group.top});
  group.add(rect);

  // 添加虚线
  const dummyLine = new fabric.Line([0, 800, 1, 0], {
    stroke: 'red',
    strokeDashArray: [5, 5],
    strokeWidth: 1,
    selectable: false,
  }).set({ left: group.left, top: group.top});
  group.add(dummyLine);


  const line = new fabric.Line([0, 800, 2, 0], {
    stroke: '#288bfd',
    strokeWidth: 2,
    selectable: true,
    lockMovementY: true,
  }).set({ left: group.left, top: group.top });
  group.add(line);

  const titleText = new fabric.Text("自动投放猪油11g", {
    fontSize: 12,
    fill: 'black',
    selectable: false,
    textAlign: 'center',
  }).set({ left: group.left + 4, top: group.top + 4 });
  group.add(titleText);

  // 创建灰色背景的小矩形
  const textBackground = new fabric.Rect({
    left: group.left + 10,
    top: group.top + 20,
    width: 70,
    height: 35,
    fill: '#fbfbfb',
    selectable: false,
    rx: 4,
  });
  group.add(textBackground);

  const text = new fabric.Text('测试文字', {
    left: group.left + 10,
    top: group.top + 20,
    fontSize: 12,
    fill: 'black',
    textAlign: 'center',
    selectable: false,
  });
  text.set({
    top: text.top + textBackground.height / 2 - text.height / 2,
    left: text.left + textBackground.width / 2 - text.width / 2,
  });
  group.add(text);

  // 刻度时间
  dummyLineText = new fabric.Text("00:00", {
    fontSize: 12,
    fill: 'black',
    selectable: false,
    textAlign: 'center',
  }).set({ left: group.left - 10, top: group.top - 20});
  group.add(dummyLineText);

  // 将组添加到画布上
  canvas.value.add(group);

  // 激活组的选择功能(选中该分组)
  // canvas.value.setActiveObject(group);
};

// 结束录制
const draw4 = () => {

  group = new fabric.Group([], {
    left: 1150,
    top: 40,
    width: 150,
    height: 400,
    selectable: true,
    lockMovementY: true,
    // 添加以下属性来隐藏边界框和控制点
    hasBorders: false,
    hasControls: false,
    hasRotatingPoint: false,
  });

  left = left + 150 + 20;

  group.on('selected', (e) => {
    console.log('组被选中', e);
  });
  // 为组添加移动事件监听器
  group.on('moving', () => {
    if (group.left >= 40) {
      let startNumber = group.left - 40 + 20;
      let timeNumber = parseInt(startNumber / 20);
      dummyLineText.set('text', timeToStr(timeNumber));
    }
  });

  const rect = new fabric.Rect({
    left: 0,
    top: 0,
    width: 50,
    height: 400,
    fill: '#ff4d4f',
    selectable: false,
    lockMovementY: true,
  }).set({left: group.left - 3, top: group.top});
  group.add(rect);

  // 添加虚线
  const dummyLine = new fabric.Line([0, 800, 1, 0], {
    stroke: '#222',
    strokeDashArray: [5, 5],
    strokeWidth: 1,
    selectable: false,
  }).set({ left: group.left, top: group.top});
  group.add(dummyLine);

  const titleText = new fabric.Text("烹\n饪\n结\n束", {
    fontSize: 16,
    fill: '#fff',
    selectable: false,
    textAlign: 'center',
  }).set({ left: group.left + 14, top: 180 });
  group.add(titleText);

  // 刻度时间
  dummyLineText = new fabric.Text("00:00", {
    fontSize: 12,
    fill: 'black',
    selectable: false,
    textAlign: 'center',
  }).set({ left: group.left - 10, top: group.top - 20});
  group.add(dummyLineText);

  // 将组添加到画布上
  canvas.value.add(group);

  // 激活组的选择功能(选中该分组)
  // canvas.value.setActiveObject(group);
};

const onMouseMove = (options) => {
  // console.log("====onMouseMove====", options);
  // console.log("====onMouseMove====", options.pointer.x);

  // let testStr = "00:28";
  // console.log(testStr, "======================", strToTime(testStr));

  if (options.pointer.x >= 40) {
    if (movDummyLine) {
      canvas.value.remove(movDummyLine);
      canvas.value.remove(movDummyLineText);
    }
    // 添加虚线
    movDummyLine = new fabric.Line([0, 800, 1, 0], {
      stroke: 'red',
      strokeDashArray: [5, 5],
      strokeWidth: 1,
      selectable: false,
    }).set({ left: options.pointer.x, top: 36 });
    canvas.value.add(movDummyLine);
    // group.add(movDummyLine);


    // 添加文字  (options.pointer.x)
    let startNumber = options.pointer.x - 40 + 20;
    let timeNumber = parseInt(startNumber / 20);

    movDummyLineText = new fabric.Text(timeToStr(timeNumber), {
      fontSize: 12,
      fill: 'black',
      selectable: false,
      textAlign: 'center',
    }).set({ left: options.pointer.x - 12, top: 20 });
    canvas.value.add(movDummyLineText);
    // group.add(movDummyLineText);

  }

};

const drawRuler = () => {
  canvas.value = new fabric.Canvas('rulerCanvas');

  // 鼠标事件
  canvas.value.on('mouse:move', onMouseMove);
  canvas.value.on('mouse:down', onMouseDown);
  canvas.value.on('mouse:out', () => {
    if (movDummyLine) {
      canvas.value.remove(movDummyLine);
      canvas.value.remove(movDummyLineText);
      movDummyLine = null;
      movDummyLineText = null;
    }
  });

  // 时间刻度
  const startHour = 0;
  const startMinute = 0;
  const intervalMinutes = 5; // 间隔
  const totalHours = 1; // 当前刻度时间
  let currentMinute = startMinute;
  let currentHour = startHour;

  // 长方形
  const rect = new fabric.Rect({
    left: 0,
    top: 0,
    width: 1100,
    height: 40,
    fill: '#fff',
    strokeWidth: 1, // 边框宽度
    selectable: false,
  });
  canvas.value.add(rect);
  // 底部边框
  const bottomBorder = new fabric.Line([0, 40, 1200, 40], {
    stroke: '#000000',
    strokeWidth: 1,
    selectable: false,
  });
  canvas.value.add(bottomBorder);

  // 时间刻度
  for (let i = 0; i <= totalHours * 60; i += intervalMinutes) {
    const x = (i / (totalHours * 60)) *  canvas.value.width + 40;
    const timeText = formatTime(currentHour, currentMinute);

    // 画刻度线
    const b = new fabric.Line([x, 50, x, 60], {
      stroke: 'black',
      strokeWidth: 1,
      selectable: false,
    }).set({ left: x, top: 28 });
    canvas.value.add(b);

    // 添加时间文本
    const a = new fabric.Text(timeText, {
      fontSize: 12,
      fill: 'black',
      selectable: false,
      textAlign: 'center',
    }).set({ left: x-14, top: 10 });
    canvas.value.add(a);

    // 更新分钟和小时
    currentMinute += intervalMinutes;
    if (currentMinute >= 60) {
      currentMinute = 0;
      currentHour++;
    }
  }
};

const formatTime = (hour, minute) => {
  return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
};

// const strToTime = (str) => {
//   let parts = str.split(':');
//   return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
// };

const timeToStr = (seconds) => {
  const minutes = Math.floor(seconds / 60);
  const secs = seconds % 60;
  const paddedMinutes = String(minutes).padStart(2, '0');
  const paddedSeconds = String(secs).padStart(2, '0');
  return `${paddedMinutes}:${paddedSeconds}`;
};
</script>
<style>
  #rulerCanvas {
      border: 1px solid black;
  }
</style>

如果侵权请联系我删除。

相关推荐
灵性(๑>ڡ<)☆11 分钟前
Vue3学习-day2
前端·vue.js·学习
疯狂的沙粒18 分钟前
HTML和CSS相关详解,如何使网页为响应式?
前端·css·html
ss27329 分钟前
2025新年源码免费送
java·前端·javascript·spring boot·后端·html
赵小左1 小时前
浅谈前端vue的自动导入插件unplugin-vue-components
前端·javascript·vue.js
黑客呀1 小时前
网络安全-web渗透环境搭建-BWAPP(基础篇)
前端·安全·web安全
86Eric1 小时前
Vue 中,使用 v-for 和 v-if 在同一个元素上时,出现报错,怎么解决
vue.js·element·计算属性·v-for 优先级
PorkCanteen1 小时前
window.print()预览时表格显示不全
前端·vue.js·elementui
江一铭1 小时前
使用python脚本爬取前端页面上的表格导出为Excel
前端·python·excel
前端啊龙1 小时前
eslint.config.js和.eslintrc.js有什么区别
开发语言·前端·javascript
无法长大1 小时前
el-upload on-preview 扩大预览事件点击范围
前端·javascript·css·vue.js·elementui·vue