Vue 框选区域放大(纯JavaScript实现)

需求:长按鼠标左键框选区域,松开后放大该区域,继续框选继续放大,反向框选恢复原始状态
实现思路:根据鼠标的落点,放大要显示的内容(内层盒子),然后利用水平偏移和垂直偏移,让外层展示的窗口(外层盒子)只看到刚刚框选的大概区域,具体代码如下
复制代码
<template>
  <div>
    <div
      class="selectable_container"
      @mousedown="handleMouseDown"
      @mousemove="handleMouseMove"
      @mouseup="handleMouseUp"
    >
      <div
        class="zoomable_element"
        :style="{
          userSelect: 'none',
          left: innerLeft + 'px',
          top: innerTop + 'px',
          width: innerWidth + 'px',
          height: innerHeight + 'px',
        }"
      >
        <img
          src="./img/test1.jpg"
          style="
            width: 100%;
            height: 100%;
            user-select: none;
            pointer-events: none;
          "
          alt=""
        />
      </div>
      <div class="selectable_element" id="selectable_element"></div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      startX: 0,
      startY: 0,
      endX: 0,
      endY: 0,
      isSelecting: false, //是否正在款选
      closeFlag: false, //是否退出放大状态
      offsetinner_left: 0, //外层容器水平偏移
      offsetinner_top: 0, //外层容器垂直偏移

      outerWidth: 0, //外层盒子宽度
      outerHeight: 0, //外层盒子高度

      zoomRatio: 1,
      innerWidth: "100%", //内层盒子宽度 初始状态等于外层盒子
      innerHeight: "100%", //内层盒子高度
      innerTop: 0, //内层盒子垂直偏移
      innerLeft: 0, //内层盒子水平偏移

      selectionLeft: 0, //框选区域水平偏移
      selectionTop: 0, //框选区域垂直偏移
      selectionWidth: 0, //框选区域宽度
      selectionHeight: 0, //框选区域高度,
    };
  },
  mounted() {
    const dom_mask = window.document.querySelector(".selectable_container");
    const rect_select = dom_mask.getClientRects()[0];
    this.offsetinner_left = rect_select.left; //水平偏移
    this.offsetinner_top = rect_select.top; //垂直偏移
    this.outerWidth = Math.ceil(rect_select.width);
    this.outerHeight = Math.ceil(rect_select.height);
    this.innerWidth = this.outerWidth;
    this.innerHeight = this.outerHeight;
  },
  methods: {
    handleMouseDown(event) {
      if (event.button === 0) {
        // 判断是否为鼠标左键按下
        this.startX = event.clientX - this.offsetinner_left;
        this.startY = event.clientY - this.offsetinner_top;
        this.isSelecting = true;
        var dom = document.getElementById("selectable_element");
        if (dom) {
          dom.style.left = this.startX + "px";
          dom.style.top = this.startY + "px";
        }
      }
    },
    handleMouseMove(event) {
      if (this.isSelecting) {
        this.closeFlag = false;
        this.endX = event.clientX - this.offsetinner_left;
        this.endY = event.clientY - this.offsetinner_top;
        var selectionLeft, selectionTop, selectionWidth, selectionHeight;
        selectionWidth = Math.abs(this.endX - this.startX);
        selectionHeight = Math.abs(this.endY - this.startY);
        // 右下
        if (this.endY >= this.startY && this.endX >= this.startX) {
          selectionLeft = this.startX;
          selectionTop = this.startY;
        }
        // 左下
        else if (this.endY >= this.startY && this.endX <= this.startX) {
          selectionLeft = this.endX;
          selectionTop = this.startY;
        }
        // 右上
        else if (this.endY <= this.startY && this.endX >= this.startX) {
          selectionLeft = this.startX;
          selectionTop = this.endY;
        }
        // 左上
        else if (this.endY <= this.startY && this.endX <= this.startX) {
          selectionLeft = this.endX;
          selectionTop = this.endY;
          this.closeFlag = true;
        }
        selectionLeft = Math.ceil(selectionLeft);
        selectionTop = Math.ceil(selectionTop);
        selectionWidth = Math.ceil(selectionWidth);
        selectionHeight = Math.ceil(selectionHeight);
        var dom = document.getElementById("selectable_element");
        if (dom) {
          dom.style.left = selectionLeft + "px";
          dom.style.top = selectionTop + "px";
          dom.style.width = selectionWidth + "px";
          dom.style.height = selectionHeight + "px";
        }
        this.selectionLeft = 0 - this.innerLeft + selectionLeft;
        this.selectionTop = 0 - this.innerTop + selectionTop;
        this.selectionWidth = selectionWidth;
        this.selectionHeight = selectionHeight;
      }
    },
    handleMouseUp(event) {
      // 判断是否为鼠标左键松开
      if (event.button === 0 && this.isSelecting) {
        // 左上清除
        if (this.closeFlag) {
          this.isSelecting = false;
          this.closeFlag = false;

          var dom = document.getElementById("selectable_element");
          if (dom) {
            dom.style.left = "0px";
            dom.style.top = "0px";
            dom.style.width = "0px";
            dom.style.height = "0px";
          }

          this.innerWidth = this.outerWidth;
          this.innerHeight = this.outerHeight;
          this.innerLeft = 0;
          this.innerTop = 0;

          return;
        }
        this.isSelecting = false;
        this.zoomRatio = Math.min(
          this.outerWidth / this.selectionWidth,
          this.outerHeight / this.selectionHeight
        ).toFixed(2);
        this.zoomRatio = Number(this.zoomRatio);
        // console.log(this.zoomRatio);
        var innerWidth = Math.ceil(this.innerWidth * this.zoomRatio);
        var innerHeight = Math.ceil(this.innerHeight * this.zoomRatio);
        var innerLeft = 0 - this.selectionLeft * this.zoomRatio;
        var innerTop = 0 - this.selectionTop * this.zoomRatio;

        // 居中处理
        innerLeft =
          innerLeft +
          (this.outerWidth - this.selectionWidth * this.zoomRatio) / 2;
        innerTop =
          innerTop +
          (this.outerHeight - this.selectionHeight * this.zoomRatio) / 2;

        // 补位处理
        if (innerWidth + innerLeft < this.outerWidth) {
          // console.log("水平补位");
          innerLeft = innerLeft + this.outerWidth - (innerWidth + innerLeft);
        }
        if (innerHeight + innerTop < this.outerHeight) {
          // console.log("垂直补位");
          innerTop = innerTop + this.innerHeight - (innerHeight + innerTop);
        }

        this.innerWidth = innerWidth;
        this.innerHeight = innerHeight;
        this.innerLeft = innerLeft;
        this.innerTop = innerTop;

        var dom = document.getElementById("selectable_element");
        if (dom) {
          dom.style.left = "0px";
          dom.style.top = "0px";
          dom.style.width = "0px";
          dom.style.height = "0px";
        }
      }
    },
  },
};
</script>
<style lang="scss" scoped>
// 外层可视窗口
.selectable_container {
  position: relative;
  width: 800px;
  height: 450px;
  border: 1px solid #ccc;
  overflow: hidden;
}
// 框选动作临时盒子
.selectable_element {
  position: absolute;
  border: 1px solid red;
}
// 内层内容盒子 需要缩放
.zoomable_element {
  position: absolute;
  left: 0;
  top: 0;
}
</style>
相关推荐
JarvanMo2 分钟前
终于来了!Flutter 拥有了一个可用的液态玻璃解决方案!
前端
性野喜悲4 分钟前
<script setup lang=“ts“>+element-plus模拟required 展示星号*且不触发 Element UI 的校验规则
javascript·vue.js·elementui
b***748823 分钟前
前端技术的边界正在消失:迈向体验统一与智能化驱动的新阶段
前端
lvchaoq26 分钟前
解决组件不能远程搜索的问题
前端·bug
GIS好难学29 分钟前
2025年华中农业大学暑期实训优秀作品(5):智慧煤仓监控系统平台——重塑煤炭仓储管理新模式
前端·vue.js·信息可视化·gis开发·webgis
pixle038 分钟前
从零学习Node.js框架Koa 【七】Koa实战:构建企业级邮箱验证注册系统
javascript·学习·node.js·koa·注册·全栈·邮箱注册
Highcharts.js1 小时前
Highcharts Dashboards 官方文档|如何创建第一个看板
前端·开发文档·仪表板·highcharts·创建看板
小明记账簿1 小时前
解锁前端新技能:让JavaScript与CSS变量共舞
前端·javascript·css
pyniu1 小时前
redis day1
java·前端·spring
b***74881 小时前
从技术复杂度到体系竞争力:2026 年前端发展的全新范式转移
前端