js封装在固定区域创建DIV并拖动

903 阅读3分钟

需求:使用cavans和div实现,当鼠标滑入指定区域时显示当前鼠标指定的位置,并且在指定区域内可自由框选区域,并且框选的区域可以拖拽

实现效果(不会放视频,就只截了两张图意思一下)

鼠标框选区域 在这里插入图片描述 鼠标按住选中区可滑动

JS版本

class createCanvas {
  constructor(options) {
    options = Object.assign(
      {
        container: document.body, // 父容器DOM, 默认是body
        lineColor: "black", // canvas中画的线的颜色
        regionBg: "pink", // 框选区域的背景颜色
        regionCanMove: true, // 框选区是否可拖动
        showLine: true, // 是否显示canvas中的线
        cursor: "crosshair", // canvas中鼠标的形状
        showValTip: true, // 是否显示数据提示,如果设置为true,则需要调用实例的pointVal方法传值
        valTipStyle: {
          // 数据提示的样式
          fontSize: "12px",
          color: "pink",
          backgroundColor: "black",
          padding: "4px",
          offset: 2, // 提示框距离线的偏移量
        },
      },
      options
    );
    this.tipVal = "";
    this.options = options;
    let { container } = options;
    this.container = container || document.body;

    // 计算父级元素的宽高帮保存到实例上
    this.H = this.container.clientHeight;
    this.W = this.container.clientWidth;

    // 初始化 canvas
    this.initCanvas(this.W, this.H);
  }

  // 创建canvas画布
  initCanvas(w, h) {
    w = w || this.W;
    h = h || this.H;
    let canvas = document.createElement("canvas");
    canvas.width = w;
    canvas.height = h;
    canvas.style.position = "absolute";
    canvas.style.cursor = this.options.cursor;
    canvas.style.zIndex = 2;
    this.canvas = canvas;
    // 插入canvasDOM
    this.container.appendChild(canvas);
    this.ctx = this.canvas.getContext("2d");
    console.dir(this.ctx);
    this.canvas.addEventListener("mouseenter", this.canvasEnter);
    this.canvas.addEventListener("mousedown", this.canvasDown);
  }

  // canvas进入时开始画线,并创建tipDOM
  canvasEnter = (ev) => {
    let valTip = document.createElement("span"),
      offset = this.options.valTipStyle.offset || 5;
    if (this.options.showValTip) {
      valTip.style.position = "absolute";
      valTip.style.whiteSpace = "nowrap";
      let { fontSize, color, backgroundColor, padding } =
        this.options.valTipStyle;
      valTip.style.fontSize = fontSize || "12px";
      valTip.style.color = color || "black";
      valTip.style.padding = padding || "4px";
      valTip.style.backgroundColor = backgroundColor || "pink";
      valTip.style.borderRadius = "2px";
      valTip.innerHTML = this.tipVal || "提示框";
      this.canvas.valTip = valTip;
      this.container.appendChild(this.canvas.valTip);
    } else {
      valTip = null;
    }

    let ctx = this.ctx;
    let W = this.canvas.width,
      H = this.canvas.height,
      lineColor = this.options.lineColor;
    // 鼠标进入canvas时画线
    const canvasMove = (ev) => {
      if (this.options.showLine) {
        // 每次画之前先清除之前的内容
        ctx.clearRect(0, 0, W, H);
        ctx.setLineDash([4, 2]); //线的样式
        // 画 Y 线
        ctx.stroke();
        ctx.beginPath();
        ctx.strokeStyle = lineColor; // 线的颜色
        ctx.moveTo(0, ev.offsetY);
        ctx.lineTo(this.W, ev.offsetY);
        // 画 X 线
        ctx.stroke();
        ctx.beginPath();
        ctx.strokeStyle = lineColor;
        ctx.moveTo(ev.offsetX, 0);
        ctx.lineTo(ev.offsetX, H);
        ctx.lineWidth = 1;
      }

      // 计算 valTip 的宽高,
      if (this.options.showValTip) {
        let valW = valTip.offsetWidth,
          valH = valTip.offsetHeight;
        this.canvas.valTip.style.top = ev.offsetY - 20 - offset + "px";
        this.canvas.valTip.style.left = ev.offsetX + offset + "px";
        if (ev.offsetY - this.canvas.offsetTop <= valH + offset) {
          this.canvas.valTip.style.top = ev.offsetY + offset + "px";
        }
        if (this.canvas.offsetWidth - ev.offsetX <= valW + offset) {
          this.canvas.valTip.style.left = ev.offsetX - offset - valW + "px";
        }
      }
    };

    // 鼠标移出canvas
    const canvasOut = (ev) => {
      ctx.clearRect(0, 0, W, H);
      this.options.showValTip ? this.canvas.valTip.remove() : null;
    };
    this.canvas.addEventListener("mousemove", canvasMove);
    this.canvas.addEventListener("mouseout", canvasOut);
  };

  // canvas鼠标按下的时候创建 regionDOM,并记录鼠标位置作为region的left
  canvasDown = (ev) => {
    ev.stopPropagation();
    if (this.canvas.region) {
      this.container.removeChild(this.canvas.region);
      delete this.canvas.region;
    }
    let region = document.createElement("div");
    region.style.position = "absolute";
    region.style.cursor = "move";
    region.className = "region";
    region.startX = ev.offsetX;
    region.endX = 0;
    region.style.width = "0";
    region.style.zIndex = 1;
    this.canvas.region = region;
    this.container.appendChild(this.canvas.region);
    this.canvas.addEventListener("mousemove", this.regionChange);

    let that = this;
    // canvas内鼠标抬起
    this.canvas.addEventListener("mouseup", (ev) => {
      that.canvas.removeEventListener("mousemove", that.regionChange);
      that.canvas.region.endX = ev.offsetX;
      that.canvas.region.style.zIndex = 1;
      if (that.options.regionCanMove) {
        that.canvas.region.style.zIndex = 3;
        that.canvas.region.addEventListener("mousedown", that.regionDown);
        that.canvas.region.addEventListener("mouseup", () => {
          that.canvas.region.removeEventListener("mousemove", that.regionMove);
        });
      }
    });
  };

  // 框选开始
  regionChange = (ev) => {
    let { borderColor, opacity, backgroundColor } = this.options.regionStyle;
    let canvas = this.canvas,
      region = canvas.region,
      H = canvas.height;
    region.endX = ev.offsetX + canvas.offsetLeft;
    region.style.backgroundColor = backgroundColor || "pink";
    region.style.opacity = opacity || 0.5;
    region.style.borderLeft =
      region.style.borderRight = `0.5px solid ${borderColor}`;
    region.style.top = 0 + "px";
    region.style.height = H + "px";
    if (region.endX > region.startX) {
      // 从左往右
      region.style.left = region.startX + "px";
      region.style.width = region.endX - region.startX + "px";
    } else {
      // 从右往左
      region.style.right = region.startX + "px";
      region.style.width = region.startX - region.endX + "px";
      region.style.left = region.endX + "px";
    }
  };

  // region开始拖动
  regionDown = (ev) => {
    ev.stopPropagation();
    let region = this.canvas.region,
      left = region.offsetLeft,
      cleft = ev.clientX - left;
    region.cleft = cleft;
    if (this.options.regionCanMove) {
      region.addEventListener("mousemove", this.regionMove);
      region.addEventListener("mouseout", () => {
        this.canvas.region.removeEventListener("mousemove", this.regionMove);
      });
    }
  };

  // region拖动中
  regionMove = (ev) => {
    let region = this.canvas.region,
      moveLeft = ev.clientX - region.cleft;
    // 判断可移动的区域
    if (moveLeft <= 0) {
      region.style.left = this.canvas.offsetLeft + "px";
    } else if (moveLeft + region.clientWidth > this.canvas.width) {
      region.style.left = this.canvas.width - region.clientWidth + "px";
    } else {
      region.style.left = moveLeft + "px";
    }
  };

  createRegion(left, width) {
    if (this.canvas.region) {
      this.container.removeChild(this.canvas.region);
      delete this.canvas.region;
    }
    let region = document.createElement("div");
    region.style.position = "absolute";
    region.style.cursor = "move";
    region.className = "region";
    region.startX = left;
    region.endX = width;

    region.style.left = left + "px";
    region.style.width = width + "px";
    region.style.height = this.canvas.height + "px";
    let { borderColor, opacity, backgroundColor } = this.options.regionStyle;
    region.style.backgroundColor = backgroundColor || "pink";
    region.style.opacity = opacity || 0.5;
    region.style.borderLeft =
      region.style.borderRight = `0.5px solid ${borderColor}`;
    region.style.zIndex = 1;
    this.canvas.region = region;
    this.container.appendChild(this.canvas.region);
    if (this.options.regionCanMove) {
      region.style.zIndex = 3;
      this.canvas.region.addEventListener("mousedown", this.regionDown);
      this.canvas.region.addEventListener("mouseup", () => {
        this.canvas.region.removeEventListener("mousemove", this.regionMove);
      });
    }
  }
  pointVal(val) {
    this.tipVal = val;
  }
}
window.createCanvas = createCanvas;

TypeScript版

interface options {
  container: HTMLElement; // 父容器DOM, 默认是body
  lineColor: string; // canvas中画的线的颜色
  regionBg: string; // 框选区域的背景颜色
  regionCanMove: boolean; // 框选区是否可拖动
  showLine: boolean; // 是否显示canvas中的线
  cursor: string; // canvas中鼠标的形状
  showValTip: boolean; // 是否显示数据提示,如果设置为true,则需要调用实例的pointVal方法传值
  valTipStyle: {
    // 数据提示的样式
    fontSize: string;
    color: string;
    backgroundColor: string;
    padding: string;
    offset: number; // 提示框距离线的偏移量
  };
  regionStyle: {
    // 框选区的样式
    borderColor: string;
    opacity: number;
    backgroundColor: string;
  };
}

class createCanvas {
  options: options;
  tipVal: string;
  container: HTMLElement;
  H: number;
  W: number;
  canvas: any;
  ctx: CanvasRenderingContext2D;
  constructor(options) {
    options = Object.assign(
      {
        container: document.body, // 父容器DOM, 默认是body
        lineColor: "black", // canvas中画的线的颜色
        regionBg: "pink", // 框选区域的背景颜色
        regionCanMove: true, // 框选区是否可拖动
        showLine: true, // 是否显示canvas中的线
        cursor: "crosshair", // canvas中鼠标的形状
        showValTip: true, // 是否显示数据提示,如果设置为true,则需要调用实例的pointVal方法传值
        valTipStyle: {
          // 数据提示的样式
          fontSize: "12px",
          color: "pink",
          backgroundColor: "black",
          padding: "4px",
          offset: 2, // 提示框距离线的偏移量
        },
        regionStyle: {
          borderColor: "#49A4FF",
          opacity: 0.2,
          backgroundColor: "#fff",
        },
      },
      options
    );
    this.tipVal = "";
    this.options = options;
    let { container } = options;
    this.container = container || document.body;

    // 计算父级元素的宽高并保存到实例上以便后期调用
    this.H = this.container.clientHeight;
    this.W = this.container.clientWidth;

    // 初始化 canvas
    this.initCanvas(this.W, this.H);
  }

  // 创建canvas画布
  initCanvas(w, h) {
    w = w || this.W;
    h = h || this.H;
    let canvas: HTMLCanvasElement = document.createElement("canvas");
    canvas.width = w;
    canvas.height = h;
    canvas.style.position = "absolute";
    canvas.style.cursor = this.options.cursor;
    canvas.style.zIndex = "2";
    this.canvas = canvas;
    // 插入canvasDOM
    this.container.appendChild(canvas);
    this.ctx = this.canvas.getContext("2d");
    this.canvas.addEventListener("mouseenter", this.canvasEnter);
    this.canvas.addEventListener("mousedown", this.canvasDown);
  }

  // canvas进入时开始画线,并创建tipDOM
  canvasEnter = (ev) => {
    let valTip = document.createElement("span"),
      offset = this.options.valTipStyle.offset || 5;
    if (this.options.showValTip) {
      valTip.style.position = "absolute";
      valTip.style.whiteSpace = "nowrap";
      let { fontSize, color, backgroundColor, padding } =
        this.options.valTipStyle;
      valTip.style.fontSize = fontSize || "12px";
      valTip.style.color = color || "black";
      valTip.style.padding = padding || "4px";
      valTip.style.backgroundColor = backgroundColor || "pink";
      valTip.style.borderRadius = "2px";
      valTip.innerHTML = this.tipVal || "提示框";
      this.canvas.valTip = valTip;
      this.container.appendChild(this.canvas.valTip);
    } else {
      valTip = null;
    }

    let ctx = this.ctx;
    let W = this.canvas.width,
      H = this.canvas.height,
      lineColor = this.options.lineColor;
    // 鼠标进入canvas时画线
    const canvasMove = (ev) => {
      if (this.options.showLine) {
        // 每次画之前先清除之前的内容
        ctx.clearRect(0, 0, W, H);
        ctx.setLineDash([4, 2]); //线的样式
        // 画 Y 线
        ctx.stroke();
        ctx.beginPath();
        ctx.strokeStyle = lineColor; // 线的颜色
        ctx.moveTo(0, ev.offsetY);
        ctx.lineTo(this.W, ev.offsetY);
        // 画 X 线
        ctx.stroke();
        ctx.beginPath();
        ctx.strokeStyle = lineColor;
        ctx.moveTo(ev.offsetX, 0);
        ctx.lineTo(ev.offsetX, H);
        ctx.lineWidth = 1;
      }

      // 计算 valTip 的宽高,
      if (this.options.showValTip) {
        let valW = valTip.offsetWidth,
          valH = valTip.offsetHeight;
        this.canvas.valTip.style.top = ev.offsetY - 20 - offset + "px";
        this.canvas.valTip.style.left = ev.offsetX + offset + "px";
        if (ev.offsetY - this.canvas.offsetTop <= valH + offset) {
          this.canvas.valTip.style.top = ev.offsetY + offset + "px";
        }
        if (this.canvas.offsetWidth - ev.offsetX <= valW + offset) {
          this.canvas.valTip.style.left = ev.offsetX - offset - valW + "px";
        }
      }
    };

    // 鼠标移出canvas
    const canvasOut = (ev) => {
      ctx.clearRect(0, 0, W, H);
      this.options.showValTip ? this.canvas.valTip.remove() : null;
    };
    this.canvas.addEventListener("mousemove", canvasMove);
    this.canvas.addEventListener("mouseout", canvasOut);
  };

  // canvas鼠标按下的时候创建 regionDOM,并记录鼠标位置作为region的left
  canvasDown = (ev) => {
    ev.stopPropagation();
    if (this.canvas.region) {
      this.container.removeChild(this.canvas.region);
      delete this.canvas.region;
    }
    let region: any = document.createElement("div");
    region.style.position = "absolute";
    region.style.cursor = "move";
    region.className = "region";
    region.startX = ev.offsetX;
    region.endX = 0;
    region.style.width = "0";
    region.style.zIndex = "1";
    this.canvas.region = region;
    this.container.appendChild(this.canvas.region);
    this.canvas.addEventListener("mousemove", this.regionChange);

    let that = this;
    // canvas内鼠标抬起
    this.canvas.addEventListener("mouseup", (ev) => {
      that.canvas.removeEventListener("mousemove", that.regionChange);
      that.canvas.region.endX = ev.offsetX;
      that.canvas.region.style.zIndex = "1";
      if (that.options.regionCanMove) {
        that.canvas.region.style.zIndex = "3";
        that.canvas.region.addEventListener("mousedown", that.regionDown);
        that.canvas.region.addEventListener("mouseup", () => {
          that.canvas.region.removeEventListener("mousemove", that.regionMove);
        });
      }
    });
  };

  // 框选开始
  regionChange = (ev) => {
    let { borderColor, opacity, backgroundColor } = this.options.regionStyle;
    let canvas = this.canvas,
      region = canvas.region,
      H = canvas.height;
    region.endX = ev.offsetX + canvas.offsetLeft;
    region.style.backgroundColor = backgroundColor || "pink";
    region.style.opacity = opacity || 0.5;
    region.style.borderLeft =
      region.style.borderRight = `0.5px solid ${borderColor}`;
    region.style.top = 0 + "px";
    region.style.height = H + "px";
    if (region.endX > region.startX) {
      // 从左往右
      region.style.left = region.startX + "px";
      region.style.width = region.endX - region.startX + "px";
    } else {
      // 从右往左
      region.style.right = region.startX + "px";
      region.style.width = region.startX - region.endX + "px";
      region.style.left = region.endX + "px";
    }
  };

  // region开始拖动
  regionDown = (ev) => {
    ev.stopPropagation();
    let region = this.canvas.region,
      left = region.offsetLeft,
      cleft = ev.clientX - left;
    region.cleft = cleft;
    if (this.options.regionCanMove) {
      region.addEventListener("mousemove", this.regionMove);
      region.addEventListener("mouseout", () => {
        this.canvas.region.removeEventListener("mousemove", this.regionMove);
      });
    }
  };

  // region拖动中
  regionMove = (ev) => {
    let region = this.canvas.region,
      moveLeft = ev.clientX - region.cleft;
    // 判断可移动的区域
    if (moveLeft <= 0) {
      region.style.left = this.canvas.offsetLeft + "px";
    } else if (moveLeft + region.clientWidth > this.canvas.width) {
      region.style.left = this.canvas.width - region.clientWidth + "px";
    } else {
      region.style.left = moveLeft + "px";
    }
  };

  createRegion(left, width) {
    if (this.canvas.region) {
      this.container.removeChild(this.canvas.region);
      delete this.canvas.region;
    }
    let region: any = document.createElement("div");
    region.style.position = "absolute";
    region.style.cursor = "move";
    region.className = "region";
    region.startX = left;
    region.endX = width;

    region.style.left = left + "px";
    region.style.width = width + "px";
    region.style.height = this.canvas.height + "px";
    let { borderColor, opacity, backgroundColor } = this.options.regionStyle;
    region.style.backgroundColor = backgroundColor || "pink";
    region.style.opacity = opacity || 0.5;
    region.style.borderLeft =
      region.style.borderRight = `0.5px solid ${borderColor}`;
    region.style.zIndex = 1;
    this.canvas.region = region;
    this.container.appendChild(this.canvas.region);
    if (this.options.regionCanMove) {
      region.style.zIndex = 3;
      this.canvas.region.addEventListener("mousedown", this.regionDown);
      this.canvas.region.addEventListener("mouseup", () => {
        this.canvas.region.removeEventListener("mousemove", this.regionMove);
      });
    }
  }
  pointVal(val) {
    this.tipVal = val;
  }
}

export default createCanvas;