永州網站開發(fā)商品seo優(yōu)化是什么意思
拖動移動元素
改變編輯器的定位系統(tǒng)
我們目前的元素都是按照塊級元素直接自上而下的排列在畫布中,為了讓元素實現精確的定位和調整,我們需要改變這些元素的定位實現。我們需要讓這些元素畫布區(qū)域來進行絕對定位。如果我們有一個元素有這些已經保存的 css 屬性,那么它就可以在編輯器,或者是在另外的 H5 端渲染出這樣的一個樣式。
基本指導思想
交互的最終結果只是修改這些樣式而已,比如拖動定位,最終就是在修改 top 和 left 的值而已,那么縮放大小,最終就是在修改 width 和 height 的值而已。
基本分析
1 拖動是在按下鼠標,然后鼠標移動這個過程中發(fā)生的。所以首先我們要響應的是鼠標按下按下的時候,也就是 MouseDown 的時候開始運作。
2 在鼠標移動的時候,我們需要將 top,left 的值更新到新的值,這個就是過程的重點。
結合交互圖進行分析:可以在線查看,地址為:https://whimsical.com/RTJphPrwzksyotCdA32LQU@VsSo8s35WxESA3XwhpMUni
計算鼠標點下去,元素偏移量:
getBoundingClientRect
Element.getBoundingClientRect() 方法返回一個 DOMRect 對象,其提供了元素的大小及其相對于視口的位置。
<template><div class="edit-wrapper"ref="editWrapper":style="styles"@mousedown="startMove"@click="onItemClick(id)" :class="{ active: active, hidden: hidden }"><slot></slot></div>
</template><script lang="ts">
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 = ref<null | 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}}
})
</script><style>
.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
<divclass="edit-wrapper"ref="editWrapper":style="styles":data-component-id="id"@mousedown="startMove"@click="onItemClick(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 + 'px'currentElement.style.left = left + 'px'}};// 鼠標松開的時候,做一些清除的工作const handleMouseUp = () => {document.removeEventListener('mousemove', handleMove)} document.addEventListener('mousemove', handleMove);document.addEventListener('mouseup', handleMouseUp);
};
這里還是有個問題:松開鼠標的時候,位置恢復到了原來的位置。
原因是我們的數據流是自上而下的,這個坐標值是從上面的屬性中props中傳遞下來的,我們現在是直接在樣式中進行修改的,所以當松開鼠標的時候,原來的屬性并沒有進行修改,就會回到原來的位置?,F在需要在松開鼠標的時候,發(fā)射一個事件,觸發(fā)對應的mutation
,更新鼠標的坐標值。
拖動移動更新元素屬性:
<template><divclass="edit-wrapper"ref="editWrapper":style="styles"@mousedown="startMove"@click="onItemClick(id)":class="{ active: active, hidden: hidden }"><slot></slot></div>
</template><script lang="ts">
// 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 = ref<null | 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,};};// 這里添加這個標識,主要是為了讓鼠標只有在移動完成之后才能進行更新,直接在元素上面進行點擊,觸發(fā)一套mouseup,mousedown動作,是不需要更新的。let isMoving = false;// @mousedown="startMove"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);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,};},
});
</script><style>
.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>
拖動改變大小
根本目的
改變大小最終的目的也是通過一系列的鼠標事件來改變一系列定位的值,上一次我們改變的值只有 top,left,現在還有有 width 和 height。
創(chuàng)建 handler
創(chuàng)建四個點就可以了,分別位于這個圖層的四個角上。
創(chuàng)建這四個 handler 應該不是很難,我們只需要創(chuàng)建四個對應的 div,將他們做成圓形,然后讓它們使用絕對定位,設置 top,left,right,bottom 值即可,就可以創(chuàng)建出這樣的一個樣式。
添加事件
我們分別在四個點,添加 mouseDown,mouseMove,然后到 mouseUp 的一系列事件,完成整個過程。
之前在改變定位的過程中,我們只需要在移動的時候改變 top,left 值即可,現在拖動改變大小要比原來復雜一些,還有 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;
}
- 接下來添加拖動右下腳圓點改區(qū)塊大小(最簡單方法)
// EditWrapper.vue
// 如果不給 resizer添加stop事件,由于冒泡事件機制,所以會冒泡到最外層editWrapper上面,從而觸發(fā) startMove事件
<divclass="edit-wrapper"ref="editWrapper":style="styles":data-component-id="id"@mousedown="startMove"@click="onItemClick(id)":class="{ active: active, hidden: hidden }"
><slot></slot><div class="resizers"><divclass="resizer top-left"@mousedown.stop="startResize('top-left')"></div><divclass="resizer top-right"@mousedown.stop="startResize('top-right')"></div><divclass="resizer bottom-left"@mousedown.stop="startResize('bottom-left')"></div><divclass="resizer bottom-right"@mousedown.stop="startResize('bottom-right')></div></div>
</div>const 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);};
- 數據更新
- 將變化數據發(fā)射出去:
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中事件監(jiān)聽
const updatePosition = (data: {left: number;top: number;id: string;
}) => {const { id } = data;const updatedData = pickBy<number>(data, (v,k) => k !== 'id')forEach(updatedData, (v, key) => {store.commit('updateComponent', { key, value: v + 'px', id})})
};
修復有滾動條時的Bug:
在contanier出現滾動條,并且把滾動條滾動到下方,將元素向上拖,元素會出現向上的突然抖動,會造成數據的錯誤。
最終的效果: