如何解决同时从两个不同的组件中拖动两个元素时出现苗条滞后
我一直在尝试构建一个简单的 svg 编辑器来试用 svelte。它一直很好,直到我为元素构建了一个选择框,以便与活动的选定元素一起拖动。 拖动所选元素时,选择框落后于元素本身。我不确定出了什么问题。
我尝试了一些方法,例如使用 store 传递位置数据并将事件放在父元素上,以便所有内容都在同一个组件上进行计算,以防万一这可能是问题,但仍然无法正常工作。我不确定我做错了什么。 我一直在尝试解决这个问题,但不知道可能是什么问题。
您可以在此处查看我的代码和框简化演示: codesandbox.io
<script lang="ts">
import ImageViewer from "../ImageViewer/ImageViewer.svelte";
import EditorControls from "../EditorControls/EditorControls.svelte";
import { app_data,app_state } from "../../stores";
import {
getBoundingBox,convertGlobalToLocalPosition,} from "../../helpers/svg";
import { elementData,elementDataRect } from "../../helpers/variables";
import { mousePointerLocation } from "../../helpers/mouse";
let activeElement = {
i: 0,bBox: {
x: 0,y: 0,width: 0,height: 0,},active: false,};
let elements = [{
type: 'rect',x: 100,y: 100,width: 400,height: 280,active: true,fill: 'rgba(0,1)',stroke: 'rgba(0,1)'
}];
let app_data_value;
const unsub_app_data = app_data.subscribe((value) => {
app_data_value = value;
});
let pos = elementData;
let posRect = elementDataRect;
let strokeWidth = 15;
let app_state_value;
const unsub_app_state = app_state.subscribe((value) => {
app_state_value = { ...value };
});
let moving = app_state_value.action === "move" ? true : false;
let movePos;
let active = false;
const elementMoveDownHandler = (e) => {
if (
(e.button === 0 || e.button === -1) &&
app_data_value.tool.selected === "select"
) {
active = true;
let i = (e.target as SVGElement).getAttribute("data-obj-id");
if (!moving) {
e.target.setPointerCapture(e.pointerId);
let cursorpt: any = mousePointerLocation(e);
let bBox: any;
bBox = getBoundingBox(e.target);
const offset = {
x: e.clientX - bBox.left,y: e.clientY - bBox.top,};
movePos = {
...movePos,init_x: cursorpt.x,init_y: cursorpt.y,offset: {
x: offset.x,y: offset.y,type: app_data_value.tool.selected,};
let pt = convertGlobalToLocalPosition(e.target);
activeElement = {
...activeElement,i: parseInt(i),bBox: {
x: pt.x,y: pt.y,width: bBox.width,height: bBox.height,};
moving = true;
}
}
};
const elementMoveMoveHandler = (e) => {
if (
e.button === 0 ||
(e.button === -1 && app_data_value.tool.selected === "select")
) {
active = true;
let i = (e.target as SVGElement).getAttribute("data-obj-id");
if (moving) {
let cursorpt: any;
let bBox: any;
bBox = getBoundingBox(e.target);
cursorpt = mousePointerLocation(e);
const offset = {
x: e.clientX - bBox.left,};
let j;
switch (e.target.nodeName) {
case "rect":
j = [...elements]
j[i]["x"] =
elements[i]["x"] - (movePos.offset.x - offset.x);
j[i]["y"] =
elements[i]["y"] - (movePos.offset.y - offset.y);
elements = j;
break;
default:
break;
}
// elements = elements;
movePos = {
...movePos,move_x: cursorpt.x,move_y: cursorpt.y,};
// activeElement = activeElement;
app_state.update((j) => {
j.action = "move";
return j;
});
}
}
};
const elementMoveUpHandler = (e) => {
moving = false;
app_state.update((j) => {
j.action = "standby";
return j;
});
e.target.releasePointerCapture(e.pointerId);
};
</script>
<div
on:pointerdown={(e) => {
elementMoveDownHandler(e);
}}
on:pointerup={(e) => {
if (active) {
elementMoveUpHandler(e);
}
}}
on:pointermove={(e) => {
if (active) {
elementMoveMoveHandler(e);
}
}}
>
<ImageViewer {strokeWidth} {elements} />
<EditorControls {pos} {posRect} {activeElement} />
</div>
<style lang="scss">
@import "./SVGEditor.scss";
</style>
<script lang="">
import { app_data } from "../../stores";
export let strokeWidth;
export let elements;
let app_data_value;
const unsub_app_data = app_data.subscribe((value) => {
app_data_value = value;
});
</script>
<svg
id="image-viewer"
width={app_data_value.doc_size.width}
height={app_data_value.doc_size.height}
>
{#each elements as item,i}
{#if typeof item === "object"}
{#if "type" in item}
{#if item.type === "line"}
{#if "x1" in item && "y1" in item && "x2" in item && "y2" in item}
<line
x1={item.x1}
y1={item.y1}
x2={item.x2}
y2={item.y2}
stroke="black"
stroke-width={strokeWidth}
data-obj-id={i}
/>
{/if}
{/if}
{#if item.type === "rect"}
{#if "x" in item && "y" in item && "width" in item && "height" in item}
<rect
x={item.x}
y={item.y}
width={item.width}
height={item.height}
stroke="black"
stroke-width={strokeWidth}
data-obj-id={i}
/>
{/if}
{/if}
{/if}
{/if}
{/each}
</svg>
<style lang="scss">
@import "./ImageViewer.scss";
</style>
<script lang="ts">
import { app_data } from "../../stores";
import SelectCtrl from "../SelectCtrl/SelectCtrl.svelte";
export let pos;
export let posRect;
export let activeElement;
let app_data_value;
const unsub_app_data = app_data.subscribe((value) => {
app_data_value = value;
});
let active;
$: active = activeElement.active;
let strokeWidth = 2;
</script>
<svg
id="editor-controls"
width={app_data_value.doc_size.width}
height={app_data_value.doc_size.height}
>
{#if active}
<SelectCtrl activeElement={activeElement} strokeWidth={2}/>
{/if}
</svg>
<style lang="scss">
@import "./EditorControls.scss";
</style>
<script lang="typescript">
export let activeElement;
export let strokeWidth;
let x = 0;
let y = 0;
let width = 0;
let height = 0;
$: x = activeElement.bBox.x;
$: y = activeElement.bBox.y;
$: width = activeElement.bBox.width;
$: height = activeElement.bBox.height;
let fill = 'rgba(0,0)';
let stroke = '#246bf0';
let strokeWidthMain;
$: strokeWidthMain = strokeWidth*2;
</script>
<g
class="selector-parent-group"
>
<g
class="selection-Box"
>
<rect
class="bounding-Box"
x={x}
y={y}
width={width}
height={height}
fill={fill}
stroke={stroke}
stroke-width={strokeWidthMain}
/>
<rect
class="bounding-Box-light"
x={x-strokeWidthMain}
y={y-strokeWidthMain}
width={width+strokeWidthMain*2}
height={height+strokeWidthMain*2}
fill={fill}
stroke={'#B9B9B9'}
stroke-width={strokeWidthMain}
/>
</g>
</g>
编辑:我没想到要为 convertGlobalToLocalPosition 和 getBoundingBox 函数添加代码,但由于解决了我的问题的答案,如果我将该代码添加为,它将更好地说明我遇到的问题嗯。
export function convertGlobalToLocalPosition(element: any) {
if (!element) return { x: 0,y: 0 };
if (typeof element.ownerSVGElement === 'undefined') return { x: 0,y: 0 };
var svg = element.ownerSVGElement;
// Get the cx and cy coordinates
var pt = svg.createSVGPoint();
let BoxParent = getBoundingBox(svg);
let Box = getBoundingBox(element);
pt.x = Box.x - BoxParent.x;
pt.y = Box.y - BoxParent.y;
while (true) {
// Get this elementents transform
var transform = element.transform.baseVal.consolidate();
// If it has a transform,then apply it to our point
if (transform) {
var matrix = element.transform.baseVal.consolidate().matrix;
pt = pt.matrixTransform(matrix);
}
// If this elementent's parent is the root SVG elementent,then stop
if (element.parentNode == svg)
break;
// Otherwise step up to the parent elementent and repeat the process
element = element.parentNode;
}
return pt;
}
export function getBoundingBox(el: any) {
let computed: any = window.getComputedStyle(el);
let strokeWidthCalc: string = computed['stroke-width'];
let strokeWidth: number = 0;
if (strokeWidthCalc.includes('px')) {
strokeWidth = parseFloat(strokeWidthCalc.substring(0,strokeWidthCalc.length - 2));
} else {
// examine value further
}
let boundingClientRect = el.getBoundingClientRect();
let bBox = el.getBBox();
if (boundingClientRect.width === bBox.width) {
boundingClientRect.x -= strokeWidth / 2;
boundingClientRect.y -= strokeWidth / 2;
boundingClientRect.width += strokeWidth;
boundingClientRect.height += strokeWidth;
}
return boundingClientRect;
}
解决方法
我认为这是导致您出现问题的原因:
在 elementMoveMoveHandler
中,您正在此处更新元素的位置:
j = [...elements];
j[i]["x"] = elements[i]["x"] - (movePos.offset.x - offset.x);
j[i]["y"] = elements[i]["y"] - (movePos.offset.y - offset.y);
elements = j;
之后,您将在 convertGlobalToLocalPosition
函数中从 DOM 中读取位置。 Svelte 将批量 DOM 更新,它没有时间更新 DOM 元素。因此 convertGlobalToLocalPosition
会给你一个旧值。最简单的解决方法是在 await tick();
之前添加 let pt = convertGlobalToLocalPosition(e.target);
并使 elementMoveMoveHandler
异步。
您可以在此处阅读有关刻度函数的更多信息:https://svelte.dev/tutorial/tick
还有一些建议:
-
您无需手动订阅商店或调用更新功能。您可以使用
$
符号,Svelte 会为您处理订阅、取消订阅和通知订阅者。 https://svelte.dev/tutorial/auto-subscriptions -
您当然可以在 Svelte 中使用不变性,但这不是必需的。就我个人而言,我发现可变代码更具可读性。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。