最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue鼠標右鍵畫矩形和Ctrl按鍵多選組件方式

 更新時間:2024年12月26日 09:02:40   作者:-小龍人  
文章介紹了一個Vue組件,該組件允許用戶通過鼠標右鍵在畫布上繪制矩形,并且支持通過Ctrl鍵進行多選,文章附帶了組件代碼和一個示例,建議讀者將代碼復(fù)制到自己的開發(fā)環(huán)境中進行調(diào)試

Vue鼠標右鍵畫矩形和Ctrl按鍵多選組件

效果圖

說明

下面會貼出組件代碼以及一個Demo,上面的效果圖即為Demo的效果,建議直接將兩份代碼拷貝到自己的開發(fā)環(huán)境直接運行調(diào)試。

組件代碼

<template>
  <!-- 鼠標畫矩形選擇對象 -->
  <div class="objects" ref="objectsRef" @mousedown="handleMouseDown">
    <!-- 矩形選擇框 -->
    <div
      class="mask"
      ref="maskRef"
      v-show="maskPosition.show"
      :style="
        'width:' +
        maskWidth +
        'left:' +
        maskLeft +
        'height:' +
        maskHeight +
        'top:' +
        maskTop
      "
    />

    <!-- 選擇對象內(nèi)容的目標插槽 -->
    <slot name="selcetObject" />
  </div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, ref, computed } from "vue";

const props = withDefaults(
  defineProps<{
    objectClassName: string; // 選擇對象的class name,用于定義如何獲取對象
    objectIdName: string; // 選擇對象的id name,用于定義如何獲取對象的id
    selectObjectIds?: Array<string>; // 選中的對象ID
    selectObjects?: Array<HTMLElement>; // 選中的對象
    useCtrlSelect?: boolean; // 是否支持按住Ctrl多選
  }>(),
  {
    useCtrlSelect: true // 默認支持按住Ctrl多選
  }
);

const objectsRef = ref();
const maskRef = ref();
const emits = defineEmits(["update:selectObjects", "update:selectObjectIds"]);
const state = reactive({
  maskPosition: {
    show: false,
    startX: 0,
    startY: 0,
    endX: 0,
    endY: 0
  }, // 矩形框位置
  isPressCtrlKey: false // 是否按下了Ctrl鍵
});
const { maskPosition, isPressCtrlKey } = toRefs(state);

// 若支持按住Ctrl多選,監(jiān)聽Ctrl事件
if (props.useCtrlSelect) {
  // 釋放
  document.addEventListener("keyup", event => {
    if (event.keyCode === 17) {
      isPressCtrlKey.value = false;
    }
  });
  // 按下
  document.addEventListener("keydown", event => {
    if (event.keyCode === 17) {
      isPressCtrlKey.value = true;
    }
  });
}

/** 鼠標按下 */
const handleMouseDown = event => {
  // 展示矩形框,通過坐標位置來畫出矩形
  maskPosition.value.show = true;
  maskPosition.value.startX = event.clientX;
  maskPosition.value.startY = event.clientY;
  maskPosition.value.endX = event.clientX;
  maskPosition.value.endY = event.clientY;
  // 監(jiān)聽鼠標移動事件和抬起離開事件
  objectsRef.value.addEventListener("mousemove", handleMouseMove);
  objectsRef.value.addEventListener("mouseup", handleMouseUp);
};

/** 鼠標移動 */
const handleMouseMove = event => {
  maskPosition.value.endX = event.clientX;
  maskPosition.value.endY = event.clientY;
};

/** 鼠標抬起離開 */
const handleMouseUp = () => {
  // 移除鼠標監(jiān)聽事件
  objectsRef.value.removeEventListener("mousemove", handleMouseMove);
  objectsRef.value.removeEventListener("mouseup", handleMouseUp);
  maskPosition.value.show = false;
  handleResetMaskPosition();
  handleGetSelectObject();
};

/** 獲取選擇的對象 */
const handleGetSelectObject = () => {
  // 選中對象ID和對象元素
  let tempSelectObjectIds: Array<string> = [];
  let tempSelectObjects: Array<HTMLElement> = [];

  // 如果按下了Ctrl鍵,之前選擇的數(shù)據(jù)不清空
  if (isPressCtrlKey.value) {
    tempSelectObjectIds =
      props.selectObjectIds === undefined ? [] : props.selectObjectIds;
    tempSelectObjects =
      props.selectObjects === undefined ? [] : props.selectObjects;
  }

  // 獲取鼠標畫出的矩形框位置
  const rectanglePosition = maskRef.value.getClientRects()[0];

  // 獲取所有選擇區(qū)域的對象; 這里獲取的元素的方式定義于父組件的objectClassName
  const selectedObjects = objectsRef.value.querySelectorAll(
    `.${props.objectClassName}`
  );
  // 遍歷對象,獲取到每個對象的坐標位置,判斷該位置是否在上面獲取到的鼠標畫矩形的框的位置中
  selectedObjects.forEach(item => {
    const objectPosition = item.getClientRects()[0];

    // 這里獲取的id的方式定義于父組件的objectIdName
    if (compareObjectPosition(objectPosition, rectanglePosition)) {
      const id = item.getAttribute(props.objectIdName);

      // 如果按下了Ctrl鍵
      if (isPressCtrlKey.value) {
        // 已被選中的需要被取消選中
        if (tempSelectObjectIds.includes(id)) {
          tempSelectObjectIds = tempSelectObjectIds.filter(a => a != id);
          tempSelectObjects = tempSelectObjects.filter(a => a != item);
        } else {
          tempSelectObjectIds.push(id);
          tempSelectObjects.push(item);
        }
      } else {
        tempSelectObjectIds.push(id);
        tempSelectObjects.push(item);
      }
    }
  });

  // 回傳到父組件
  emits("update:selectObjects", tempSelectObjects);
  emits("update:selectObjectIds", tempSelectObjectIds);
};

/**
 * 判斷對象坐標是否在鼠標畫出的矩形框坐標位置內(nèi)
 * @param objectPosition 對象坐標位置
 * @param rectanglePosition 鼠標畫出的矩形框坐標位置
 */
const compareObjectPosition = (objectPosition, rectanglePosition) => {
  const maxX = Math.max(
    objectPosition.x + objectPosition.width,
    rectanglePosition.x + rectanglePosition.width
  );
  const maxY = Math.max(
    objectPosition.y + objectPosition.height,
    rectanglePosition.y + rectanglePosition.height
  );
  const minX = Math.min(objectPosition.x, rectanglePosition.x);
  const minY = Math.min(objectPosition.y, rectanglePosition.y);
  return (
    maxX - minX <= objectPosition.width + rectanglePosition.width &&
    maxY - minY <= objectPosition.height + rectanglePosition.height
  );
};

/** 重置鼠標位置 */
const handleResetMaskPosition = () => {
  maskPosition.value.startX = 0;
  maskPosition.value.startY = 0;
  maskPosition.value.endX = 0;
  maskPosition.value.endY = 0;
};

/** 通過鼠標位置實時計算矩形框大小 */
const maskWidth = computed(() => {
  return `${Math.abs(maskPosition.value.endX - maskPosition.value.startX)}px;`;
});
const maskHeight = computed(() => {
  return `${Math.abs(maskPosition.value.endY - maskPosition.value.startY)}px;`;
});
const maskLeft = computed(() => {
  return `${Math.min(maskPosition.value.startX, maskPosition.value.endX)}px;`;
});
const maskTop = computed(() => {
  return `${Math.min(maskPosition.value.startY, maskPosition.value.endY)}px;`;
});
</script>
<style scoped lang="scss">
.objects {
  height: 100%;
  width: 100%;
  overflow-y: auto;

  .mask {
    position: fixed;
    background: #409eff;
    opacity: 0.4;
    z-index: 100;
  }
}
</style>

Demo

建議直接將上面組件命名為 MouseDrawRectangle

<template>
  <!------------- 鼠標畫矩形選擇對象組件DEMO,可以直接拷貝到你的頁面去運行----------------------->
  <div class="content">
    <!-- 
    MouseDrawRectangle說明:
    objectClassName綁定到下面對象class名稱; 
    objectIdName名稱對應(yīng)object_id;
    useCtrlSelect默認是打開的,用于按住Ctrl鍵進行多選,以及取消已選擇的對象。
    
    selectObjectIds會實時從子組件更新過來,監(jiān)聽它的值來控制頁面的選擇狀態(tài)即可。
    另外有參數(shù)selectObjects會實時從子組件傳回被選中的對象Dom信息
    -->
    <MouseDrawRectangle
      objectClassName="select_object"
      objectIdName="object_id"
      :useCtrlSelect="true"
      v-model:selectObjectIds="selectObjectIds"
      v-model:selectObjects="selectObjects"
    >
      <!-- 這個是插槽,將業(yè)務(wù)內(nèi)容的Dom限制在MouseDrawRectangle組件內(nèi),
      這樣可以將后面組件所有的監(jiān)聽事件綁定到組件上而不是整個頁面Dom上,
      鼠標滑動的區(qū)域也會限制死在組件內(nèi),而不是整個頁面的范圍 -->
      <template #selcetObject>
        <div class="objects_content">
          <!-- 每一個選擇的目標對象 -->
          <div
            v-for="item in 50"
            :key="item"
            class="select_object"
            :object_id="item"
            :class="
              selectObjectIds.includes(item.toString()) ? 'is_selected' : ''
            "
          >
            {{ item }}
          </div>
        </div>
      </template>
    </MouseDrawRectangle>
  </div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, watch } from "vue";
import MouseDrawRectangle from "@/components/objectSelect/mouseDrawRectangle.vue";

const state = reactive({
  selectObjectIds: [] as Array<string>, // 選中的對象ID
  selectObjects: [] as Array<HTMLElement> // 選中的對象DOM
});
const { selectObjectIds, selectObjects } = toRefs(state);

watch(
  () => [selectObjectIds.value, selectObjects.value],
  () => {
    console.log("選中的ID=>", selectObjectIds);
    console.log("選中的Dom=>", selectObjects);
  }
);
</script>
<style scoped lang="scss">
.content {
  // 因為使用flex布局,最下面一行盒子換行只會出現(xiàn)一半的高度,這里最好減去下每個盒子的高度
  height: calc(100% - 50px);
  overflow-y: auto;
  padding: 20px;

  .objects_content {
    user-select: none;
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 10px;

    // 盒子樣式
    > div {
      width: 200px;
      height: 100px;
      background-color: #999;
    }

    .is_selected {
      color: #fff;
      box-sizing: border-box;
      border: 3px #317aff solid;
      border-radius: 5px;
    }
  }
}
</style>

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • vue實現(xiàn)面包屑的方法

    vue實現(xiàn)面包屑的方法

    這篇文章主要為大家詳細介紹了vue實現(xiàn)面包屑的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Vue面試中observable原理詳解

    Vue面試中observable原理詳解

    這篇文章主要為大家介紹了vue面試observable原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • Vue版本vue2.9.6升級到vue3.0的詳細步驟

    Vue版本vue2.9.6升級到vue3.0的詳細步驟

    vue版本升級相信大家應(yīng)該都遇到過,下面這篇文章主要給大家介紹了關(guān)于Vue版本vue2.9.6升級到vue3.0的詳細步驟,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2022-09-09
  • v-show和v-if的區(qū)別?及應(yīng)用場景

    v-show和v-if的區(qū)別?及應(yīng)用場景

    這篇文章主要介紹了v-show和v-if的區(qū)別及應(yīng)用場景,vue中v-show與?v-if的作用效果是相同的,都能控制元素在頁面是否顯示,但是也有一定的區(qū)別,下面文章梳理總結(jié)v-show和v-if的區(qū)別,需要的小伙伴可以參考一下
    2022-06-06
  • Vite+Vue3使用MockJS的實現(xiàn)示例

    Vite+Vue3使用MockJS的實現(xiàn)示例

    寫一些純前端的項目時,自己造數(shù)據(jù)有些麻煩,于是我們可以利用mock造一些簡單的數(shù)據(jù),來滿足我們的需求,本文主要介紹了Vite+Vue3使用MockJS的實現(xiàn)示例,感興趣的可以了解一下
    2024-01-01
  • Vue中數(shù)組與對象修改觸發(fā)頁面更新的機制與原理解析

    Vue中數(shù)組與對象修改觸發(fā)頁面更新的機制與原理解析

    這篇文章主要介紹了Vue中關(guān)于數(shù)組與對象修改觸發(fā)頁面更新的機制與原理簡析,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-12-12
  • 在vue項目中正確使用iconfont的方法

    在vue項目中正確使用iconfont的方法

    今天小編就為大家分享一篇在vue項目中正確使用iconfont的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09
  • Vue實現(xiàn)table上下移動功能示例

    Vue實現(xiàn)table上下移動功能示例

    這篇文章主要介紹了Vue實現(xiàn)table上下移動功能,結(jié)合實例形式分析了vue.js針對table表格元素動態(tài)操作相關(guān)實現(xiàn)技巧,需要的朋友可以參考下
    2019-02-02
  • vue 使用自定義指令實現(xiàn)表單校驗的方法

    vue 使用自定義指令實現(xiàn)表單校驗的方法

    今天小編就為大家分享一篇vue 使用自定義指令實現(xiàn)表單校驗的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-08-08
  • iview日期控件,雙向綁定日期格式的方法

    iview日期控件,雙向綁定日期格式的方法

    下面小編就為大家分享一篇iview日期控件,雙向綁定日期格式的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03

最新評論

延长县| 平和县| 敖汉旗| 榆中县| 邹城市| 房山区| 阿鲁科尔沁旗| 郑州市| 河东区| 灵寿县| 肃宁县| 奉化市| 娱乐| 正安县| 蓬安县| 麻城市| 苏州市| 崇信县| 台江县| 仪陇县| 宁德市| 伽师县| 万山特区| 淳化县| 香河县| 江门市| 五华县| 西青区| 鱼台县| 武功县| 徐汇区| 九台市| 康保县| 凤阳县| 湖北省| 金昌市| 东乡| 晋中市| 康保县| 岳西县| 同江市|