使用 interact.js + SVG 中的手柄调整大小
Resizing with handles in interact.js + SVG
在这个 jsFiddle 中,我有一个用 interact.js
调整大小的 SVG 矩形。这工作正常,但我需要在每个点添加调整大小句柄 n/ne/e/se/s/sw/w/nw
,8x8 像素正方形。这些手柄应该用于调整矩形的大小(而不是拖动矩形的边)。
我在 HTML 中找到了示例,而不是 SVG,例如 here,但我无法弄清楚如何在 SVG 而不是 HTML 中实现它。任何帮助将不胜感激。
var svg = document.getElementById('mysvg');
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
svg.appendChild(rect);
rect.setAttribute('x', 100);
rect.setAttribute('y', 100);
rect.setAttribute('width', 100);
rect.setAttribute('height', 100);
rect.setAttribute('class', 'resize-me');
rect.setAttribute('stroke-width', 2);
rect.setAttribute('stroke', 'white');
rect.setAttribute('fill', 'grey')
interact('.resize-me')
.resizable({
edges: { left: true, right: true, bottom: true, top: true }
})
.on('resizemove', function(event) {
var target = event.target;
var x = (parseFloat(target.getAttribute('endx')) || 0)
var y = (parseFloat(target.getAttribute('endy')) || 0)
target.setAttribute('width', event.rect.width);
target.setAttribute('height', event.rect.height);
x += event.deltaRect.left
y += event.deltaRect.top
target.setAttribute('transform', 'translate(' + x + ', ' + y + ')')
target.setAttribute('endx', x)
target.setAttribute('endy', y)
});
好的,完成了。看看:
const svg = document.getElementById('mysvg');
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
svg.appendChild(group);
group.appendChild(rect);
group.setAttribute('class', 'resize-me');
rect.setAttribute('x', 100);
rect.setAttribute('y', 100);
rect.setAttribute('width', 100);
rect.setAttribute('height', 100);
rect.setAttribute('stroke-width', 2);
rect.setAttribute('stroke', 'white');
rect.setAttribute('fill', 'grey');
// Create the handles
const handles = [];
for (let i = 0; i < 8; i++) {
const handle = document.createElementNS("http://www.w3.org/2000/svg", "rect");
handle.setAttribute('width', 8);
handle.setAttribute('height', 8);
handle.setAttribute('stroke-width', 1);
handle.setAttribute('stroke', 'white');
handle.setAttribute('fill', 'black');
handles.push(handle);
group.appendChild(handle);
}
// Manually assign them their resize duties (R->L, T->B)
handles[0].classList.add('resize-top', 'resize-left');
handles[1].classList.add('resize-top');
handles[2].classList.add('resize-top', 'resize-right');
handles[3].classList.add('resize-left');
handles[4].classList.add('resize-right');
handles[5].classList.add('resize-bottom', 'resize-left');
handles[6].classList.add('resize-bottom');
handles[7].classList.add('resize-bottom', 'resize-right');
// This function takes the rect and the list of handles and positions
// the handles accordingly
const findLocations = (r, h) => {
const x = Number(r.getAttribute('x'));
const y = Number(r.getAttribute('y'));
const width = Number(r.getAttribute('width'));
const height = Number(r.getAttribute('height'));
// Important these are in the same order as the classes above
let locations = [
[0, 0],
[width / 2, 0],
[width, 0],
[0, height / 2],
[width, height / 2],
[0, height],
[width / 2, height],
[width, height]
];
// Move each location such that it's relative to the (x,y) of the rect,
// and also subtract half the width of the handles to make up for their
// own size.
locations = locations.map(subarr => [
subarr[0] + x - 4,
subarr[1] + y - 4
]);
for (let i = 0; i < locations.length; i++) {
h[i].setAttribute('x', locations[i][0]);
h[i].setAttribute('y', locations[i][1]);
}
}
interact('.resize-me')
.resizable({
edges: {
left: '.resize-left',
right: '.resize-right',
bottom: '.resize-bottom',
top: '.resize-top'
}
})
.on('resizemove', function(event) {
// Resize the rect, not the group, it will resize automatically
const target = event.target.querySelector('rect');
for (const attr of ['width', 'height']) {
let v = Number(target.getAttribute(attr));
v += event.deltaRect[attr];
target.setAttribute(attr, v);
}
for (const attr of ['top', 'left']) {
const a = attr == 'left' ? 'x' : 'y';
let v = Number(target.getAttribute(a));
v += event.deltaRect[attr];
target.setAttribute(a, v);
}
findLocations(rect, handles);
});
findLocations(rect, handles);
svg {
width: 100%;
height: 240px;
background-color: #2e9;
-ms-touch-action: none;
touch-action: none;
}
body {
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/interactjs@latest/dist/interact.min.js"></script>
<svg id="mysvg"></svg>
这通过利用 Interact.js 对使用单独元素作为句柄的支持来实现,如他们的文档 here 中所述,以及您正在使用 SVG 的有趣事实HTML。这意味着您不必担心使用 CSS transform
。相反,您只需移动 rect
的 x
和 y
,计算就会变得相当简单。
我必须做的唯一 easy-to-miss 更改是我必须将矩形和手柄放在一个组中。这是因为 Interact.js 只允许您使用 child 元素作为调整大小的句柄。这意味着听众在组中,它会调整自身内部矩形的大小(然后,当然,会导致组增长,因为它们与 children 的大小相匹配)。
如果我遗漏了什么,请告诉我。
在这个 jsFiddle 中,我有一个用 interact.js
调整大小的 SVG 矩形。这工作正常,但我需要在每个点添加调整大小句柄 n/ne/e/se/s/sw/w/nw
,8x8 像素正方形。这些手柄应该用于调整矩形的大小(而不是拖动矩形的边)。
我在 HTML 中找到了示例,而不是 SVG,例如 here,但我无法弄清楚如何在 SVG 而不是 HTML 中实现它。任何帮助将不胜感激。
var svg = document.getElementById('mysvg');
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
svg.appendChild(rect);
rect.setAttribute('x', 100);
rect.setAttribute('y', 100);
rect.setAttribute('width', 100);
rect.setAttribute('height', 100);
rect.setAttribute('class', 'resize-me');
rect.setAttribute('stroke-width', 2);
rect.setAttribute('stroke', 'white');
rect.setAttribute('fill', 'grey')
interact('.resize-me')
.resizable({
edges: { left: true, right: true, bottom: true, top: true }
})
.on('resizemove', function(event) {
var target = event.target;
var x = (parseFloat(target.getAttribute('endx')) || 0)
var y = (parseFloat(target.getAttribute('endy')) || 0)
target.setAttribute('width', event.rect.width);
target.setAttribute('height', event.rect.height);
x += event.deltaRect.left
y += event.deltaRect.top
target.setAttribute('transform', 'translate(' + x + ', ' + y + ')')
target.setAttribute('endx', x)
target.setAttribute('endy', y)
});
好的,完成了。看看:
const svg = document.getElementById('mysvg');
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
svg.appendChild(group);
group.appendChild(rect);
group.setAttribute('class', 'resize-me');
rect.setAttribute('x', 100);
rect.setAttribute('y', 100);
rect.setAttribute('width', 100);
rect.setAttribute('height', 100);
rect.setAttribute('stroke-width', 2);
rect.setAttribute('stroke', 'white');
rect.setAttribute('fill', 'grey');
// Create the handles
const handles = [];
for (let i = 0; i < 8; i++) {
const handle = document.createElementNS("http://www.w3.org/2000/svg", "rect");
handle.setAttribute('width', 8);
handle.setAttribute('height', 8);
handle.setAttribute('stroke-width', 1);
handle.setAttribute('stroke', 'white');
handle.setAttribute('fill', 'black');
handles.push(handle);
group.appendChild(handle);
}
// Manually assign them their resize duties (R->L, T->B)
handles[0].classList.add('resize-top', 'resize-left');
handles[1].classList.add('resize-top');
handles[2].classList.add('resize-top', 'resize-right');
handles[3].classList.add('resize-left');
handles[4].classList.add('resize-right');
handles[5].classList.add('resize-bottom', 'resize-left');
handles[6].classList.add('resize-bottom');
handles[7].classList.add('resize-bottom', 'resize-right');
// This function takes the rect and the list of handles and positions
// the handles accordingly
const findLocations = (r, h) => {
const x = Number(r.getAttribute('x'));
const y = Number(r.getAttribute('y'));
const width = Number(r.getAttribute('width'));
const height = Number(r.getAttribute('height'));
// Important these are in the same order as the classes above
let locations = [
[0, 0],
[width / 2, 0],
[width, 0],
[0, height / 2],
[width, height / 2],
[0, height],
[width / 2, height],
[width, height]
];
// Move each location such that it's relative to the (x,y) of the rect,
// and also subtract half the width of the handles to make up for their
// own size.
locations = locations.map(subarr => [
subarr[0] + x - 4,
subarr[1] + y - 4
]);
for (let i = 0; i < locations.length; i++) {
h[i].setAttribute('x', locations[i][0]);
h[i].setAttribute('y', locations[i][1]);
}
}
interact('.resize-me')
.resizable({
edges: {
left: '.resize-left',
right: '.resize-right',
bottom: '.resize-bottom',
top: '.resize-top'
}
})
.on('resizemove', function(event) {
// Resize the rect, not the group, it will resize automatically
const target = event.target.querySelector('rect');
for (const attr of ['width', 'height']) {
let v = Number(target.getAttribute(attr));
v += event.deltaRect[attr];
target.setAttribute(attr, v);
}
for (const attr of ['top', 'left']) {
const a = attr == 'left' ? 'x' : 'y';
let v = Number(target.getAttribute(a));
v += event.deltaRect[attr];
target.setAttribute(a, v);
}
findLocations(rect, handles);
});
findLocations(rect, handles);
svg {
width: 100%;
height: 240px;
background-color: #2e9;
-ms-touch-action: none;
touch-action: none;
}
body {
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/interactjs@latest/dist/interact.min.js"></script>
<svg id="mysvg"></svg>
这通过利用 Interact.js 对使用单独元素作为句柄的支持来实现,如他们的文档 here 中所述,以及您正在使用 SVG 的有趣事实HTML。这意味着您不必担心使用 CSS transform
。相反,您只需移动 rect
的 x
和 y
,计算就会变得相当简单。
我必须做的唯一 easy-to-miss 更改是我必须将矩形和手柄放在一个组中。这是因为 Interact.js 只允许您使用 child 元素作为调整大小的句柄。这意味着听众在组中,它会调整自身内部矩形的大小(然后,当然,会导致组增长,因为它们与 children 的大小相匹配)。
如果我遗漏了什么,请告诉我。