• Fabric.js 图形标注


    需求分析

    画布中显示需要标注的图片,鼠标绘制矩形进行标注(矩形绘制在图片需要标注的位置,矩形中显示标注的内容文字)。最后可以拿到标注的内容位置信息、标注信息等并且回显所有标注内容。

    效果展示

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4QQvIGlZ-1667875228046)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e9185752f2554bdda4efede9a6e7ba60~tplv-k3u1fbpfcp-watermark.image?)]

    使用的技术

    vue.js(vue2) + fabric.js

    实现代码

    <template>
      <div id="fabricCanvas">
        <div id="pic-label">
          <div class="canvasDraw">
            <el-button @click="getData">保存修改el-button>
            <div class="context__x">
              <canvas ref="canvas" id="labelCanvas"> canvas>
              
              <div
                id="menu"
                class="menu-x"
                v-show="showCon"
                :style="menuPosition"
                @contextmenu.prevent=""
                ref="menu"
              >
                <div>
                  <ul>
                    <li v-for="(item, index) in tagData" @click="changeTag(item)">
                      {{ item.value }}
                    li>
                  ul>
                div>
                <div class="del" @click="delEl">删除div>
              div>
            div>
          div>
          <div class="tagCon">
            <div class="tagTitle" v-show="!isAdd">
              <div>标签栏div>
              <el-button type="primary" size="small" @click="addTag"
                >添加标签el-button
              >
            div>
            <div class="tagDOM tagItem" v-show="isAdd">
              <el-input
                v-model="tagCon"
                ref="addTask"
                @keyup.enter.native="addNewTag"
              >el-input>
              <el-button type="text" size="small" @click="addNewTag"
                >确定el-button
              >
              <el-button type="text" size="small" @click="cancelAdd"
                >取消el-button
              >
            div>
            
            <div style="margin-top: 10px">
              <el-input placeholder="请输入">el-input>
            div>
            <ul style="margin-top: 15px">
              <li v-for="(item, index) in tagData" class="tagItem">
                <div v-show="!item.isEdit" class="tagDOM">
                  <el-tooltip
                    class="item"
                    effect="dark"
                    :content="item.value"
                    placement="right-end"
                  >
                    <span @click="changeTag(item)" class="tagName">
                      {{ item.value }}
                    span>
                  el-tooltip>
                  <div class="iconCon">
                    <i
                      class="el-icon-edit editIcon"
                      @click="changeEdit(item, index)"
                    >i>
                    <i class="el-icon-delete delIcon" @click="delTag(item)">i>
                  div>
                div>
                <div class="tagDOM" v-show="item.isEdit">
                  <el-input
                    v-model="item.value"
                    ref="editTask"
                    @keyup.enter.native="changeText(item)"
                  >el-input>
                  <el-button type="text" size="small" @click="changeText(item)"
                    >确定el-button
                  >
                  <el-button type="text" size="small" @click="cancelChange(item)"
                    >取消el-button
                  >
                div>
              li>
            ul>
          div>
        div>
      div>
    template>
    
    <script>
    import { fabric } from "fabric";
    import { uuid } from "vue-uuid";
    export default {
      name: "",
      data() {
        return {
          canvasInfo: {
            width: "",
            height: "",
          },
          editorCanvas: "",
          mouseFrom: {},
          mouseTo: {},
          showCon: false,
          drawingObject: null,
          currentTarget: null,
          menuPosition: null,
          rectId: "",
          activeEl: "",
          isDrawing: false,
          currentType: "rect",
          // 标签栏
          isAdd: false,
          tagCon: "",
          tagData: [
            {
              value: "电视",
              id: "1",
              isEdit: false,
            },
            // {
            //   value: "电视柜",
            //   id: "2",
            //   isEdit: false,
            // },
            // {
            //   value: "灯",
            //   id: "3",
            //   isEdit: false,
            // },
          ],
        };
      },
      mounted() {
        // 后端返回:图片的长宽 2560 1200 ,用于等比例缩放图片
        this.canvasInfo.width = 2560 / 2;
        this.canvasInfo.height = 1200 / 2;
        this.init();
    
        // 监听键盘时间,按下backspace进行删除
        document.onkeydown = (e) => {
          let key = window.event.keyCode;
          // console.log("key", key);
          const isEdit = this.tagData.every((item) => item.isEdit == false);
          console.log("isEdit", isEdit);
          if (key == 8 && isEdit) {
            this.backSpaceDel();
          }
        };
      },
      methods: {
        // 按下backspace进行删除
        backSpaceDel() {
          // console.log("item", this.activeEl);
          // this.activeEl选中的标注内容
          if (this.activeEl) {
            this.editorCanvas.getObjects().forEach((item) => {
              console.log("item", item);
              if (item.rectId == this.activeEl.rectId) {
                this.editorCanvas.remove(item);
              }
            });
            this.editorCanvas.requestRenderAll();
          }
        },
        // 删除标签栏的tag---(实际项目中配合联调接口删除)
        delTag(item) {
          this.$confirm(`此操作将永久删除标签${item.value}, 是否继续?`, "提示", {
            confirmButtonText: "确定",
            cancelButtonText: "取消",
            type: "warning",
          })
            .then(() => {
              item.isEdit = false;
              let text = item.value;
              this.editorCanvas.getObjects().forEach((item1) => {
                console.log("item111--delTag", item1.textID, item.id);
                if (item1.textID && item1.textID == item.id) {
                  this.editorCanvas.remove(item1);
                  this.editorCanvas.requestRenderAll();
                }
              });
              // 删除标签
              this.tagData = this.tagData.filter((el) => el.id != item.id);
              console.log("tagData", this.tagData);
              this.$message({
                type: "success",
                message: "删除成功!",
              });
            })
            .catch(() => {
              this.$message({
                type: "info",
                message: "已取消删除",
              });
            });
        },
        init() {
          this.initeditorCanvas();
          this.initD();
        },
        // 初始化模板编辑画布
        initeditorCanvas() {
          // 根据canvas绘制保存的内容
          const str = JSON.parse(localStorage.getItem("canvasdata"));
          console.log("str", str);
          // 初始化canvas
          this.editorCanvas = new fabric.Canvas("labelCanvas", {
            // devicePixelRatio: true,
            width: this.canvasInfo.width, // canvas 宽
            height: this.canvasInfo.height,
            backgroundColor: "#ffffff",
            transparentCorners: false,
            fireRightClick: true, // 启用右键,button的数字为3
            stopContextMenu: true, // 禁止默认右键菜单
          });
          // this.editorCanvas.preserveObjectStacking = true;
          // this.editorCanvas.selectable = false;
          // this.editorCanvas.selection = false;
          // this.editorCanvas.toJSON(['rectId'])
          // this.editorCanvas.skipTargetFind = true;
          var img = "https://i1.mifile.cn/f/i/18/mitv4A/40/build.jpg";
          // mounted内预设的比例(由于图片太大,展示不下,实际项目中可以根据后端返回的图片大小范围去设置缩放比例)
          const scaleX = this.canvasInfo.width / 2560;
          const scaleY = this.canvasInfo.height / 1200;
          // 将图片设置成背景
          this.editorCanvas.setBackgroundImage(
            img,
            this.editorCanvas.renderAll.bind(this.editorCanvas), // 刷新画布
            {
              scaleX,
              scaleY,
              originX: "left",
              originY: "top",
              left: 0,
              top: 0,
            }
          );
          /**
           * 模型返回的绘制:根据拿到的left、top, width, height去绘制新矩形(根据图片与canvas的比例)
             drawRect()
           */
          // 监听鼠标右键的执行
          this.editorCanvas.on("mouse:down", this.canvasOnMouseDown);
          // 数据回显
          if (str) {
            // this.editorCanvas.loadFromJSON(str)
            this.editorCanvas.loadFromJSON(
              str,
              this.editorCanvas.renderAll.bind(this.editorCanvas),
              function (o, object) {
                // `o` = json object
                // `object` = fabric.Object instance
                // ... do some stuff ...
                // console.log('objqwe', o, object)
              }
            );
          }
        },
        initD() {
          this.editorCanvas.on("mouse:down", (options) => {
            // 记录当前鼠标的起点坐标
            if (!this.editorCanvas.getActiveObject()) {
              this.mouseFrom.x = options.pointer.x;
              this.mouseFrom.y = options.pointer.y;
              this.isDrawing = true;
            }
          });
          // 监听鼠标移动
          this.editorCanvas.on("mouse:move", (options) => {
            // console.log("move", options);
            if (!this.editorCanvas.getActiveObject() && this.isDrawing) {
              console.log("move");
              this.mouseTo.x =
                options.pointer.x > this.editorCanvas.width
                  ? this.editorCanvas.width
                  : options.pointer.x;
              this.mouseTo.y =
                options.pointer.y > this.editorCanvas.height
                  ? this.editorCanvas.height
                  : options.pointer.y;
            }
          });
          this.editorCanvas.on("mouse:up", (options) => {
            this.isDrawing = false;
            // console.log("mouse:up", options);
            if (
              !this.editorCanvas.getActiveObject() &&
              this.currentType == "rect"
            ) {
              // 解决绘制的时候超出边界
              this.mouseTo.x =
                options.pointer.x > this.editorCanvas.width
                  ? this.editorCanvas.width
                  : options.pointer.x;
              this.mouseTo.y =
                options.pointer.y > this.editorCanvas.height
                  ? this.editorCanvas.height
                  : options.pointer.y;
    
              // 宽高为负值或为0
              let width = this.mouseTo.x - this.mouseFrom.x;
              let height = this.mouseTo.y - this.mouseFrom.y;
              // 如果点击和松开鼠标,都是在同一个坐标点或者反向,不绘制矩形
              if (width <= 0 || height <= 0) return;
              this.drawRect();
            }
            this.editorCanvas.renderAll();
          });
          this.editorCanvas.on("object:moving", (e) => {
            // 边界处理
            var obj = e.target;
            // if object is too big ignore
            if (
              obj.currentHeight > obj.canvas.height ||
              obj.currentWidth > obj.canvas.width
            ) {
              return;
            }
            obj.setCoords();
            // top-left  corner
            if (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) {
              obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top);
              obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left);
            }
            // bot-right corner
            if (
              obj.getBoundingRect().top + obj.getBoundingRect().height >
                obj.canvas.height ||
              obj.getBoundingRect().left + obj.getBoundingRect().width >
                obj.canvas.width
            ) {
              obj.top = Math.min(
                obj.top,
                obj.canvas.height -
                  obj.getBoundingRect().height +
                  obj.top -
                  obj.getBoundingRect().top
              );
              obj.left = Math.min(
                obj.left,
                obj.canvas.width -
                  obj.getBoundingRect().width +
                  obj.left -
                  obj.getBoundingRect().left
              );
            }
          });
          this.editorCanvas.on("object:scaling", (options) => {
            // console.log("scale", options);
            var text = options.target.item(1);
            let group = options.target;
            console.log("text", text.width, group.width, group.getScaledWidth());
            let scaleX = group.width / group.getScaledWidth();
            let scaleY = group.height / group.getScaledHeight();
            text.set({
              fontSize: 14,
              scaleX,
              scaleY,
            });
          });
        },
        // 绘制矩形
        /**
         * 绘制的原理:矩形+文字的组合
         */
        drawRect() {
          console.log("绘图啦11111", this.mouseFrom, this.mouseTo);
          // 通过UUID拿到唯一的ID
          let rectId = uuid.v1();
          /**
           * 删除之前的this.drawingObject
           */
          if (this.drawingObject) {
            this.editorCanvas.remove(this.drawingObject);
          }
          this.drawingObject = null;
          // 计算矩形长宽
          let left = this.mouseFrom.x;
          let top = this.mouseFrom.y;
          let width = this.mouseTo.x - this.mouseFrom.x;
          let height = this.mouseTo.y - this.mouseFrom.y;
          const drawingObject = new fabric.Rect({
            width: width,
            height: height,
            fill: "#d70202",
            lockRotation: true,
            opacity: 0.5,
            rectId,
            lockScalingFlip: true, // 禁止负值反转
            originX: "center",
            originY: "center",
          });
          const text = new fabric.Textbox("", {
            // width,
            // height,
            fontFamily: "Helvetica",
            fill: "white", // 设置字体颜色
            fontSize: 14,
            textAlign: "center",
            rectId,
            lockScalingX: true,
            lockScalingY: true,
            lockScalingFlip: true, // 禁止负值反转
            originX: "center",
            originY: "center",
          });
          if (drawingObject) {
            const group = new fabric.Group([drawingObject, text], {
              rectId,
              left: left,
              top: top,
              width: width,
              height: height,
              lockScalingFlip: true,
              lockRotation: true,
            });
            this.editorCanvas.add(group);
            console.log("this.editorCanvas", this.editorCanvas);
            this.editorCanvas.renderAll();
            this.drawingObject = drawingObject;
            // 绘制完成展示右键菜单栏(因为在鼠标绘制时,通过mouseup拿不到绘制的内容)
            let len = this.editorCanvas._objects.length;
            let curOptions = this.editorCanvas._objects[len - 1];
            this.showMenuCon(curOptions);
          }
        },
        // 绘制时展示右键菜单栏内容
        showMenuCon(options) {
          console.log(options);
          this.activeEl = options;
          // 当前鼠标位置
          let pointX = options.left + options.width * options.scaleX;
          let pointY = options.top;
    
          this.menuPosition = `
                    left: ${pointX}px;
                    top: ${pointY}px;
                  `;
          this.showCon = true;
        },
        // 编辑
        changeEdit(item, index) {
          item.isEdit = true;
          // focus: 点击编辑聚焦
          this.$nextTick(() => this.$refs.editTask[index].focus());
          // console.log('this.$refs.editTask', this.$refs.editTask)
        },
        // 修改选中的标注内容
        changeText(item) {
          if (item.value == "") {
            this.$message({
              type: "error",
              message: "标签内容不能为空",
              offset: 200,
            });
            return;
          }
          item.isEdit = false;
          let text = item.value;
          this.editorCanvas.getObjects().forEach((item1) => {
            if (item1.textID && item1.textID == item.id) {
              console.log("item111", item1.textID, item.id);
              item1.item(1).set({
                text,
                originX: "center",
                originY: "center",
                textAlign: "center",
              });
              this.editorCanvas.requestRenderAll();
            }
          });
        },
        // 取消修改
        cancelChange(item) {
          item.isEdit = false;
        },
        // 右键菜单
        canvasOnMouseDown(options) {
          if (options.button === 3 && options.target && !options.target.rectId) {
            return;
          }
          this.activeEl = options.target;
          // console.log("opt", options);
          // 判断:右键,且在元素上右键
          // opt.button: 1-左键;2-中键;3-右键
          // 在画布上点击:opt.target 为 null
          if (options.button === 3 && options.target) {
            // 获取当前元素
            // 设置右键菜单位置
            // 右键菜单的位置
            let pointX =
              options.target.left + options.target.width * options.target.scaleX;
            let pointY = options.target.top;
            // 设置右键菜单定位
            this.menuPosition = `
                    left: ${pointX}px;
                    top: ${pointY}px;
                  `;
            this.showCon = true;
          } else {
            this.showCon = false;
          }
        },
        // 添加标签
        addTag() {
          this.isAdd = true;
          // input鼠标聚焦
          this.$nextTick(() => this.$refs.addTask.focus());
        },
        addNewTag() {
          if (this.tagCon.trim() == "") {
            this.message({
              type: "error",
              message: "内容不能为空",
              offset: 200,
            });
            return;
          }
          // 调接口
          this.tagData.push({
            value: this.tagCon,
            id: uuid.v1(),
            isEdit: false,
          });
          // 置空 关闭
          this.tagCon = "";
          this.isAdd = false;
        },
        // 取消添加
        cancelAdd() {
          this.isAdd = false;
        },
        // 根据选中的TAG进行修改
        changeTag(el) {
          if (this.activeEl) {
            // console.log("item", el.value, this.activeEl.rectId);
            let text = el.value;
            let textID = el.id;
            this.editorCanvas.getObjects().forEach((item) => {
              // console.log("item", item);
              if (item.rectId == this.activeEl.rectId) {
                // console.log("item", item);
                item.set({
                  textID,
                });
                item.item(1).set({
                  text,
                  originX: "center",
                  originY: "center",
                  textAlign: "center",
                });
              }
            });
            this.editorCanvas.requestRenderAll();
            this.showCon = false;
          }
        },
        // 删除选中的元素
        delEl() {
          this.editorCanvas.getObjects().forEach((item) => {
            console.log("item", item);
            if (item.rectId == this.activeEl.rectId) {
              this.editorCanvas.remove(item);
              // console.log(item.rectId, this.activeEl.rectId)
            }
          });
          this.editorCanvas.requestRenderAll();
          this.showCon = false;
        },
        // 拿到canvas上的所有数据
        /**
         * 最终提交给后端要说明:scaleX,scaleY
         */
        getData() {
          console.log(
            "this.editorCanvas",
            this.editorCanvas,
            this.editorCanvas.toJSON()
          );
          // rectId自定义属性
          localStorage.setItem(
            "canvasdata",
            JSON.stringify(
              this.editorCanvas.toJSON(["rectId", "textID", "lockScalingFlip"])
            )
          );
          console.log("getObjects", this.editorCanvas.getObjects());
        },
      },
    };
    script>
    
    <style lang="scss" scoped>
    #fabricCanvas {
      padding: 20px;
      background: #f6f6f6;
      #pic-label {
        width: 100%;
        display: flex;
        justify-content: center;
        background: #f6f6f6;
        .canvasDraw {
          background: #fff;
          padding: 20px;
        }
        #labelCanvas {
          position: relative;
          box-shadow: 0 0 25px #cac6c6;
          width: 100%;
          display: block;
          // margin: 15px auto;
          height: 100%;
    
          #editDel {
            position: absolute;
            // top: 50%;
            // left: 50%;
            // transform: translate(-50%, -50%);
            width: 100px;
            height: 100px;
            line-height: 100px;
            background: red;
            color: white;
            text-align: center;
            margin: auto auto;
            z-index: 99999;
          }
        }
        .tagCon {
          width: 20%;
          margin-left: 20px;
          min-width: 250px;
          background: #fff;
          padding: 10px 20px;
    
          .tagTitle {
            height: 62px;
            font-size: 18px;
            font-weight: 700;
            display: flex;
            justify-content: space-between;
            align-items: center;
            // padding: 0 20px;
            box-sizing: border-box;
          }
          .tagItem {
            margin-bottom: 10px;
            border: 1px solid #dcdfe6;
            padding: 10px;
            border-left: 4px solid blue;
          }
          .tagDOM {
            display: flex;
            justify-content: space-between;
            align-items: center;
            .tagName {
              width: 70%;
              height: 40px;
              line-height: 40px;
              margin-right: 10px;
              display: inline-block;
              // width: 100px;
              text-align: left;
              overflow: hidden;
              text-overflow: ellipsis;
              white-space: nowrap;
              font-size: 14px;
              color: #606266;
            }
            .iconCon {
              display: none;
              .editIcon {
                cursor: pointer;
                margin-right: 10px;
              }
              .delIcon {
                cursor: pointer;
              }
            }
    
            &:hover .iconCon {
              display: block;
            }
          }
        }
      }
    }
    style>
    <style scoped>
    .context__x {
      position: relative;
      margin-top: 15px;
    }
    
    .menu-x {
      width: 200px;
      position: absolute;
      background-color: #fff;
      border-radius: 4px;
      box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
    }
    
    .menu-x div {
      box-sizing: border-box;
      padding: 4px 8px;
      border-bottom: 1px solid #ccc;
      cursor: pointer;
    }
    
    .menu-x ul > li:hover {
      background-color: antiquewhite;
    }
    
    .menu-x .del:hover {
      background-color: antiquewhite;
    }
    
    .menu-x div:first-child {
      border-top-left-radius: 4px;
      border-top-right-radius: 4px;
    }
    
    .menu-x div:last-child {
      border-bottom: none;
      border-bottom-left-radius: 4px;
      border-bottom-right-radius: 4px;
    }
    .tagDOM >>> .el-input__inner {
      border: 0;
      padding: 0;
    }
    #fabricCanvas >>> .el-card__body,
    .el-main {
      padding: 0;
    }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    • 510
    • 511
    • 512
    • 513
    • 514
    • 515
    • 516
    • 517
    • 518
    • 519
    • 520
    • 521
    • 522
    • 523
    • 524
    • 525
    • 526
    • 527
    • 528
    • 529
    • 530
    • 531
    • 532
    • 533
    • 534
    • 535
    • 536
    • 537
    • 538
    • 539
    • 540
    • 541
    • 542
    • 543
    • 544
    • 545
    • 546
    • 547
    • 548
    • 549
    • 550
    • 551
    • 552
    • 553
    • 554
    • 555
    • 556
    • 557
    • 558
    • 559
    • 560
    • 561
    • 562
    • 563
    • 564
    • 565
    • 566
    • 567
    • 568
    • 569
    • 570
    • 571
    • 572
    • 573
    • 574
    • 575
    • 576
    • 577
    • 578
    • 579
    • 580
    • 581
    • 582
    • 583
    • 584
    • 585
    • 586
    • 587
    • 588
    • 589
    • 590
    • 591
    • 592
    • 593
    • 594
    • 595
    • 596
    • 597
    • 598
    • 599
    • 600
    • 601
    • 602
    • 603
    • 604
    • 605
    • 606
    • 607
    • 608
    • 609
    • 610
    • 611
    • 612
    • 613
    • 614
    • 615
    • 616
    • 617
    • 618
    • 619
    • 620
    • 621
    • 622
    • 623
    • 624
    • 625
    • 626
    • 627
    • 628
    • 629
    • 630
    • 631
    • 632
    • 633
    • 634
    • 635
    • 636
    • 637
    • 638
    • 639
    • 640
    • 641
    • 642
    • 643
    • 644
    • 645
    • 646
    • 647
    • 648
    • 649
    • 650
    • 651
    • 652
    • 653
    • 654
    • 655
    • 656
    • 657
    • 658
    • 659
    • 660
    • 661
    • 662
    • 663
    • 664
    • 665
    • 666
    • 667
    • 668
    • 669
    • 670
    • 671
    • 672
    • 673
    • 674
    • 675
    • 676
    • 677
    • 678
    • 679
    • 680
    • 681
    • 682
    • 683
    • 684
    • 685
    • 686
    • 687
    • 688
    • 689
    • 690
    • 691
    • 692
    • 693
    • 694
    • 695
    • 696
    • 697
    • 698
    • 699
    • 700
    • 701
    • 702
    • 703
    • 704
    • 705
    • 706
    • 707
    • 708
    • 709
    • 710
    • 711
    • 712
    • 713
    • 714
    • 715
    • 716
    • 717
    • 718
    • 719
    • 720
    • 721
    • 722
    • 723
    • 724
    • 725
    • 726
    • 727
    • 728
    • 729
    • 730
    • 731
    • 732
    • 733
    • 734
    • 735
    • 736
    • 737
    • 738
    • 739
    • 740

    学习使用Fabric

    边界处理-拖拽

    部分基本内容学习

  • 相关阅读:
    Vue知识系列(7)每天10个小知识点
    第五章:Java中的方法和方法重载
    微信关于权重条件,连续下降积分的原因有以下这些
    Python类的疑难点
    [笔记]JavaScript 实现按钮拖拽效果
    迷宫回溯问题(思路分析) [数据结构][Java]
    PostgreSQL数据类型——范围类型
    Linux开发环境配置
    【云原生】Kubeadmin安装k8s集群
    JAVA计算机毕业设计延安市图书馆管理Mybatis+系统+数据库+调试部署
  • 原文地址:https://blog.csdn.net/ME_GIRL/article/details/127746221