svg - 同时从两个不同的组件中拖动两个元素时出现苗条滞后
问题描述
我一直在尝试构建一个简单的 svg 编辑器来尝试 svelte。它一直很好,直到我为元素构建了一个选择框,以便与活动的选定元素一起拖动。拖动所选元素时,选择框落后于元素本身。我不确定有什么问题。
我尝试了一些方法,例如使用商店传递位置数据并将事件放在父元素上,以便所有内容都在同一个组件上进行计算,以防万一这可能是问题但仍然无法正常工作。我不确定我做错了什么。我一直在尝试解决这个问题,但不知道可能是什么问题。
你可以在这里查看我的代码和框简化演示: 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, 0, 0, 1)',
stroke: 'rgba(0, 0, 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,
},
active: true,
};
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,
y: e.clientY - bbox.top,
};
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,
type: app_data_value.tool.selected,
};
let pt = convertGlobalToLocalPosition(e.target);
activeElement = {
...activeElement,
bbox: {
x: pt.x,
y: pt.y,
width: bbox.width,
height: bbox.height,
},
};
// 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,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 中使用不变性,但这不是必需的。就个人而言,我发现可变代码更具可读性。
推荐阅读
- ag-grid - 您可以在行选择中使用 AutoHeight 行吗
- python - 如何修复“连接到 localhost:6379 的错误 111。当我启动 rq-worker 时连接被拒绝?
- python-3.x - How to initialize an empty array that can accept any size?
- php - 如何检查我的 sql 表中的双重条目?
- ios - 在 UIButton 上结合扩展和自定义类会产生问题
- c++ - 通过命令行将 RCDATA 资源导入 Visual Studio 项目
- python - 使用给定项目拆分列表
- reactjs - React 中获取的下拉数据的第一项未发布
- rest - 在 REST 服务中返回异步 ElasticSearch 数据
- html - 如何将多个 div(及其子元素)显示为内联?