canva拖拽选择列表

99 阅读1分钟

组件 DragSelectBoard:

<template>
  <div v-if="isVisible" id="canvas_board" class="canvas-board" />
</template>

<script>
export default {
  name: 'DragSelectBoard',

  props: {
    visible: {
      type: Boolean,
      default: false
    }
  },

  data() {
    return {
      // 画布
      canvas: null,
      ctx: null,
      // 虚线
      lineWidth: 2,
      lineColor: 'red',
      // 移动
      moving: false,
      // 虚线起点和终点
      startPos: { x: 0, y: 0 },
      endPos: { x: 0, y: 0 }
    };
  },

  computed: {
    isVisible: {
      get() {
        return this.visible;
      },
      set(val) {
        this.$emit('update:visible', val);
      }
    }
  },

  watch: {
    isVisible(val) {
      this.$nextTick(() => {
        if (val) {
          // 创建canvas画布
          this.createCanvas();
        } else {
          // 清空画布
          this.clearCanvas();
        }
      });
    }
  },

  methods: {
    // 创建画布
    createCanvas() {
      const container = document.getElementById('canvas_board');

      const containerSize = container.getBoundingClientRect();
      this.canvas = document.createElement('canvas');
      this.canvas.width = containerSize.width;
      this.canvas.height = containerSize.height;

      container.appendChild(this.canvas);
      this.canvas.addEventListener('mousedown', this.canvasMousedown);
      this.canvas.addEventListener('mouseup', this.canvasMouseup);

      this.ctx = this.canvas.getContext('2d');
      this.ctx.globalAlpha = 0.4;
      this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
      this.ctx.fillStyle = '#000000';
    },

    // 清除画布
    clearCanvas() {
      this.canvas.removeEventListener('mousedown', this.canvasMousedown);
      this.canvas.removeEventListener('mouseup', this.canvasMouseup);
      this.canvas = null;
    },

    // 绘制虚线选框
    drawLine() {
      if (!this.ctx) {
        return;
      }

      // 清空
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

      // 绘制背景色
      this.ctx.globalAlpha = 0.4;
      this.ctx.fillStyle = '#000000';
      this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

      // 绘制虚线
      this.ctx.globalAlpha = 1;
      this.ctx.beginPath();
      this.ctx.strokeStyle = this.lineColor;
      this.ctx.setLineDash([10, 6]);
      this.ctx.lineWidth = this.lineWidth;
      this.ctx.lineJoin = 'round';
      this.ctx.moveTo(this.startPos.x, this.startPos.y);
      this.ctx.lineTo(this.endPos.x, this.startPos.y);
      this.ctx.lineTo(this.endPos.x, this.endPos.y);
      this.ctx.lineTo(this.startPos.x, this.endPos.y);
      this.ctx.lineTo(this.startPos.x, this.startPos.y);
      this.ctx.stroke();
      this.ctx.closePath();
    },

    // 鼠标按下
    canvasMousedown(el) {
      this.moving = true;
      this.startPos = {
        x: el.pageX,
        y: el.pageY
      };
      this.canvas.addEventListener('mousemove', this.canvasMousemove);
    },

    // 鼠标移动
    canvasMousemove(el) {
      this.endPos = {
        x: el.pageX,
        y: el.pageY
      };
      this.$nextTick(() => {
        this.drawLine();
      });
    },

    // 鼠标放开
    canvasMouseup() {
      this.moving = false;
      this.canvas.removeEventListener('mousemove', this.canvasMousemove);

      // 选择大的作为结束点,小的为起点,用与解决反向选择的BUG
      const params = {
        start: { x: Math.min(this.startPos.x, this.endPos.x), y: Math.min(this.startPos.y, this.endPos.y) },
        end: { x: Math.max(this.startPos.x, this.endPos.x), y: Math.max(this.startPos.y, this.endPos.y) }
      };

      this.$emit('select', params);

      this.resetLine();
    },

    // 清空虚线
    resetLine() {
      this.startPos = { x: 0, y: 0 };
      this.endPos = { x: 0, y: 0 };
      this.$nextTick(() => {
        this.drawLine();
      });
    },

    /**
     * 判断两个矩形是否有重叠部分
     * @param {Object} pos1 - 矩形1
     * @param {Object} pos1.start - 矩形1的左上角坐标
     * @param {number} pos1.start.x
     * @param {number} pos1.start.y
     * @param {Object} pos1.end - 矩形1的右下角坐标
     * @param {number} pos1.end.x
     * @param {number} pos1.end.y
     * @returns {Boolean}
     */
    overlap(pos1, pos2) {
      // x轴
      const beginX = Math.max(pos1.start.x, pos2.start.x);
      const endX = Math.min(pos1.end.x, pos2.end.x);
      const overlapX = endX - beginX > 0;

      // y轴
      const beginY = Math.max(pos1.start.y, pos2.start.y);
      const endY = Math.min(pos1.end.y, pos2.end.y);
      const overlapY = endY - beginY > 0;

      // 当X,Y同时存在重叠距离的时候,两个矩形有重叠部分
      return overlapX && overlapY;
    }
  }
};
</script>

<style lang="scss" scoped>
.canvas-board {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  z-index: 99999999;
}
</style>

父组件:parent

template:

    <drag-select-board
      ref="dragSelect"
      :visible.sync="dragSelectBoardVisible"
      @select="$_multipleSelect"
    />
{
data() {
    return {
      dragSelectBoardVisible: false
    };
  },

  mounted() {
    this.addEvent();
  },

  beforeDestroy() {
    this.removeEvent();
  },

  methods: {
    addEvent() {
      document.addEventListener('keydown', this.pressShift);
      document.addEventListener('keyup', this.upShift);
    },

    removeEvent() {
      document.removeEventListener('keydown', this.pressShift);
      document.removeEventListener('keyup', this.upShift);
    },

    pressShift(el) {
      if (el.key === 'Shift') {
        this.dragSelectBoardVisible = true;
      }
    },

    upShift(el) {
      if (el.key === 'Shift') {
        this.dragSelectBoardVisible = false;
      }
    },

    // 虚线多选
    $_multipleSelect(area) {
      const cardList = this.$refs.card;
      if (cardList) {
        this.$refs.card.forEach((item) => {
          const rect = item.$el.getBoundingClientRect();
          const pos = {
            start: { x: rect.left, y: rect.top },
            end: { x: rect.left + rect.width, y: rect.top + rect.height }
          };
          if (this.$refs.dragSelect.overlap(pos, area)) {
            item.handleChangeSelect(true);
          }
        });
      }
    }
  }
}