首页 > 其他分享 >浏览器实现大画布操作

浏览器实现大画布操作

时间:2024-04-10 12:31:59浏览次数:21  
标签:canvas 浏览器 uuid object 画布 item 操作 const

首先这个画布是超出浏览器画布的限制的最大范围的;
需求:在一个大画布上标注 画矩形;还是使用的fablicjs库;可以查看我的另一个文章 详细介绍了使用
fablicjs画矩形和多边形,这篇主要是讲述我完成大画布功能的过程;

准备工作

首先我需要知道各大浏览器对canvas的限制

那么重点来了,考虑怎么完成大画布呢?我采用的方法是分割画布

正式开始

  1. 怎么分割
    因为我的画布是纵向的,所以我只考虑高度分割,宽度撑满容器的宽度即可;
    我们先假设画布90000,每个画布高1000;也就是说我们分割9个画布,先完成canvas的渲染;并且给每一个canvas绑定事件
// 在HTML中创建一个包含分割画布的容器元素,例如:
<div id="canvas-container" />

const canvasWidth = 1000; // 画布宽度
const canvasHeight = 90000; // 画布高度
const blockSize = 1000; // 小块宽度和高度

  // 计算需要分割的小块数
  const numBlocks = Math.ceil(canvasHeight / blockSize);

  // 循环创建和显示小块画布
  private mounted () {
    this.init();
  }

  private init () {
    // 先清空之前的canvas 和数组
    const canvasContainer = document.getElementById('canvas-container') as any;
    canvasContainer.innerHTML = '';
    this.canvases = [];
    // 循环创建和显示小块画布
    for (let i = 0; i < this.numBlocks; i++) {
      const canvasElement = document.createElement('canvas');
      canvasElement.id = `canvas-${i}`;
      canvasElement.width = this.width;
      // 假设不是整数的话 看最后还剩下多少高度
      canvasElement.height = i < this.numBlocks - 1 ? this.blockSize : this.height - this.blockSize * (this.numBlocks - 1);
      const canvasContainer = document.getElementById('canvas-container') as any;

      // 添加每个部分的画布对象到画布容器中
      canvasContainer.appendChild(canvasElement);

      const canvas = new fabric.Canvas(`canvas-${i}`, {
        width: this.width,
        height: i < this.numBlocks - 1 ? this.blockSize : this.height - this.blockSize * (this.numBlocks - 1)
      });
	  // 给canvas绑定事件
      canvas.on('mouse:down', (e) => { this.mouseDown(e, i); });
      canvas.on('mouse:move', (e) => { this.mouseMove(e, i); });
      canvas.on('mouse:up', this.mouseUp);
      canvas.on('selection:updated', e => {
        this.edit(e.target);
      });
      canvas.on('selection:created', e => {
        this.edit(e.target);
      });
      canvas.on('object:modified', this.onChange);
      canvas.on('object:scaling', e => {
        this.onScaling(e.target, i);
      });
      canvas.on('object:moving', e => {
        this.mouseMoving(e.target, i);
      });
      // canvases存放所有的画布
      this.canvases.push(canvas);
    }
  }


// 注意需要在代码中引入 fabric.js 库,并确保正确加载。
`
  1. 我们接下来就可以完成跨画布画矩形了
    在这个步骤中,我们面临的情况比较多
    原本不跨画布->跨画布
    原本不跨画布->跨画布->不跨画布
    从各个方向的绘画

我的解决办法:跨的画布的图形 他们的的高度都是一致的。只是他们的top不一致罢了 同时给他们同一个uuid,用于之后给后端传数据的时候 同一个uuid的数据只需要一个即可;

  private mouseDownRect (position: MousePosition, index: number) {
    if (this.drawingObject) {
      // 点击第二个点的时候
      this.drawEnd(this.drawingObject);
      return;
    }
    // 点击第一个点的时候  记录下来点击的第几个画布
    this.activeCanvas = this.canvases[index];
    const rect = this.drawRect([position.x, position.y, 0, 0]);
    this.drawingObject = rect;
    this.activeCanvas.add(rect);
  }

  // 鼠标移动
  private mouseMove (e, index) {
    if (!this.drawType || !this.drawingObject) {
      return;
    }
    this[`mouseMove${this.drawType}`](this.transformMouse(e), index);
  }

  private mouseMoveRect (position: MousePosition, mouseMoveCanvasIndex: any) {
    if (this.drawingObject) {
      // 从第几个画布开始绘画的
      const startIndex = Number(this.activeCanvas.lowerCanvasEl.id.split('-')[1]);

      // 检查是否跨越相邻画布并绘制矩形  提到上面  因为取消渲染的时候也需要判断是从上到下还是从下到上
      // getCanvasFromCoordinates 是返回当前结束的坐标在第几个画布
      const adjacentCanvas = this.getCanvasFromCoordinates(position.x, position.y + mouseMoveCanvasIndex * this.blockSize);
      // adjacentCanvas 返回null  则不执行下面
      if (!adjacentCanvas) {
        return;
      }
      const adjacentCanvasIndex = Number(adjacentCanvas.lowerCanvasEl.id.split('-')[1]);

	  // 如果一直在跨了画布。看是从上到下 还是从下到上
      const fromTopToBottom = startIndex < adjacentCanvasIndex;
      // 最后一个画布的index不是最开始的index
      if (this.adjacentCanvasIndex > -1 && this.adjacentCanvasIndex !== startIndex) {
        // 从上往下  清除下面的框。从下往上 清楚上面的框【不然每次都会在所跨画布新增多个矩形】
        const clearBottom = startIndex < this.adjacentCanvasIndex;
        // for (let i = startIndex + 1; i < this.adjacentCanvasIndex + 1; i++) {
        for (let i = clearBottom ? (startIndex + 1) : (startIndex - 1);
          clearBottom ? (i < this.adjacentCanvasIndex + 1) : (i > this.adjacentCanvasIndex - 1);
          clearBottom ? i++ : i--) {
          const draweredItem = this.canvases[i].getObjects().filter(x => x.uuid === this.drawingObject.uuid)[0];
          this.canvases[i].remove(draweredItem);
        }
      }

      if (adjacentCanvas && adjacentCanvas !== this.activeCanvas) {
        // for (let i = startIndex + 1; i < adjacentCanvasIndex + 1; i++) {
        for (let i = fromTopToBottom ? startIndex + 1 : startIndex - 1;
          fromTopToBottom ? i < adjacentCanvasIndex + 1 : i > adjacentCanvasIndex - 1;
          fromTopToBottom ? i++ : i--) {
          // 重新绘画第一个画布的矩形  修改高度为图形的总高度
          const width = position.x - this.drawingObject.get('left');
          const height = position.y + (mouseMoveCanvasIndex - startIndex) * this.blockSize - this.drawingObject.get('top');

          this.drawingObject.set({
            width: width,
            height: height,
            adjCanvasNumber: adjacentCanvasIndex - startIndex
          });
          this.activeCanvas.renderAll();

          // 已经渲染过的
          // const draweredItem = this.canvases[i].getObjects().filter(x => x.uuid === this.drawingObject.uuid)[0];
          // this.canvases[i].remove(draweredItem);

	      // 绘制跨画布的矩形 同一个uuid 同时记录他们跨了几个画布adjCanvasNumber;
          const adjObject = this.drawRect([
            this.drawingObject.get('left'),
            -((this.blockSize * (i - startIndex)) - this.drawingObject.get('top')),
            position.x - this.drawingObject.get('left'),
            height],
          {
            uuid: this.drawingObject.uuid,
            adjCanvasNumber: adjacentCanvasIndex - startIndex
          });
          this.canvases[i].add(adjObject);
          // 最后的画布记录下来 如果用户跨画布 但是又取消跨画布 需要清楚所跨的画布的图形
          if (i === adjacentCanvasIndex) {
            this.adjacentCanvasIndex = i;
          }
        }
      } else {
      	// 没有跨画布 则正常处理
        const width = position.x - this.drawingObject.get('left');
        const height = position.y - this.drawingObject.get('top');
        this.drawingObject.set({
          width: width,
          height: height,
          adjCanvasNumber: 0
          // startCanvansIndex: startIndex
        });
        this.activeCanvas.renderAll();
      }
    }
  }

  // 获取包含指定坐标的画布
  private getCanvasFromCoordinates (x, y) {
    for (let i = 0; i < this.canvases.length; i++) {
      const canvas = this.canvases[i];
      const localPoint = canvas.getPointer({ x: x, y: y });
      const canvasElement = canvas.getElement();
      const canvasRect = canvasElement.parentElement.getBoundingClientRect();
      if (
        localPoint.x >= 0 &&
      localPoint.x <= canvasRect.width &&
      localPoint.y >= 0 &&
      localPoint.y <= canvasRect.height
      ) {
        return canvas;
      }
    }
    return null;
  }

  // 画四边形 originTop是用于缩放和移动的时候新增的字段 
  private drawRect (points: Array<number>, others?: OthersConfigModel) {
    const rect = new fabric.Rect({
      type: DrawType.Rect,
      uuid: createUuid(), // todo
      left: points[0],
      top: points[1],
      width: points[2] || 0,
      height: points[3] || 0,
      objectCaching: false,
      transparentCorners: false,
      selectionColor: 'rgba(0,0,0,0)',
      lockRotation: true,
      strokeUniform: true,
      // 移动时候需要 记录他移动了多少 来让同一个矩形uuid也移动
      originLeft: points[0],
      originTop: points[1],
      ...defaultRectStyle,
      ...this.rectStyle,
      ...others
    });
    // eslint-disable-next-line spellcheck/spell-checker
    rect.setControlsVisibility({ mtr: false }); // 隐藏旋转点
    return rect;
  }
  1. 处理缩放+移动
    原本一个画布->缩放跨画布
    原本一个画布->缩放跨画布->取消跨画布
    原本跨画布->缩放为一个画布
    跨多个画布的缩放
    不同方向的缩放

在处理缩放的时候 一开始面临了一个问题 就是假设是从上到下绘画的话 移动上面的矩形就会出现bug

  private onScaling (object, index: number) {
    // 得到所有同一个uuid的图形
    const sameUuidObject = this.getObjectByUuid(object.uuid);
    sameUuidObject.map(item => {
      item.shape.set({
        scaleX: object.scaleX,
        scaleY: object.scaleY,
        left: object.left// 万一用户拉的左侧的,
      });
      this.canvases[item.canvasIndex].renderAll();
    });
    // 跨canvas的时候 防止他缩放的是上面 带来的bug
    this.mouseMoving(object, index);
  }

移动的情况
不能移动出超出范围外;左右不能移动出去,第一个和最后一个画布不能移动到上面/下面

private mouseMoving (object, index) {
    const padding = -1.5; // 内容距离画布的空白宽度,主动设置
    // 限制左右不能移出外面
    object.setCoords();
    const objBoundingBox = object.getBoundingRect();
    if (objBoundingBox.left < padding) {
      object.left = Math.max(object.left, object.left - objBoundingBox.left + padding);
    }
    if (objBoundingBox.left + objBoundingBox.width > object.canvas.width - padding) {
      object.left = Math.min(object.left, object.canvas.width - objBoundingBox.width + object.left - objBoundingBox.left - padding);
    }

    // 如果是第一块  限制不能移到上面
    if (!index && objBoundingBox.top < 0) {
      object.top = Math.max(object.top, object.top - objBoundingBox.top);
    } else if (index === this.numBlocks - 1 && objBoundingBox.top + objBoundingBox.height > object.canvas.height) {
      // 如果是最后一块 不能移动到下面
      object.top = Math.min(object.top, object.canvas.height - objBoundingBox.height + object.top - objBoundingBox.top);
    }

    // 跨框的情况下
    if (object.adjCanvasNumber) {
      const sameUuidObject = this.getObjectByUuid(object.uuid);
      sameUuidObject.map(item => {
        item.shape.left = object.left;
        item.shape.top = item.shape.originTop - (object.originTop - object.top);
        // 防止框一闪一闪的
        item.shape.visible = true;
        this.canvases[item.canvasIndex].renderAll();
        this.canvases[item.canvasIndex].setActiveObject(item.shape);
      });
      // // 原本跨多个canvas 移动到下个画布 会消失
      // if (object.adjCanvasNumber > 1) {
      const adjCanvasNumber = Math.floor((object.top + object.height * object.scaleY) / this.blockSize);
      if (index + adjCanvasNumber > object.adjCanvasNumber + sameUuidObject[0].canvasIndex) {
        const lastRect = sameUuidObject[object.adjCanvasNumber].shape;

        // 如果在同一个画布停顿两次 第一次就会留下
        const draweredItem = this.canvases[index + adjCanvasNumber].getObjects().filter(x => x.uuid === object.uuid)[0];

        if (draweredItem) {
          return;
        }
        lastRect.set({
          originTop: -(this.blockSize - lastRect.originTop)
        });
        const adjObject = this.drawRect([
          object.left,
          -(this.blockSize - lastRect.originTop),
          object.width,
          object.height],
        {
          uuid: object.uuid,
          adjCanvasNumber: object.adjCanvasNumber
        });
        // 设置为同样的缩放比例
        adjObject.set({
          scaleX: object.scaleX,
          scaleY: object.scaleY
        });
        this.canvases[index + adjCanvasNumber].add(adjObject);
        this.canvases[index + adjCanvasNumber].setActiveObject(adjObject);
        // 移动/缩放 导致跨的画布+1了
        sameUuidObject.map(item => {
          item.shape.adjCanvasNumber = object.adjCanvasNumber + 1;
        });
      }
      // 多个画布的向上移动
      if (object.top < 0) {
        const draweredItem = this.canvases[index - 1].getObjects().filter(x => x.uuid === object.uuid)[0];

        if (draweredItem) {
          return;
        }
        const adjObject = this.drawRect([
          object.left,
          this.blockSize + object.originTop,
          object.width,
          object.height],
        {
          uuid: object.uuid,
          adjCanvasNumber: object.adjCanvasNumber
        });
        adjObject.set({
          scaleX: object.scaleX,
          scaleY: object.scaleY,
          // 不然就会出现闪一下
          visible: false
        });
        this.canvases[index - 1].add(adjObject);
      }
      return;
    }

    // 原本不跨canvas 变量adjCanvasNumber变更
    const adjCanvasNumber = Math.floor((object.top + object.height * object.scaleY) / this.blockSize);
    if (adjCanvasNumber) {
      object.adjCanvasNumber = adjCanvasNumber;
      for (let i = index + 1; i < adjCanvasNumber + index + 1; i++) {
        const adjObject = this.drawRect([
          object.left,
          -(this.blockSize - object.originTop),
          object.width,
          object.height],
        // i !== adjCanvasNumber ? this.blockSize + 1 : object.height * object.scaleY],
        {
          uuid: object.uuid,
          adjCanvasNumber: adjCanvasNumber
        });
        adjObject.set({
          scaleX: object.scaleX,
          scaleY: object.scaleY,
          visible: false
        });
        this.canvases[i].add(adjObject);
        // this.canvases[i].setActiveObject(adjObject);
      }
    }

    //  不跨canvas 向上移动  缩放或移动导致跨 其他块向上移动或缩放 成了1;第一块向上移动还是0
    // 用于之前不跨 移动或缩放导致的跨画布处理
    if (object.top < 0) {
      object.adjCanvasNumber = index ? 1 : 0;
    }
  }
  1. 删除多余图形
    鼠标抬起的时候删除多余图形
  private mouseUp (e) {
    this.setCursor();

    if (this[`mouseUp${this.drawType}`] && e.pointer) {
      return;
    }
    if (this.selectObject()) {
      // 删除不在范围内的图形 并且重置他的adjCanvasNumber
      this.deleteExceedShape();
    }
  }

  private deleteExceedShape () {
    this.canvases.map(item => {
      const draweredItem = item.getObjects().filter(x => x.uuid === this.selectObject().uuid)[0];
      if (draweredItem) {
        // 向下超出的被删除
        // 向上超出也要被删除  || draweredItem.top + draweredItem.height * draweredItem.scaleY < 0
        if (draweredItem.top > this.blockSize || draweredItem.top + draweredItem.height * draweredItem.scaleY < 0) {
          // 修改adjCanvasNumber
          const sameUuidObject = this.getObjectByUuid(draweredItem.uuid);
          sameUuidObject.map(sameUuiditem => {
            sameUuiditem.shape.adjCanvasNumber = sameUuiditem.shape.adjCanvasNumber - 1;
          });
          // 删除
          item.remove(draweredItem);
          item.renderAll();
        }
      }
    });
  }
  1. 绘画过程中 鼠标为+字
    在鼠标抬起和鼠标移动过程中设置。canvas提供方法setCursor
  private mouseMove (e, index) {
    // 画的时候使用十字
    this.setCursor();
    ....
  }
  private mouseUp (e) {
    this.setCursor();
    ....
  }


  private setCursor () {
    if (this.drawType) {
      this.canvases.map(item => {
        item.setCursor('crosshair');
      });
    }
  }
  1. 鼠标点击处存在多个矩形,处理点击哪个矩形
    (这个问题其实是之前处理 fabric的遗留问题了,这里顺便记录一下吧!)
    因为矩形也是有面积的,在fabric中他会按照渲染顺序来处理,可以理解为就是和z-index相关,也就意味着有些小的矩形如果在下面,我们鼠标点击的时候是获取不到的
    之前因为矩形不多,所以之前的处理方法是右键点击可以置于顶层或底层,方便他选中自己想要的;
    新的解决方法:按照矩形的面积来判断;
    在这个过程中 出现了一个问题 就是因为存在多个画布 所以必须保证点击的最小面积是在当前这个画布内 所以新增了一个canvasIndex字段
// 鼠标按下
  private mouseDown (e, index) {
    // 如果type不为‘’,则认为正在编辑图形,鼠标点击事件不触发画新图形
    if (!this.drawType) {
      // 设置点击的图形为活跃 从面积来判断
      // 需要多增一个canvasIndex字段 点击的其他位置 在其他画布也有可能有更小面积的图形 所以需要保证画布统一
      const allObjects: any = [];
      this.canvases.map((canvas, canvasIndex) => {
        if (canvas.getObjects().length) {
          canvas.getObjects().map(object => {
            allObjects.push({
              shape: object,
              canvasIndex: canvasIndex
            });
          });
        }
      });

      let smallestRect: any = null;

      const pointer = this.transformMouse(e);

      // 遍历画布上的对象
      allObjects.forEach(function (object) {
        const shape = object.shape;
        const shapeArea = Math.abs(shape.width) * shape.scaleX * shape.scaleY * Math.abs(shape.height);
        const smallestArea = smallestRect ? Math.abs(smallestRect.width) * smallestRect.scaleX * smallestRect.scaleY * Math.abs(smallestRect.height) : 0;
        if (shape.containsPoint(pointer) && object.canvasIndex === index && (smallestRect === null || shapeArea < smallestArea)) {
          smallestRect = shape;
        }
      });

      if (smallestRect !== null) {
        this.setActiveByUuid(smallestRect.uuid);
      }

      this.operateAttribute.map(item => {
        if (this.selectObject() && this.selectObject()[item]) {
          this.selectObject()[item] = !this.rectStyle ? this.selectObject()[item] : this.rectStyle[item];
        }
      });

      // 点击的哪个图形
      this.$emit('clickShape', this.selectObject());
      return;
    }
    // // 防止在画1的里面画2的时候 影响1
    // if (this.selectObject() && !this.drawingObject) {
    //   this.operateAttribute.map(item => {
    //     this.selectObject()[item] = true;
    //   });
    // }
    this[`mouseDown${this.drawType}`](this.transformMouse(e), index);
  }

最后附上全部代码

<template>
  <div
    class="drawer"
    :style="`width: ${width}px; height: ${height}px`"
  >
    <div id="canvas-container" />
    <div class="container">
      <slot />
    </div>
  </div>
</template>
<script lang="ts">
import Vue from 'vue';
import { Component, Prop, Watch } from 'vue-property-decorator';
import { fabric } from 'fabric';
import { createUuid } from '@/utils/uuid';

export const enum DrawType {
  Pointer = '',
  Polygon = 'polygon',
  Rect = 'rectangle',
  Line = 'Line',
}

export interface MousePosition {
  x: number;
  y: number;
}

export interface Shape {
  type: DrawType | string;
  points: Array<MousePosition | number> | number[][];
  content?: number|string;
  others?: OthersConfigModel;
}

export interface RectModel {
  left: number;
  top: number;
  width: number;
  height: number;
  scaleX?: number;
  scaleY?: number;
}

export interface GetShapeByUuidModel {
  shape: any;
  canvasIndex: number;
}

const defaultRectStyle: OthersConfigModel = {
  stroke: 'rgb(0, 232, 8)',
  strokeWidth: 1,
  fill: '',
  opacity: 0.8,
  cornerColor: 'rgb(0, 232, 8)',
  cornerSize: 4,
  selectionLineWidth: 0,
  hasBorders: false
};

export interface OthersModel{
  stroke: string;
  opacity: number;
  uuid: string | null;
  strokeWidth: number;
  fill: string;
  cornerColor: string;
  cornerStyle: string;
  cornerSize: number;
  radius: number;
  selectionLineWidth: number;
  clickIndex: number;
  hasBorders: boolean;
  hasControls: boolean; //  不显示边框点
  selectable: boolean; // 禁止选中当前元素
  lockMovementX: boolean; // 禁止元素移动
  lockMovementY: boolean;
  lockScalingX: boolean; // 禁止元素缩
  lockScalingY: boolean;
  visible: boolean; // 设置元素不可见
  topToRect: number; // 文字到矩形顶部的距离
  leftToRect: number; // 文字到矩形左边的距离
  originX: string; // 旋转x轴 设置文字时候使用
  originY: string; // 旋转y轴 设置文字时候使用
  adjCanvasNumber: number; // 这个图形跨canvas的数字
}

export type OthersConfigModel = Partial<OthersModel>

@Component
export default class ImageMarkDrawer extends Vue {
  @Prop({
    type: Object,
    required: false,
    default: () => {}
  })
  private rectStyle!: {};

  @Prop({
    type: Number,
    required: false,
    default: 780
  })
  private width!: number; // 画布宽度

  @Prop({
    type: Number,
    required: false,
    default: 580
  })
  private height!: number; // 画布高度

  private blockSize = 5000; // 小块宽度和高度
  // 计算需要分割的小块数
  private numBlocks = Math.ceil(this.height / this.blockSize);

  private canvases: any=[]; // canvas数组
  private activeCanvas: any = null;
  // private adjacentCanvas: any = null; // 跨canvas
  private adjacentCanvasIndex = -1; // 跨canvas的最后一个index

  private drawType = ''; // 绘画类型
  private drawingObject: any = null;
  private adjObject: any = null; // 跨canvas的对象
  // private drawingShape: any[] = [];

  private operateAttribute = ['lockMovementX', 'lockMovementY', 'lockScalingX', 'lockScalingY']; // 画框的时候不能放大缩小移动;
  // private canvasObjects: number[] | null = null; // 用户点击置于顶层/底层 导致顺序变化 ids变化 index也变化

  private mounted () {
    this.init();
  }

  private init () {
    // 先清空之前的canvas 和数组
    const canvasContainer = document.getElementById('canvas-container') as any;
    canvasContainer.innerHTML = '';
    this.canvases = [];
    // 循环创建和显示小块画布
    for (let i = 0; i < this.numBlocks; i++) {
      const canvasElement = document.createElement('canvas');
      canvasElement.id = `canvas-${i}`;
      canvasElement.width = this.width;
      canvasElement.height = i < this.numBlocks - 1 ? this.blockSize : this.height - this.blockSize * (this.numBlocks - 1);
      const canvasContainer = document.getElementById('canvas-container') as any;

      // 添加每个部分的画布对象到画布容器中
      canvasContainer.appendChild(canvasElement);

      const canvas = new fabric.Canvas(`canvas-${i}`, {
        width: this.width,
        height: i < this.numBlocks - 1 ? this.blockSize : this.height - this.blockSize * (this.numBlocks - 1)
      });
      canvas.on('mouse:down', (e) => { this.mouseDown(e, i); });
      canvas.on('mouse:move', (e) => { this.mouseMove(e, i); });
      canvas.on('mouse:up', this.mouseUp);
      canvas.on('selection:updated', e => {
        this.edit(e.target);
      });
      canvas.on('selection:created', e => {
        this.edit(e.target);
      });
      canvas.on('object:modified', this.onChange);
      canvas.on('object:scaling', e => {
        this.onScaling(e.target, i);
      });
      canvas.on('object:moving', e => {
        this.mouseMoving(e.target, i);
      });
      this.canvases.push(canvas);
    }
  }

  // 鼠标按下
  private mouseDown (e, index) {
    // 如果type不为line,则认为正在编辑图形,鼠标点击事件不触发画新图形
    if (!this.drawType) {
      // 设置点击的图形为活跃 从面积来判断
      // 需要多增一个canvasIndex字段 点击的其他位置 在其他画布也有可能有更小面积的图形 所以需要保证画布统一
      const allObjects: any = [];
      this.canvases.map((canvas, canvasIndex) => {
        if (canvas.getObjects().length) {
          canvas.getObjects().map(object => {
            allObjects.push({
              shape: object,
              canvasIndex: canvasIndex
            });
          });
        }
      });

      let smallestRect: any = null;

      const pointer = this.transformMouse(e);

      // 遍历画布上的对象
      allObjects.forEach(function (object) {
        const shape = object.shape;
        const shapeArea = Math.abs(shape.width) * shape.scaleX * shape.scaleY * Math.abs(shape.height);
        const smallestArea = smallestRect ? Math.abs(smallestRect.width) * smallestRect.scaleX * smallestRect.scaleY * Math.abs(smallestRect.height) : 0;
        if (shape.containsPoint(pointer) && object.canvasIndex === index && (smallestRect === null || shapeArea < smallestArea)) {
          smallestRect = shape;
        }
      });

      if (smallestRect !== null) {
        this.setActiveByUuid(smallestRect.uuid);
      }

      this.operateAttribute.map(item => {
        if (this.selectObject() && this.selectObject()[item]) {
          this.selectObject()[item] = !this.rectStyle ? this.selectObject()[item] : this.rectStyle[item];
        }
      });

      // 点击的哪个图形
      this.$emit('clickShape', this.selectObject());
      return;
    }
    // // 防止在画1的里面画2的时候 影响1
    // if (this.selectObject() && !this.drawingObject) {
    //   this.operateAttribute.map(item => {
    //     this.selectObject()[item] = true;
    //   });
    // }
    this[`mouseDown${this.drawType}`](this.transformMouse(e), index);
  }

  private mouseDownRect (position: MousePosition, index: number) {
    if (this.drawingObject) {
      this.drawEnd(this.drawingObject);
      return;
    }
    this.activeCanvas = this.canvases[index];
    const rect = this.drawRect([position.x, position.y, 0, 0]);
    this.drawingObject = rect;
    this.activeCanvas.add(rect);
  }

  // 鼠标移动
  private mouseMove (e, index) {
    // 画的时候使用十字
    this.setCursor();

    if (!this.drawType || !this.drawingObject) {
      return;
    }
    this[`mouseMove${this.drawType}`](this.transformMouse(e), index);
  }

  private mouseMoveRect (position: MousePosition, mouseMoveCanvasIndex: any) {
    if (this.drawingObject) {
      const startIndex = Number(this.activeCanvas.lowerCanvasEl.id.split('-')[1]);

      // 检查是否跨越相邻画布并绘制矩形  提到上面  因为取消渲染的时候也需要判断是从上到下还是从下到上
      const adjacentCanvas = this.getCanvasFromCoordinates(position.x, position.y + mouseMoveCanvasIndex * this.blockSize);
      if (!adjacentCanvas) {
        return;
      }
      const adjacentCanvasIndex = Number(adjacentCanvas.lowerCanvasEl.id.split('-')[1]);

      const fromTopToBottom = startIndex < adjacentCanvasIndex;
      // 最后一个画布的index不是最开始的index
      if (this.adjacentCanvasIndex > -1 && this.adjacentCanvasIndex !== startIndex) {
        // 从上往下  清除下面的框
        const clearBottom = startIndex < this.adjacentCanvasIndex;
        // for (let i = startIndex + 1; i < this.adjacentCanvasIndex + 1; i++) {
        for (let i = clearBottom ? (startIndex + 1) : (startIndex - 1);
          clearBottom ? (i < this.adjacentCanvasIndex + 1) : (i > this.adjacentCanvasIndex - 1);
          clearBottom ? i++ : i--) {
          const draweredItem = this.canvases[i].getObjects().filter(x => x.uuid === this.drawingObject.uuid)[0];
          this.canvases[i].remove(draweredItem);
        }
      }

      if (adjacentCanvas && adjacentCanvas !== this.activeCanvas) {
        // for (let i = startIndex + 1; i < adjacentCanvasIndex + 1; i++) {
        for (let i = fromTopToBottom ? startIndex + 1 : startIndex - 1;
          fromTopToBottom ? i < adjacentCanvasIndex + 1 : i > adjacentCanvasIndex - 1;
          fromTopToBottom ? i++ : i--) {
          // 重新绘画开始的矩形 具体是下面这种这样方式  还是说高度+1 将它隐藏 待定
          const width = position.x - this.drawingObject.get('left');
          const height = position.y + (mouseMoveCanvasIndex - startIndex) * this.blockSize - this.drawingObject.get('top');

          this.drawingObject.set({
            width: width,
            height: height,
            adjCanvasNumber: adjacentCanvasIndex - startIndex
          });
          this.activeCanvas.renderAll();

          // 已经渲染过的
          const draweredItem = this.canvases[i].getObjects().filter(x => x.uuid === this.drawingObject.uuid)[0];
          this.canvases[i].remove(draweredItem);

          const adjObject = this.drawRect([
            this.drawingObject.get('left'),
            -((this.blockSize * (i - startIndex)) - this.drawingObject.get('top')),
            position.x - this.drawingObject.get('left'),
            height],
          {
            uuid: this.drawingObject.uuid,
            adjCanvasNumber: adjacentCanvasIndex - startIndex
          });
          this.canvases[i].add(adjObject);
          // 最后的画布记录下来 如果用户跨画布 但是又取消跨画布 需要清楚所跨的画布的图形
          if (i === adjacentCanvasIndex) {
            this.adjacentCanvasIndex = i;
          }
        }
      } else {
        const width = position.x - this.drawingObject.get('left');
        const height = position.y - this.drawingObject.get('top');
        this.drawingObject.set({
          width: width,
          height: height,
          adjCanvasNumber: 0
          // startCanvansIndex: startIndex
        });
        this.activeCanvas.renderAll();
      }
    }
  }

  private mouseUp (e) {
    this.setCursor();

    if (this[`mouseUp${this.drawType}`] && e.pointer) {
      return;
    }
    if (this.selectObject()) {
      // 删除不在范围内的图形 并且重置他的adjCanvasNumber
      this.deleteExceedShape();
    }
  }

  private setCursor () {
    if (this.drawType) {
      this.canvases.map(item => {
        item.setCursor('crosshair');
      });
    }
  }

  private deleteExceedShape () {
    this.canvases.map(item => {
      const draweredItem = item.getObjects().filter(x => x.uuid === this.selectObject().uuid)[0];
      if (draweredItem) {
        // 向下超出的被删除
        // 向上超出也要被删除  || draweredItem.top + draweredItem.height * draweredItem.scaleY < 0
        if (draweredItem.top > this.blockSize || draweredItem.top + draweredItem.height * draweredItem.scaleY < 0) {
          // 修改adjCanvasNumber
          const sameUuidObject = this.getObjectByUuid(draweredItem.uuid);
          sameUuidObject.map(sameUuiditem => {
            sameUuiditem.shape.adjCanvasNumber = sameUuiditem.shape.adjCanvasNumber - 1;
          });
          // 删除
          item.remove(draweredItem);
          item.renderAll();
        }
      }
    });
  }

  private async drawEnd (object) {
    // 设置为当前活跃
    this.setActiveByUuid(object.uuid);
    // 如果高度< 0 则重新整一下他的高度
    if (object.height < 0) {
      const sameUuidObject = this.getObjectByUuid(object.uuid);
      sameUuidObject.map(item => {
        item.shape.set({
          top: item.shape.height + item.shape.top,
          originTop: item.shape.height + item.shape.top,
          height: Math.abs(object.height)
        });
        this.canvases[item.canvasIndex].renderAll();
      });
    }

    this.drawingObject = null;
    this.adjObject = null;
    this.adjacentCanvasIndex = -1;

    // this.adjacentCanvas = null;
    // this.drawingShape = [];

    // if (this.canvasObjects) {
    //   this.canvasObjects.push(object); // 置于顶层/底层之后
    // }
    this.edit(object);

    await this.$nextTick();
    this.onChange();
    this.$emit('drawEnd');
  }

  public setActiveByUuid (uuid: string) {
    // 设置为当前活跃
    this.canvases.map(item => {
      const draweredItem = item.getObjects().filter(x => x.uuid === uuid)[0];
      // discardActiveObject 抛弃当前处于活动状态的Object。
      draweredItem ? item.setActiveObject(draweredItem) : item.discardActiveObject();
      item.renderAll();
    });
  }

  // 改变时候触发
  private async onChange () {
    let allObjects: any = [];
    this.canvases.map((canvas, index) => {
      if (canvas.getObjects().length) {
        // 更改他的top
        const getObjects = canvas.getObjects().map(shape => {
          return {
            ...shape,
            top: shape.top + index * this.blockSize
          };
        });
        allObjects.push(...getObjects);
      }
    });

    const hash = {};
    const newData = [...allObjects];
    allObjects = newData.reduce((item, next) => {
      if (!hash[next.uuid]) {
        hash[next.uuid] = true;
        item.push(next);
      }
      return item;
    }, []);

    this.$emit('onChange', allObjects);
  }

  // points 数组 存放left、top、width、height  从后端渲染框到前端
  public narrowRect (points: number[], others?: OthersConfigModel) {
    if (!this.canvases.length) {
      return;
    }
    const belowXPoint = points[1] + points[3];
    const startCanvasIndex = Math.floor(points[1] / this.blockSize);
    const adjCanvasNumber = Math.floor(belowXPoint / this.blockSize);
    // 因为points传入的top是全局图片的 需要把它转化为某个canvans的相对top
    const drawPoint = [
      points[0],
      points[1] - startCanvasIndex * this.blockSize,
      points[2],
      points[3]
    ];

    // 跨框
    if (startCanvasIndex !== adjCanvasNumber) {
      // const uuid = createUuid();
      for (let i = startCanvasIndex; i < adjCanvasNumber + 1; i++) {
        const adjObject = this.drawRect([
          points[0],
          // i===startCanvasIndex?points[1]:i !== adjCanvasNumber?this.blockSize + 1 :
          i === startCanvasIndex ? drawPoint[1] : -((this.blockSize * (i - startCanvasIndex)) - drawPoint[1]),
          points[2],
          points[3]],
        {
          ...others,
          // uuid: uuid,
          adjCanvasNumber: adjCanvasNumber - startCanvasIndex
        });
        this.canvases[i].add(adjObject);
      }
    } else {
      // 不跨框
      this.canvases[startCanvasIndex].add(this.drawRect(drawPoint, {
        ...others,
        adjCanvasNumber: 0
      }));
    }
  }

  public narrowLine (points: number[], others?: OthersConfigModel) {
    const startCanvasIndex = Math.floor(points[1] / this.blockSize);
    const drawPoint = [
      points[0],
      points[1] - startCanvasIndex * this.blockSize,
      points[2],
      points[3] - startCanvasIndex * this.blockSize
    ];
    const adjObject = this.drawLine(drawPoint, others);
    this.canvases[startCanvasIndex].add(adjObject);
  }

  private onScaling (object, index: number) {
    // 跨了之后取消跨画布 sameUuidObject 依然还是2个 后续如果清楚另一个框的话  return的判断就需要换一下了
    const sameUuidObject = this.getObjectByUuid(object.uuid);
    sameUuidObject.map(item => {
      item.shape.set({
        scaleX: object.scaleX,
        scaleY: object.scaleY,
        left: object.left// 万一用户拉的左侧的,
      });
      this.canvases[item.canvasIndex].renderAll();
    });
    // 跨canvas的时候 防止他缩放的是上面 带来的bug
    this.mouseMoving(object, index);
  }

  private mouseMoving (object, index) {
    const padding = -1.5; // 内容距离画布的空白宽度,主动设置
    // 限制左右不能移出外面
    object.setCoords();
    const objBoundingBox = object.getBoundingRect();
    if (objBoundingBox.left < padding) {
      object.left = Math.max(object.left, object.left - objBoundingBox.left + padding);
    }
    if (objBoundingBox.left + objBoundingBox.width > object.canvas.width - padding) {
      object.left = Math.min(object.left, object.canvas.width - objBoundingBox.width + object.left - objBoundingBox.left - padding);
    }

    // 如果是第一块  限制不能移到上面
    if (!index && objBoundingBox.top < 0) {
      object.top = Math.max(object.top, object.top - objBoundingBox.top);
    } else if (index === this.numBlocks - 1 && objBoundingBox.top + objBoundingBox.height > object.canvas.height) {
      // 如果是最后一块 不能移动到下面
      object.top = Math.min(object.top, object.canvas.height - objBoundingBox.height + object.top - objBoundingBox.top);
    }

    // 跨框的情况下
    if (object.adjCanvasNumber) {
      const sameUuidObject = this.getObjectByUuid(object.uuid);
      sameUuidObject.map(item => {
        item.shape.left = object.left;
        item.shape.top = item.shape.originTop - (object.originTop - object.top);
        item.shape.visible = true;
        // 不在这里做判断 因为跨框之后 删除就会操成停顿 解决办法:除了mouseUp写移除 在就是 设置新增的那个为移动状态 ???
        this.canvases[item.canvasIndex].renderAll();
        this.canvases[item.canvasIndex].setActiveObject(item.shape);
      });
      // // 原本跨多个canvas 移动到下个画布 会消失
      // if (object.adjCanvasNumber > 1) {
      const adjCanvasNumber = Math.floor((object.top + object.height * object.scaleY) / this.blockSize);
      if (index + adjCanvasNumber > object.adjCanvasNumber + sameUuidObject[0].canvasIndex) {
        const lastRect = sameUuidObject[object.adjCanvasNumber].shape;

        // 如果在同一个画布停顿两次 第一次就会留下
        const draweredItem = this.canvases[index + adjCanvasNumber].getObjects().filter(x => x.uuid === object.uuid)[0];

        if (draweredItem) {
          return;
        }
        lastRect.set({
          originTop: -(this.blockSize - lastRect.originTop)
        });
        const adjObject = this.drawRect([
          object.left,
          -(this.blockSize - lastRect.originTop),
          object.width,
          object.height],
        {
          uuid: object.uuid,
          adjCanvasNumber: object.adjCanvasNumber
        });
        adjObject.set({
          scaleX: object.scaleX,
          scaleY: object.scaleY
        });
        this.canvases[index + adjCanvasNumber].add(adjObject);
        this.canvases[index + adjCanvasNumber].setActiveObject(adjObject);
        sameUuidObject.map(item => {
          item.shape.adjCanvasNumber = object.adjCanvasNumber + 1;
        });
      }
      // 多个画布的向上移动
      if (object.top < 0) {
        const draweredItem = this.canvases[index - 1].getObjects().filter(x => x.uuid === object.uuid)[0];

        if (draweredItem) {
          return;
        }
        const adjObject = this.drawRect([
          object.left,
          this.blockSize + object.originTop,
          object.width,
          object.height],
        {
          uuid: object.uuid,
          adjCanvasNumber: object.adjCanvasNumber
        });
        adjObject.set({
          scaleX: object.scaleX,
          scaleY: object.scaleY,
          // 不然就会出现闪一下
          visible: false
        });
        this.canvases[index - 1].add(adjObject);
      }
      return;
    }

    // 原本不跨canvas 变量adjCanvasNumber变更
    const adjCanvasNumber = Math.floor((object.top + object.height * object.scaleY) / this.blockSize);
    if (adjCanvasNumber) {
      object.adjCanvasNumber = adjCanvasNumber;
      for (let i = index + 1; i < adjCanvasNumber + index + 1; i++) {
        const adjObject = this.drawRect([
          object.left,
          -(this.blockSize - object.originTop),
          object.width,
          object.height],
        // i !== adjCanvasNumber ? this.blockSize + 1 : object.height * object.scaleY],
        {
          uuid: object.uuid,
          adjCanvasNumber: adjCanvasNumber
        });
        adjObject.set({
          scaleX: object.scaleX,
          scaleY: object.scaleY,
          visible: false
        });
        this.canvases[i].add(adjObject);
        // this.canvases[i].setActiveObject(adjObject);
      }
    }

    // 向上移动 原本不跨canvas 缩放或移动导致跨  第一块向上移动还是0
    if (object.top < 0) {
      object.adjCanvasNumber = index ? 1 : 0;
    }
  }

  // 获取包含指定坐标的画布
  private getCanvasFromCoordinates (x, y) {
    for (let i = 0; i < this.canvases.length; i++) {
      const canvas = this.canvases[i];
      const localPoint = canvas.getPointer({ x: x, y: y });
      const canvasElement = canvas.getElement();
      const canvasRect = canvasElement.parentElement.getBoundingClientRect();
      if (
        localPoint.x >= 0 &&
      localPoint.x <= canvasRect.width &&
      localPoint.y >= 0 &&
      localPoint.y <= canvasRect.height
      ) {
        return canvas;
      }
    }
    return null;
  }

  private getObjectByUuid (uuid: number) {
    const objects: GetShapeByUuidModel[] = [];
    this.canvases.map((canvas, index) => {
      const object = canvas.getObjects().filter(x => x.uuid === uuid)[0];
      object && objects.push({
        shape: object,
        canvasIndex: index
      });
    });
    return objects;
  }

  private edit (object) {
    if (object.type === DrawType.Polygon) {
      const lastControl = object.points.length - 1;
      object.controls = object.points.reduce((a, point, index) => {
        a['p' + index] = new fabric.Control({
          positionHandler: (dim, finalMatrix, fabricObject) => {
            const x = fabricObject.points[index].x - fabricObject.pathOffset.x;
            const y = fabricObject.points[index].y - fabricObject.pathOffset.y;
            return fabric.util.transformPoint(
              { x: x, y: y },
              fabric.util.multiplyTransformMatrices(
                fabricObject.canvas.viewportTransform,
                fabricObject.calcTransformMatrix()
              )
            );
          },
          actionHandler: this.anchorWrapper(index > 0 ? index - 1 : lastControl, this.actionHandler),
          actionName: 'modifyPolygon',
          pointIndex: index
        });
        return a;
      }, {});
    } else {
      object.cornerStyle = 'circle';
      object.controls = fabric.Object.prototype.controls;
    }

    // 点击的时候 已经传给后端的框 防止画框执行下面的
    //  !this.drawingObject 在已有的框中结束画 会触发emit事件  导致被选中的图形不是绘画的而是点击的
    if (typeof (object.clickIndex) !== 'undefined' && !this.drawingObject) {
      this.$emit('editIndex', object.clickIndex);
    }
    // const ids = (this.canvasObjects ?? this.activeCanvas.getObjects()).map(item => item.uuid).filter(x => x);
    // this.activeCanvas.requestRenderAll();
    // this.$emit('editIndex', object.clickIndex ?? ids.indexOf(object.uuid));
  }

  // 画四边形
  private drawRect (points: Array<number>, others?: OthersConfigModel) {
    const rect = new fabric.Rect({
      type: DrawType.Rect,
      uuid: createUuid(), // todo
      left: points[0],
      top: points[1],
      width: points[2] || 0,
      height: points[3] || 0,
      objectCaching: false,
      transparentCorners: false,
      selectionColor: 'rgba(0,0,0,0)',
      lockRotation: true,
      strokeUniform: true,
      // 移动时候需要 记录他移动了多少 来让同一个矩形uuid也移动
      originLeft: points[0],
      originTop: points[1],
      ...defaultRectStyle,
      ...this.rectStyle,
      ...others
    });
    // eslint-disable-next-line spellcheck/spell-checker
    rect.setControlsVisibility({ mtr: false }); // 隐藏旋转点
    return rect;
  }

  // 起点坐标 xy // 终点坐标 xy others目前专门设置颜色
  private drawLine (position: number[], others?: OthersConfigModel) {
    return new fabric.Line(position, {
      type: DrawType.Line,
      stroke: 'blue',
      strokeWidth: 2,
      objectCaching: false,
      hasBorders: false,
      selectable: false,
      transparentCorners: true,
      lockRotation: true,
      lockMovementX: true,
      lockMovementY: true,
      lockScalingX: true,
      lockScalingY: true,
      hasControls: false, // 隐藏控制点
      ...others
    });
  }

  public selectObject () {
    const selectedObjects = this.canvases.map(canvas => canvas.getActiveObject()).filter(x => x);
    return selectedObjects.length ? selectedObjects[0] : undefined;
  }

  public setDrawType (type) {
    // 外部调用此方法,切换画图模式
    this.drawType = type;
  }

  public removeAll () {
    this.canvases.map(item => {
      item.clear();
    });
  }

  public removeLine () {
    this.canvases.map(item => {
      const draweredItem = item.getObjects().filter(x => x.type === DrawType.Line)[0];
      if (draweredItem) {
        // 删除
        item.remove(draweredItem);
        item.renderAll();
      }
    });
  }

  public removeSelectedObject () {
    this.canvases.map(item => {
      if (this.selectObject()) {
        const draweredItem = item.getObjects().filter(x => x.uuid === this.selectObject().uuid)[0];
        if (draweredItem) {
        // 删除
          item.remove(draweredItem);
          item.renderAll();
        }
      }
    });
  }

  public setObjectVisible (uuid: string, visible: boolean) {
    // 设置为当前活跃
    this.canvases.map(item => {
      const draweredItem = item.getObjects().filter(x => x.uuid === uuid)[0];
      if (draweredItem) {
        draweredItem.visible = visible;
        draweredItem.setControlsVisibility({
          mt: visible,
          mb: visible,
          ml: visible,
          mr: visible,
          bl: visible,
          br: visible,
          tl: visible,
          tr: visible
        });
      }
      item.renderAll();
    });
  }

  private transformMouse (e): MousePosition {
    return e.pointer;
  }

  private anchorWrapper (anchorIndex, fn) {
    return function (eventData, transform, x, y) {
      const fabricObject = transform.target;
      const absolutePoint = fabric.util.transformPoint(
        {
          x: fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x,
          y: fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y
        },
        fabricObject.calcTransformMatrix()
      );
      const actionPerformed = fn(eventData, transform, x, y);
      const polygonBaseSize = fabricObject._getNonTransformedDimensions();
      const newX = (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x;
      const newY = (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y;
      fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
      return actionPerformed;
    };
  }

  private actionHandler (eventData, transform, x, y) {
    const polygon = transform.target;
    const currentControl = polygon.controls[polygon.__corner];
    const mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center');
    const polygonBaseSize = polygon._getNonTransformedDimensions();
    const size = polygon._getTransformedDimensions(0, 0);
    const finalPointPosition = {
      x: (mouseLocalPosition.x * polygonBaseSize.x) / size.x + polygon.pathOffset.x,
      y: (mouseLocalPosition.y * polygonBaseSize.y) / size.y + polygon.pathOffset.y
    };
    polygon.points[currentControl.pointIndex] = finalPointPosition;
    return true;
  }

  @Watch('width')
  @Watch('height')
  private styleChaneg () {
    this.numBlocks = Math.ceil(this.height / this.blockSize);
    this.init();
  }
}
</script>

<style lang="scss" scoped>
.drawer {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
  width: 100%;
  height: calc(100% - 70px);
  #canvas-container,
  .container {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
  }
  #canvas-container {
    z-index: 2;
  }
}
</style>

标签:canvas,浏览器,uuid,object,画布,item,操作,const
From: https://blog.csdn.net/weixin_43957384/article/details/137147356

相关文章

  • 嵌入式单片机补光灯项目操作实现
    1.【实验目的】       用于直播效果的补光 2.【实验原理】原理框架图2.各部分原理及主要功能  1.充电和供电:采用5V2Atepy_c接口充电,3.7V锂电池供电,2.功能:产品主要是用于直播或拍照时的补光。分为三个模式:白光/暧光&#x......
  • 浅谈从浏览器输入URL到页面渲染的流程
    浏览器输入URL到页面渲染完成,这个过程大致可分为两个阶段:网络通信和页面渲染。一、网络通信互联网内各网络间设备的通信遵循TCP/IP协议,利用TCP/IP协议进行网络通信时,会通过分层与对方通信。数据传输的过程:由应用层产生数据后,经过传输层的分段处理(添加TCP或UDP包头)、网络层(添加IP......
  • 小美的数组操作(美团2024届秋招笔试第二场编程真题)
    题面核心思想可以从示例中看出当sum/n能够整除时我们选择平均数作为众数即可不能整除时也就表示着不可能让所有数相同那么我们可以舍弃掉一个数a记剩下的数集合为b那么当b需要+1或-1后可能会剩下一些数那么我们可以选择让a去执行相反操作从而不影响b中剩......
  • 谷歌浏览器插件1688采购助手,1688代采代购系统,1688代采集运系统,号称重构全球贸易体验
    1688采购助手是一款旨在提高1688平台采购效率的谷歌浏览器插件,它通过提供比价、竞品分析等功能来辅助商家和采购者。以下是关于1688采购助手及其相关系统的一些详细介绍:价格比较:该插件可以帮助用户快速找到全网最具性价比的产品。在安装了1688采购助手之后,用户可以直接在1688......
  • 详解Java Chassis 3与Spring Cloud的互操作
    本文分享自华为云社区《JavaChassis3技术解密:与SpringCloud的互操作》,作者:liubao68。JavaChassis3一个很重要的设计原则:利用架构的韧性设计来解决兼容性问题。比如通过引入微服务网关,来解决不同语言、不同框架、遗留系统之间的互操作问题。本文在这个架构原则基础上,讨论......
  • MySQL基础操作
    目录一、用户管理1、创建用户2、查看用户3、删除用户4、修改密码二、权限管理1、赋予权限2、查看权限3、收回权限三、复制表结构和数据四、数据库操作1、创建数据库2、查看该数据库基本信息3、删除数据库4、查看MySQL中所有的数据库5、将数据库的字符集修改为gbk6、查看当前使用的......
  • Windows 中的 REG 命令是用于在命令行界面下直接操作 Windows 注册表的工具。注册表是
    Windows中的REG命令是用于在命令行界面下直接操作Windows注册表的工具。注册表是Windows系统中存储配置信息、应用程序设置以及系统参数的数据库,通过修改注册表可以影响系统的行为和配置。REG命令允许用户通过命令行界面来查询、修改和删除注册表中的键值。它的主要作用......
  • 工程软件-实验一:MATLAB操作基础与绘图
    目录一、实验目的:二、实验器材:三、实验内容及结果1.先求下列表达式的值,然后显示Matlab工作空间的使用情况并保存全部变量。2.已知​编辑求下列表达式的值:A+6*B和A-B+I(其中I为单位矩阵)A*B和A.*BA^3和A.^3A/B及B\A[A,B]和[A([1,3],:);B^2]3.设有矩阵A和B​编辑求它们......
  • 1、docker基础及安装,镜像操作
    docker基础dockerarchitecture:dockerclient:dockerbuild、dockerpull、dockerrundocker_host:dockerdeamon:containers+imagesdockerregistry:dockerobjects:images、containers、networks、volumes、plugins镜像:静态的容器:动态,生命周期1、docker安装和使......
  • CMD 批处理脚本自定义 UEFI 启动项,您需要使用 bcdedit 命令来操作 Windows 引导管理器
    CMD批处理脚本自定义UEFI启动项,您需要使用bcdedit命令来操作Windows引导管理器(BootConfigurationData,BCD)。BCD存储了计算机启动时所需的信息,包括可用的操作系统和启动选项。以下是一个简单的示例,演示如何使用CMD批处理脚本添加、修改和删除启动项:1.添加启动项要添......