不知道为啥我上一篇的图片热区被移出首页,其实我主要是是为了自己看,其次才是分享,这段时间我又整理了一下热区,将里面的功能进一步完善了一下;
解决一下问题:
1.解决拖拽到规定区域外后松开鼠标再次进入后,坐标错误的问题
2.新增6个方向的拖拽放大缩小热区区域
3.新增放大了图片区域
4.做了相对于屏幕大小进行的百分比定位,解决因屏幕大小导致的定位错误问题
5.新增了新增热区功能,可手动点击按钮生成热区
6.解决了热区贴边不准确的问题
<!-- 上传组件 --> <template> <el-dialog v-model="dialog_visible" fullscreen @close="close_event"> <template #header> <div class="title re"> <div class="tc size-16 fw">编辑热区</div> </div> </template> <el-scrollbar class="content-scrollbar"> <div class="pa-40 flex-row gap-40"> <div class="left-content flex-1 pa-20"> <el-scrollbar class="img-scrollbar"> <div class="img-container"> <div class="re"> <div ref="imgBoxRef" class="oh" @mousedown.prevent="start_drag" @mousemove.prevent="move_drag" @mouseup.prevent="end_drag"> <div ref="imgRef"> <el-image :src="hot_list.img" class="w img block" @selectstart.prevent @contextmenu.prevent @dragstart.prevent></el-image> </div> <div ref="areaRef" class="area" :style="init_drag_style"></div> <div v-for="(item, index) in hot_list.data" :key="index" class="area-box" :style="rect_style(item.drag_start, item.drag_end)" @mousedown.stop="start_drag_area_box(index, $event)" @dblclick="dbl_drag_event(item, index)"> <div class="del-btn" @click.stop="del_area_event(index)"><icon name="close"></icon></div> <div class="drag-btn drag-tl" :data-index="index" @mousedown.stop="start_drag_btn_tl(index, $event)"></div> <div class="drag-btn drag-tc" :data-index="index" @mousedown.stop="start_drag_btn_tc(index, $event)"></div> <div class="drag-btn drag-lc" :data-index="index" @mousedown.stop="start_drag_btn_lc(index, $event)"></div> <div class="drag-btn drag-bl" :data-index="index" @mousedown.stop="start_drag_btn_bl(index, $event)"></div> <div class="drag-btn drag-bc" :data-index="index" @mousedown.stop="start_drag_btn_bc(index, $event)"></div> <!-- 已完成 --> <div class="drag-btn drag-br" :data-index="index" @mousedown.stop="start_drag_btn_br(index, $event)"></div> <div class="drag-btn drag-rc" :data-index="index" @mousedown.stop="start_drag_btn_rc(index, $event)"></div> <div class="text"> <div class="name">{{ item.name }}</div> <div class="status" :class="!is_obj_empty(item.link) ? 'cr-primary' : 'cr-error'">{{ !is_obj_empty(item.link) ? (item.link?.name ?? '未设置') : '未设置' }}</div> </div> </div> </div> </div> </div> </el-scrollbar> </div> <div class="right-content flex-1 pa-20"> <div class="size-16 fw mb-10">图片热区</div> <div class="flex-col gap-20 item"> <div v-for="(item, index) in hot_list.data" :key="index" class="flex-row align-c gap-10"> <el-input v-model="item.name" class="name" placeholder="名称"></el-input> <url-value v-model="item.link"></url-value> <icon name="del" size="20" @click="del_event(index)"></icon> </div> <el-button type="primary" class="add_hot" @click="add_event">添加选区</el-button> <div class="size-12 cr-9">框选热区范围,双击设置热区信息</div> </div> </div> </div> </el-scrollbar> <template #footer> <span class="dialog-footer"> <el-button class="plr-28 ptb-10" type="primary" @click="confirm_event">完成</el-button> </span> </template> </el-dialog> <el-dialog v-model="hot_dialog_visible" width="560" append-to-body draggable @close="hot_close_event"> <template #header> <div class="title re"> <div class="tc size-16 fw">设置热区</div> </div> </template> <div class="content"> <el-form ref="formRef" :model="form" label-width="85px" class="pa-20 mt-16"> <el-form-item label="名称"> <el-input v-model="form.name" placeholder="请输入名称"></el-input> </el-form-item> <el-form-item label="热区跳转链接"> <url-value v-model="form.link"></url-value> </el-form-item> </el-form> </div> <template #footer> <span class="dialog-footer"> <el-button class="plr-28 ptb-10" @click="hot_close_event">取消</el-button> <el-button class="plr-28 ptb-10" type="primary" @click="hot_confirm_event">确定</el-button> </span> </template> </el-dialog> <el-button class="w" @click="open_hot_event"><icon name="add">编辑热区</icon></el-button> </template> <script lang="ts" setup> import { cloneDeep } from 'lodash'; import { is_obj_empty } from '@/utils'; const app = getCurrentInstance(); /** * @description: 热区 * @param modelValue{Object} 默认值 * @param dialog_visible {Boolean} 弹窗显示 * @return {*} update:modelValue */ const props = defineProps({}); const modelValue = defineModel({ type: Object as PropType<hotData>, default: {} }); const dialog_visible = defineModel('visibleDialog', { type: Boolean, default: false }); const hot_list = ref<hotData>({ img: '', img_height: 1, img_width: 1, data: [], }); const hot_list_index = ref(0); //#region 左侧画布-----------------------------------------------start const imgBoxRef = ref<HTMLElement | null>(null); const imgRef = ref<HTMLElement | null>(null); const rect_start = ref<rectCoords>({ x: 0, y: 0, width: 0, height: 0 }); const rect_end = ref<rectCoords>({ x: 0, y: 0, width: 0, height: 0 }); const areaRef = ref<HTMLElement | null>(null); const init_drag_style = ref(''); // 拖拽生成盒子的开关 const drag_bool = ref(false); // 拖拽盒子的开关 const drag_box_bool = ref(false); // 拖拽放大缩小盒子的开关 const drag_box_scale_bool = ref(false); const start_drag = (event: MouseEvent) => { drag_bool.value = true; if (!imgBoxRef.value) return; rect_start.value.x = rect_start.value.x !== 0 ? rect_start.value.x : event.clientX - imgBoxRef.value.getBoundingClientRect().left; rect_start.value.y = rect_start.value.y !== 0 ? rect_start.value.y : event.clientY - imgBoxRef.value.getBoundingClientRect().top; rect_start.value.width = 0; rect_start.value.height = 0; }; const move_drag = (event: MouseEvent) => { if (drag_bool.value) { if (!imgBoxRef.value) return; rect_end.value.x = event.clientX - imgBoxRef.value.getBoundingClientRect().left; rect_end.value.y = event.clientY - imgBoxRef.value.getBoundingClientRect().top; rect_end.value.width = rect_end.value.x - rect_start.value.x > 0 ? rect_end.value.x - rect_start.value.x : 0; rect_end.value.height = rect_end.value.y - rect_start.value.y > 0 ? rect_end.value.y - rect_start.value.y : 0; init_drag_style.value = `left: ${rect_start.value.x}px;top: ${rect_start.value.y}px;width: ${Math.max(rect_end.value.width, 1)}px;height: ${Math.max(rect_end.value.height, 1)}px;display: flex;`; } }; const end_drag = (event: MouseEvent) => { drag_bool.value = false; if (areaRef.value) areaRef.value.style.display = 'none'; if (!imgBoxRef.value) return; init_drag_style.value = ``; if (rect_end.value.width > 16 && rect_end.value.height > 16) { hot_list.value.data.push({ name: '热区' + (hot_list.value.data.length + 1), link: {}, drag_start: cloneDeep(rect_start.value), drag_end: cloneDeep(rect_end.value), }); } rect_start.value = { x: 0, y: 0, width: 0, height: 0 }; rect_end.value = { x: 0, y: 0, width: 0, height: 0 }; }; const area_box_point = ref({ x: 0, y: 0 }); // area-box const dbl_drag_event = (item: hotListData, index: number) => { hot_dialog_visible.value = true; form.value.link = item.link; form.value.name = item.name; hot_list_index.value = index; }; const start_drag_area_box = (index: number, event: MouseEvent) => { hot_list_index.value = index; event.stopPropagation(); drag_box_bool.value = true; let clone_drag_start = cloneDeep(hot_list.value.data[hot_list_index.value].drag_start); let clone_drag_end = cloneDeep(hot_list.value.data[hot_list_index.value].drag_end); // 记录原始位置 area_box_point.value = { x: clone_drag_start.x - event.clientX, y: clone_drag_start.y - event.clientY, }; // 当子元素拖拽方法触发后父元素方法不触发 document.onmousemove = (areaBoxEvent) => { // areaBoxEvent.stopPropagation(); if (drag_box_bool.value) { if (!imgBoxRef.value) return; const new_coordinate = { x: areaBoxEvent.clientX + area_box_point.value.x, y: areaBoxEvent.clientY + area_box_point.value.y, }; // 左上边界判断 if (new_coordinate.x < 0) { new_coordinate.x = 0; } if (new_coordinate.y < 0) { new_coordinate.y = 0; } // 右下边界判断 if (new_coordinate.x + Math.max(clone_drag_end.width, 1) > imgBoxRef.value.getBoundingClientRect().width) { new_coordinate.x = imgBoxRef.value.getBoundingClientRect().width - Math.max(clone_drag_end.width, 1); } if (new_coordinate.y + Math.max(clone_drag_end.height, 1) > imgBoxRef.value.getBoundingClientRect().height) { new_coordinate.y = imgBoxRef.value.getBoundingClientRect().height - Math.max(clone_drag_end.height, 1); } hot_list.value.data[hot_list_index.value].drag_start.x = new_coordinate.x; hot_list.value.data[hot_list_index.value].drag_start.y = new_coordinate.y; hot_list.value.data[hot_list_index.value].drag_end.x = new_coordinate.x + Math.max(clone_drag_end.width, 1); hot_list.value.data[hot_list_index.value].drag_end.y = new_coordinate.y + Math.max(clone_drag_end.height, 1); } }; document.onmouseup = (areaBoxEvent) => { // areaBoxEvent.stopPropagation(); drag_box_bool.value = false; }; }; // drag-btn const start_drag_btn_br = (index: number, event: MouseEvent) => { start_drag_btn(index, event, 'br'); }; const start_drag_btn_bl = (index: number, event: MouseEvent) => { start_drag_btn(index, event, 'bl'); }; const start_drag_btn_bc = (index: number, event: MouseEvent) => { start_drag_btn(index, event, 'bc'); }; const start_drag_btn_tl = (index: number, event: MouseEvent) => { start_drag_btn(index, event, 'tl'); }; const start_drag_btn_tc = (index: number, event: MouseEvent) => { start_drag_btn(index, event, 'tc'); }; const start_drag_btn_lc = (index: number, event: MouseEvent) => { start_drag_btn(index, event, 'lc'); }; const start_drag_btn_rc = (index: number, event: MouseEvent) => { start_drag_btn(index, event, 'rc'); }; // 画布拖拽公用方法 const start_drag_btn = (index: number, event: MouseEvent, type: string) => { hot_list_index.value = index; event.stopPropagation(); drag_box_scale_bool.value = true; let clone_drag_start = hot_list.value.data[hot_list_index.value].drag_start; let clone_drag_end = hot_list.value.data[hot_list_index.value].drag_end; document.onmousemove = (dragBtnEvent) => { // dragBtnEvent.stopPropagation(); //用鼠标的位置减去鼠标相对元素的位置,得到元素的位置 if (drag_box_scale_bool.value) { if (!imgBoxRef.value) return; switch (type) { case 'br': // 下右 clone_drag_end.x = handleBoundary(dragBtnEvent.clientX - imgBoxRef.value.getBoundingClientRect().left, 0, imgBoxRef.value.getBoundingClientRect().width); clone_drag_end.y = handleBoundary(dragBtnEvent.clientY - imgBoxRef.value.getBoundingClientRect().top, 0, imgBoxRef.value.getBoundingClientRect().height); hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, clone_drag_end); break; case 'bl': // 下左 clone_drag_start.x = handleBoundary(dragBtnEvent.clientX - imgBoxRef.value.getBoundingClientRect().left, 0, clone_drag_end.x); clone_drag_end.y = handleBoundary(dragBtnEvent.clientY - imgBoxRef.value.getBoundingClientRect().top, 0, imgBoxRef.value.getBoundingClientRect().height); hot_list.value.data[hot_list_index.value].drag_start.x = clone_drag_start.x; hot_list.value.data[hot_list_index.value].drag_end.y = clone_drag_end.y; hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, { y: clone_drag_end.y }); break; case 'bc': // 下中 clone_drag_end.y = handleBoundary(dragBtnEvent.clientY - imgBoxRef.value.getBoundingClientRect().top, 0, imgBoxRef.value.getBoundingClientRect().height); hot_list.value.data[hot_list_index.value].drag_end.y = clone_drag_end.y; hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, { y: clone_drag_end.y }); break; case 'tl': // 上左 clone_drag_start.x = handleBoundary(dragBtnEvent.clientX - imgBoxRef.value.getBoundingClientRect().left, 0, clone_drag_end.x); clone_drag_start.y = handleBoundary(dragBtnEvent.clientY - imgBoxRef.value.getBoundingClientRect().top, 0, clone_drag_end.y); hot_list.value.data[hot_list_index.value].drag_start.x = clone_drag_start.x; hot_list.value.data[hot_list_index.value].drag_start.y = clone_drag_start.y; hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, {}); break; case 'tc': // 上中 clone_drag_start.y = handleBoundary(dragBtnEvent.clientY - imgBoxRef.value.getBoundingClientRect().top, 0, clone_drag_end.y); hot_list.value.data[hot_list_index.value].drag_start.y = clone_drag_start.y; hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, { y: clone_drag_end.y }); break; case 'lc': // 左中 clone_drag_start.x = handleBoundary(dragBtnEvent.clientX - imgBoxRef.value.getBoundingClientRect().left, 0, clone_drag_end.x); hot_list.value.data[hot_list_index.value].drag_start.x = clone_drag_start.x; hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, {}); break; case 'rc': // 右中 clone_drag_end.x = handleBoundary(dragBtnEvent.clientX - imgBoxRef.value.getBoundingClientRect().left, 0, imgBoxRef.value.getBoundingClientRect().width); hot_list.value.data[hot_list_index.value].drag_end.x = clone_drag_end.x; hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, { x: clone_drag_end.x }); break; } } }; document.onmouseup = (dragBtnEvent2) => { // dragBtnEvent2.stopPropagation(); drag_box_scale_bool.value = false; }; }; // 辅助函数用于更新drag_end const updateDragEnd = (dragStart: { x: number; y: number }, dragEnd: { x: number; y: number }, newDragEnd: { x?: number; y?: number }) => { const newX = newDragEnd.x !== undefined ? newDragEnd.x : dragEnd.x; const newY = newDragEnd.y !== undefined ? newDragEnd.y : dragEnd.y; return { x: newX, y: newY, width: newX - dragStart.x > 0 ? newX - dragStart.x : 0, height: newY - dragStart.y > 0 ? newY - dragStart.y : 0, }; }; // 辅助函数用于更新drag_start const updateDragStart = (dragStart: { x: number; y: number }, newDragStart: { x?: number; y?: number }) => { const newX = newDragStart.x !== undefined ? newDragStart.x : dragStart.x; const newY = newDragStart.y !== undefined ? newDragStart.y : dragStart.y; return { x: newX, y: newY }; }; // 辅助函数用于处理边界 const handleBoundary = (value: number, min: number, max: number) => Math.max(min, Math.min(value, max)); const del_area_event = (index: number) => { hot_list.value.data.splice(index, 1); }; const rect_style = computed(() => { return (start: rectCoords, end: rectCoords) => { return `left: ${start.x}px;top: ${start.y}px;width: ${Math.max(end.width, 1)}px;height: ${Math.max(end.height, 1)}px;display: flex;`; }; }); //#endregion 左侧画布-----------------------------------------------end //#region 右侧热区编辑-----------------------------------------------start const del_event = (index: number) => { hot_list.value.data.splice(index, 1); }; const add_event = () => { hot_list.value.data.push({ name: '热区' + (hot_list.value.data.length + 1), link: {}, drag_start: { x: 0, y: 0, width: 0, height: 0, }, drag_end: { x: 100, y: 100, width: 100, height: 100, }, }); }; //#endregion 右侧热区编辑-----------------------------------------------end //#region 设置热区弹窗-----------------------------------------------start const hot_dialog_visible = ref(false); interface formData { link: linkData; name: string; } const form = ref<formData>({ link: { name: '', }, name: '', }); const hot_close_event = () => { hot_dialog_visible.value = false; }; const hot_confirm_event = () => { hot_list.value.data[hot_list_index.value].name = form.value.name; if (hot_list.value.data[hot_list_index.value].link) { hot_list.value.data[hot_list_index.value].link = form.value.link; } hot_close_event(); }; //#endregion 设置热区弹窗-----------------------------------------------end //#region 热区开启关闭确认取消回调 -----------------------------------------------start // 打开热区弹窗 const open_hot_event = () => { if (modelValue.value.img.length > 0) { dialog_visible.value = true; hot_list.value.img = modelValue.value.img; setTimeout(() => { // 创建临时变量储存传过来的数据 let temp_data = cloneDeep(modelValue.value); // 获取最新的图片高度和宽度 temp_data.img_height = imgBoxRef.value?.clientHeight || 0; temp_data.img_width = imgBoxRef.value?.clientWidth || 0; // 根据原始数据的宽高和更新后的宽高的比例,计算出事实的坐标比例 const scale = temp_data.img_width / modelValue.value.img_width; console.log(scale); temp_data.data.forEach((item) => { item.drag_start.x = item.drag_start.x * scale; item.drag_start.y = item.drag_start.y * scale; item.drag_end.x = item.drag_end.x * scale; item.drag_end.y = item.drag_end.y * scale; item.drag_end.width = item.drag_end.width * scale; item.drag_end.height = item.drag_end.height * scale; }); hot_list.value = temp_data; }, 100); } else { ElMessage({ type: 'warning', message: '请先选择图片', }); } }; // 取消回调 const close_event = () => { dialog_visible.value = false; }; // 确认回调 const confirm_event = () => { if (hot_list.value.data.length > 0) { // 筛选数组hot中所有的link是否有空值,如果有则提示出来 if (is_obj_empty(hot_list.value.data)) { ElMessage({ type: 'warning', message: '请先设置热区', }); return; } const no_link_list = hot_list.value.data.filter((item) => { return is_obj_empty(item.link); }); if (no_link_list.length > 0) { ElMessage.error('请设置热区链接!'); return; } else { modelValue.value = cloneDeep(hot_list.value); close_event(); } } else { ElMessage.error('至少选择一个热区!'); } }; //#endregion 热区开启关闭确认取消回调 -----------------------------------------------end </script> <style lang="scss" scoped> @import 'index.scss'; </style>
标签:end,list,hot,value,js,start,drag,vue3,功能完善 From: https://www.cnblogs.com/sws-kevin/p/18367830