建设网站企业网上银行登录入口官方,推广普通话手抄报简单又好看,淄博网站建设yx718,.net做网站用mvc拖动移动元素
改变编辑器的定位系统
我们目前的元素都是按照块级元素直接自上而下的排列在画布中#xff0c;为了让元素实现精确的定位和调整#xff0c;我们需要改变这些元素的定位实现。我们需要让这些元素画布区域来进行绝对定位。如果我们有一个元素有这些已经保存的 c…拖动移动元素
改变编辑器的定位系统
我们目前的元素都是按照块级元素直接自上而下的排列在画布中为了让元素实现精确的定位和调整我们需要改变这些元素的定位实现。我们需要让这些元素画布区域来进行绝对定位。如果我们有一个元素有这些已经保存的 css 属性那么它就可以在编辑器或者是在另外的 H5 端渲染出这样的一个样式。
基本指导思想
交互的最终结果只是修改这些样式而已比如拖动定位最终就是在修改 top 和 left 的值而已那么缩放大小最终就是在修改 width 和 height 的值而已。
基本分析
1 拖动是在按下鼠标然后鼠标移动这个过程中发生的。所以首先我们要响应的是鼠标按下按下的时候也就是 MouseDown 的时候开始运作。 2 在鼠标移动的时候我们需要将 topleft 的值更新到新的值这个就是过程的重点。 结合交互图进行分析可以在线查看地址为https://whimsical.com/RTJphPrwzksyotCdA32LQUVsSo8s35WxESA3XwhpMUni 计算鼠标点下去元素偏移量
getBoundingClientRect Element.getBoundingClientRect() 方法返回一个 DOMRect 对象其提供了元素的大小及其相对于视口的位置。
templatediv classedit-wrapperrefeditWrapper:stylestylesmousedownstartMoveclickonItemClick(id) :class{ active: active, hidden: hidden }slot/slot/div
/templatescript langts
import { defineComponent, computed, ref } from vue
import { pick } from lodash-es
export default defineComponent({props: {id: {type: String,required: true},active: {type: Boolean,default: false},hidden: {type: Boolean,default: false},props: {type: Object}},emits: [set-active],setup(props, context) {const editWrapper refnull | HTMLElement(null)const onItemClick (id: string) {context.emit(set-active, id)}const gap {x: 0,y: 0}const styles computed(() pick(props.props, [position, top, left, width, height]))const startMove (e: MouseEvent) {const currentElement editWrapper.valueif (currentElement) {const { left, top } currentElement.getBoundingClientRect() gap.x e.clientX - leftgap.y e.clientY - topconsole.log(gap)}}return {onItemClick,styles,editWrapper,startMove}}
})
/scriptstyle
.edit-wrapper {padding: 0px;cursor: pointer;border: 1px solid transparent;user-select: none;
}
.edit-wrapper * {position: static !important;width: 100% !important;height: 100% !important;left: auto !important;top: auto !important;
}
.edit-wrapper:hover {border: 1px dashed #ccc;
}
.edit-wrapper.hidden {display: none;
}
.edit-wrapper.active {border: 1px solid #1890ff;user-select: none;z-index: 1500;
}
/style拖动移动实现元素移动
HTMLElement.offsetTop HTMLElement.offsetTop 为只读属性它返回当前元素相对于其 offsetParent 元素的顶部内边距的距离。和getBoundingClientRect有些类似 // EditWrapper.vue
divclassedit-wrapperrefeditWrapper:stylestyles:data-component-ididmousedownstartMoveclickonItemClick(id):class{ active: active, hidden: hidden }
/div// 在移动的过程中计算top和left的值
const caculateMovePosition (e: MouseEvent) {// 拿到画布最外层的dom元素(offsetLeft也可以使用Element.getBoundingClientRect())// 由于 canvas-area 元素的定位是fixed所以其offsetParent为null返回的值和 Element.getBoundingClientRect()是一样的const container document.getElementById(canvas-area) as HTMLElementconst left e.clientX - gap.x - container.offsetLeft;const top e.clientY - gap.y - container.offsetTopconsole.log(container.offsetParent);console.log(container.offsetLeft, container.getBoundingClientRect().left);return {left,top,};
};
const startMove (e: MouseEvent) {const currentElement editWrapper.value;if (currentElement) {const { left, top } currentElement.getBoundingClientRect();gap.x e.clientX - left;gap.y e.clientY - top;console.log(gap);}const handleMove (e: MouseEvent) {const { left, top } caculateMovePosition(e);console.log(left, top);if (currentElement) {currentElement.style.top top pxcurrentElement.style.left left px}};// 鼠标松开的时候做一些清除的工作const handleMouseUp () {document.removeEventListener(mousemove, handleMove)} document.addEventListener(mousemove, handleMove);document.addEventListener(mouseup, handleMouseUp);
};这里还是有个问题松开鼠标的时候位置恢复到了原来的位置。 原因是我们的数据流是自上而下的这个坐标值是从上面的属性中props中传递下来的我们现在是直接在样式中进行修改的所以当松开鼠标的时候原来的属性并没有进行修改就会回到原来的位置。现在需要在松开鼠标的时候发射一个事件触发对应的mutation更新鼠标的坐标值。
拖动移动更新元素属性
templatedivclassedit-wrapperrefeditWrapper:stylestylesmousedownstartMoveclickonItemClick(id):class{ active: active, hidden: hidden }slot/slot/div
/templatescript langts
// EditWrapper.vue
import { defineComponent, computed, ref } from vue;
import { pick } from lodash-es;
export default defineComponent({props: {id: {type: String,required: true,},active: {type: Boolean,default: false,},hidden: {type: Boolean,default: false,},props: {type: Object,},},emits: [set-active, update-position],setup(props, context) {const editWrapper refnull | HTMLElement(null);const onItemClick (id: string) {context.emit(set-active, id);};const gap {x: 0,y: 0,};const styles computed(() pick(props.props, [position, top, left, width, height]));const caculateMovePosition (e: MouseEvent) {const container document.getElementById(canvas-area) as HTMLElement;const left e.clientX - gap.x - container.offsetLeft;const top e.clientY - gap.y - container.offsetTop;return {left,top,};};// 这里添加这个标识主要是为了让鼠标只有在移动完成之后才能进行更新直接在元素上面进行点击触发一套mouseupmousedown动作是不需要更新的。let isMoving false;// mousedownstartMoveconst startMove (e: MouseEvent) {const currentElement editWrapper.value;if (currentElement) {const { left, top } currentElement.getBoundingClientRect();gap.x e.clientX - left;gap.y e.clientY - top;console.log(gap);}const handleMove (e: MouseEvent) {const { left, top } caculateMovePosition(e);isMoving true;console.log(left, top);if (currentElement) {currentElement.style.top top px;currentElement.style.left left px;}};const handleMouseUp (e: MouseEvent) {document.removeEventListener(mousemove, handleMove);if (isMoving) {const { left, top } caculateMovePosition(e);context.emit(update-position, { left, top, id: props.id });isMoving false;}// 做清理工作nextTick(() {document.removeEventListener(mouseup, handleMouseUp);
});};document.addEventListener(mousemove, handleMove);document.addEventListener(mouseup, handleMouseUp);};return {onItemClick,styles,editWrapper,startMove,};},
});
/scriptstyle
.edit-wrapper {padding: 0px;cursor: pointer;border: 1px solid transparent;user-select: none;
}
.edit-wrapper * {position: static !important;width: 100% !important;height: 100% !important;left: auto !important;top: auto !important;
}
.edit-wrapper:hover {border: 1px dashed #ccc;
}
.edit-wrapper.hidden {display: none;
}
.edit-wrapper.active {border: 1px solid #1890ff;user-select: none;z-index: 1500;
}
/style拖动改变大小
根本目的
改变大小最终的目的也是通过一系列的鼠标事件来改变一系列定位的值上一次我们改变的值只有 topleft现在还有有 width 和 height。
创建 handler
创建四个点就可以了分别位于这个图层的四个角上。 创建这四个 handler 应该不是很难我们只需要创建四个对应的 div将他们做成圆形然后让它们使用绝对定位设置 topleftrightbottom 值即可就可以创建出这样的一个样式。
添加事件
我们分别在四个点添加 mouseDownmouseMove然后到 mouseUp 的一系列事件完成整个过程。 之前在改变定位的过程中我们只需要在移动的时候改变 topleft 值即可现在拖动改变大小要比原来复杂一些还有 width 和 height 值的修改同时对于四个角度的拖动有不同的处理。
请看具体的交互图 拖动改变大小代码实现
实现右下方拖拽大小 首先对先择块的样式进行处理添四个点的css 样式
.edit-wrapper .resizers {display: none;
}
.edit-wrapper.active .resizers {display: block;
}
.edit-wrapper.active .resizers .resizer {width: 10px;height: 10px;border-radius: 50%;background: #fff;border: 3px solid #1890ff;position: absolute;
}
.edit-wrapper .resizers .resizer.top-left {left: -5px;top: -5px;cursor: nwse-resize;
}
.edit-wrapper .resizers .resizer.top-right {right: -5px;top: -5px;cursor: nesw-resize;
}
.edit-wrapper .resizers .resizer.bottom-left {left: -5px;bottom: -5px;cursor: nesw-resize;
}
.edit-wrapper .resizers .resizer.bottom-right {right: -5px;bottom: -5px;cursor: nwse-resize;
}接下来添加拖动右下脚圆点改区块大小最简单方法
// EditWrapper.vue
// 如果不给 resizer添加stop事件由于冒泡事件机制所以会冒泡到最外层editWrapper上面从而触发 startMove事件
divclassedit-wrapperrefeditWrapper:stylestyles:data-component-ididmousedownstartMoveclickonItemClick(id):class{ active: active, hidden: hidden }
slot/slotdiv classresizersdivclassresizer top-leftmousedown.stopstartResize(top-left)/divdivclassresizer top-rightmousedown.stopstartResize(top-right)/divdivclassresizer bottom-leftmousedown.stopstartResize(bottom-left)/divdivclassresizer bottom-rightmousedown.stopstartResize(bottom-right)/div/div
/divconst startResize () {const currentElement editWrapper.value;const handleMove (e: MouseEvent) {if (currentElement) {const { left, top } currentElement.getBoundingClientRect();currentElement.style.height e.clientY - top px;currentElement.style.width e.clientX - left px;}};const handleMouseUp () {document.removeEventListener(mousemove, handleMove);};document.addEventListener(mousemove, handleMove);document.addEventListener(mouseup, handleMouseUp);};我们已实现右下脚拖动改变
现在就是在其他几个方向重用这个方法进行尺寸改变
type ResizeDirection top-left | top-right | bottom-left | bottom-right
interface OriginalPositions {left: number;right: number;top: number;bottom: number;
}const caculateMovePosition (e: MouseEvent) {const container document.getElementById(canvas-area) as HTMLElement;const left e.clientX - gap.x - container.offsetLeft;const top e.clientY - gap.y - container.offsetTop;return {left,top,};};const caculateSize (direction: ResizeDirection,e: MouseEvent,positions: OriginalPositions) {const { clientX, clientY } e;const { left, right, top, bottom } positions;const container document.getElementById(canvas-area) as HTMLElement;const rightWidth clientX - left;const leftWidth right - clientX;const bottomHeight clientY - top;const topHeight bottom - clientY;const topOffset clientY - container.offsetTop;const leftOffset clientX - container.offsetLeft;switch (direction) {case top-left:return {width: leftWidth,height: topHeight,top: topOffset,left: leftOffset,};case top-right:return {width: rightWidth,height: topHeight,top: topOffset,};case bottom-left:return {width: leftWidth,height: bottomHeight,left: leftOffset,};case bottom-right:return {width: rightWidth,height: bottomHeight,};default:break;}};const startResize (direction: ResizeDirection) {const currentElement editWrapper.value as HTMLElement;const { left, right, top, bottom } currentElement.getBoundingClientRect();const handleMove (e: MouseEvent) {const size caculateSize(direction, e, { left, right, top, bottom });const { style } currentElement;if (size) {style.width size.width px;style.height size.height px;if (size.left) {style.left size.left px;}if (size.top) {style.top size.top px;}}};const handleMouseUp () {document.removeEventListener(mousemove, handleMove);};document.addEventListener(mousemove, handleMove);document.addEventListener(mouseup, handleMouseUp);};
数据更新 将变化数据发射出去
const handleMouseUp (e: MouseEvent) {document.removeEventListener(mousemove, handleMove);const size caculateSize(direction, e, { left, right, top, bottom });context.emit(update-position, { ...size, id: props.id})nextTick(() {document.removeEventListener(mouseup, handleMouseUp)})};修改Editor.vue中事件监听
const updatePosition (data: {left: number;top: number;id: string;
}) {const { id } data;const updatedData pickBynumber(data, (v,k) k ! id)forEach(updatedData, (v, key) {store.commit(updateComponent, { key, value: v px, id})})
};修复有滚动条时的Bug:
在contanier出现滚动条并且把滚动条滚动到下方将元素向上拖元素会出现向上的突然抖动会造成数据的错误。 最终的效果