Как определить размер объекта Рафаэля после его масштабирования и поворота?
Если бы я должен был создать изображение как объект Raphael.js с начальным размером [w1, h1], то масштабировать его, используя transform()
метод, как бы я впоследствии получить его новый размер [w2, h2]? Потому что зовет image.attr("width")
возвращает w1, начальную ширину, а не ширину после преобразования.
Я знаю, вы могли бы сказать, что я должен просто умножить w1 и h1 на коэффициенты масштабирования, но в большинстве случаев также применяется преобразование вращения, что делает вещи немного более громоздкими.
Итак, вкратце, есть ли в Raphael.js метод, который возвращает правильный размер объекта, независимо от каких-либо преобразований, которые могли быть применены к нему?
2 ответа
Существует метод для получения ограничивающей рамки элемента с преобразованием и без него.
С преобразованием:
var bbox = image.getBBox(),
width = bbox.width,
height = bbox.height;
Без трансформации:
var bbox = image.getBBoxWOTransform(),
width = bbox.width,
height = bbox.height;
Вы можете расширить Raphael.el с помощью вспомогательных методов (если вы осторожны), которые предоставляют ширину и высоту напрямую, если это поможет вам. Вы можете просто использовать метод ограничивающего прямоугольника и вернуть интересующую вас часть, но для большей эффективности я вычислил только запрошенное свойство, используя свойство матрицы для элементов и позицию / ширину / высоту из атрибутов.
(function (r) {
function getX() {
var posX = this.attr("x") || 0,
posY = this.attr("y") || 0;
return this.matrix.x(posX, posY);
}
function getY() {
var posX = this.attr("x") || 0,
posY = this.attr("y") || 0;
return this.matrix.y(posX, posY);
}
function getWidth() {
var posX = this.attr("x") || 0,
posY = this.attr("y") || 0,
maxX = posX + (this.attr("width") || 0),
maxY = posY + (this.attr("height") || 0),
m = this.matrix,
x = [
m.x(posX, posY),
m.x(maxX, posY),
m.x(maxX, maxY),
m.x(posX, maxY)
];
return Math.max.apply(Math, x) - Math.min.apply(Math, x);
}
function getHeight() {
var posX = this.attr("x") || 0,
posY = this.attr("y") || 0,
maxX = posX + (this.attr("width") || 0),
maxY = posY + (this.attr("height") || 0),
m = this.matrix,
y = [
m.y(posX, posY),
m.y(maxX, posY),
m.y(maxX, maxY),
m.y(posX, maxY)
];
return Math.max.apply(Math, y) - Math.min.apply(Math, y);
}
r.getX = getX;
r.getY = getY;
r.getWidth = getWidth;
r.getHeight = getHeight;
}(Raphael.el))
С использованием:
var x = image.getX();
var y = image.getY();
var width = image.getWidth();
var height = image.getHeight();
Просто включите сценарий после включения Рафаэля. Обратите внимание, что он работает только для элементов с атрибутами width, height, x и y, что подходит для изображений. Рафаэль фактически вычисляет ограничивающую рамку из данных пути, которая преобразует все точки в пути и получает значения min/max x/y после их преобразования.
Пример Мэтта Эша может работать только с некоторыми (прямоугольными) элементами, но для получения метрик всех возможных элементов (а также в случаях, когда на элемент влияют вложенные преобразования родительских групп), мы должны использовать другой подход. Чтобы получить различные метрики (ширину, высоту, ширину bbox, высоту bbox, координаты угла, длину сторон и т. Д.) Любого преобразованного элемента SVG, я сделал функцию get_metrics():
Полный функциональный пример: http://output.jsbin.com/zuvuborehe/
На следующем рисунке показан один из возможных вариантов использования get_metrics():
Функция get_metrics()
использует чистый JavaScript, без библиотек. Но его можно использовать с библиотеками, такими как Raphaël. Он основан на родной element1.getTransformToElement(element2)
который может получить матрицу отношений между двумя элементами, которые в этом случае являются преобразованным элементом (например, <path>
) и корневой элемент SVG (<svg>
). Конечно, вы можете использовать и другие элементы, такие как изображение, многоугольник, прямоугольник и т. Д. Кстати, getTransformToElement
очень мощный и универсальный, его можно использовать, например. чтобы сгладить преобразования пути, состоящие из любых команд пути (даже дуг, если они впервые были преобразованы в кубики с использованием пути Raphaël path2curve) следующим образом: http://jsbin.com/atidoh/9.
SVGElement.prototype.getTransformToElement =
SVGElement.prototype.getTransformToElement || function(elem)
{
return elem.getScreenCTM().inverse().multiply(this.getScreenCTM());
};
function get_metrics(el) {
function pointToLineDist(A, B, P) {
var nL = Math.sqrt((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y));
return Math.abs((P.x - A.x) * (B.y - A.y) - (P.y - A.y) * (B.x - A.x)) / nL;
}
function dist(point1, point2) {
var xs = 0,
ys = 0;
xs = point2.x - point1.x;
xs = xs * xs;
ys = point2.y - point1.y;
ys = ys * ys;
return Math.sqrt(xs + ys);
}
var b = el.getBBox(),
objDOM = el,
svgDOM = objDOM.ownerSVGElement;
// Get the local to global matrix
var matrix = svgDOM.getTransformToElement(objDOM).inverse(),
oldp = [[b.x, b.y], [b.x + b.width, b.y], [b.x + b.width, b.y + b.height], [b.x, b.y + b.height]],
pt, newp = [],
obj = {},
i, pos = Number.POSITIVE_INFINITY,
neg = Number.NEGATIVE_INFINITY,
minX = pos,
minY = pos,
maxX = neg,
maxY = neg;
for (i = 0; i < 4; i++) {
pt = svgDOM.createSVGPoint();
pt.x = oldp[i][0];
pt.y = oldp[i][1];
newp[i] = pt.matrixTransform(matrix);
if (newp[i].x < minX) minX = newp[i].x;
if (newp[i].y < minY) minY = newp[i].y;
if (newp[i].x > maxX) maxX = newp[i].x;
if (newp[i].y > maxY) maxY = newp[i].y;
}
// The next refers to the transformed object itself, not bbox
// newp[0] - newp[3] are the transformed object's corner
// points in clockwise order starting from top left corner
obj.newp = newp; // array of corner points
obj.width = pointToLineDist(newp[1], newp[2], newp[0]) || 0;
obj.height = pointToLineDist(newp[2], newp[3], newp[0]) || 0;
obj.toplen = dist(newp[0], newp[1]);
obj.rightlen = dist(newp[1], newp[2]);
obj.bottomlen = dist(newp[2], newp[3]);
obj.leftlen = dist(newp[3], newp[0]);
// The next refers to the transformed object's bounding box
obj.BBx = minX;
obj.BBy = minY;
obj.BBx2 = maxX;
obj.BBy2 = maxY;
obj.BBwidth = maxX - minX;
obj.BBheight = maxY - minY;
return obj;
}