#drag-and-drop #raphael #rotation
#перетаскивание #Рафаэль #поворот
Вопрос:
Я использую RaphaelJS 2.0 для создания нескольких фигур в div. Каждую фигуру необходимо независимо перетаскивать в пределах div. При двойном щелчке по фигуре эта фигура должна повернуться на 90 градусов. Затем ее можно перетаскивать и поворачивать снова.
Я загрузил некоторый код в fiddler: http://jsfiddle.net/QRZMS. По сути, это:
window.onload = function () {
var angle = 0;
var R = Raphael("paper", "100%", "100%"),
shape1 = R.rect(100, 100, 100, 50).attr({ fill: "red", stroke: "none" }),
shape2 = R.rect(200, 200, 100, 50).attr({ fill: "green", stroke: "none" }),
shape3 = R.rect(300, 300, 100, 50).attr({ fill: "blue", stroke: "none" }),
shape4 = R.rect(400, 400, 100, 50).attr({ fill: "black", stroke: "none" });
var start = function () {
this.ox = this.attr("x");
this.oy = this.attr("y");
},
move = function (dx, dy) {
this.attr({ x: this.ox dx, y: this.oy dy });
},
up = function () {
};
R.set(shape1, shape2, shape3, shape4).drag(move, start, up).dblclick(function(){
angle -= 90;
shape1.stop().animate({ transform: "r" angle }, 1000, "<>");
});
}
Перетаскивание работает, а также одна из фигур поворачивается при двойном щелчке. Однако есть две проблемы / questions:
-
Как я могу автоматически прикрепить поворот к каждой фигуре без необходимости жестко кодировать каждую ссылку на элемент в методе rotate? То есть я просто хочу нарисовать фигуры один раз, затем все они автоматически подвергаются одинаковому поведению, чтобы каждую из них можно было перетаскивать / отбрасывать / поворачивать независимо без необходимости явно применять это поведение к каждой фигуре.
-
После поворота фигуры она больше не перемещается корректно — как будто перемещение мыши для перетаскивания относится к исходной ориентации фигуры, а не обновляется при повороте фигуры. Как я могу заставить это работать правильно, чтобы фигуры можно было просто перетаскивать и поворачивать много раз, без швов?
Большое спасибо за любые указания!
Ответ №1:
Я несколько раз пытался разобраться в новом механизме преобразования, но безрезультатно. Итак, я вернулся к первым принципам.
Наконец-то мне удалось правильно перетащить объект, который претерпел несколько преобразований, после попытки определить влияние различных преобразований — t, T, … t, … T, r, R и т.д…
Итак, вот суть решения
var ox = 0;
var oy = 0;
function drag_start(e)
{
};
function drag_move(dx, dy, posx, posy)
{
r1.attr({fill: "#fa0"});
//
// Here's the interesting part, apply an absolute transform
// with the dx,dy coordinates minus the previous value for dx and dy
//
r1.attr({
transform: "...T" (dx - ox) "," (dy - oy)
});
//
// store the previous versions of dx,dy for use in the next move call.
//
ox = dx;
oy = dy;
}
function drag_up(e)
{
// nothing here
}
Вот и все. Глупо просто, и я уверен, что это уже приходило в голову множеству людей, но, возможно, кто-то найдет это полезным.
Вот скрипка, с которой вы можете поиграть.
… и это рабочее решение первоначального вопроса.
Комментарии:
1. Это здорово, спасибо за настойчивость, амадан — я действительно ценю это. Честно говоря, мой первоначальный опыт работы с Raphael не был удачным. Документация в лучшем случае краткая (например, попробуйте найти что-нибудь, объясняющее, как использовать матричную часть метода преобразования), а в Интернете так мало поддержки, что это просто усложняет задачу новичку вроде меня. Возможно, просто версия v2 такая новая и непохожая, или, может быть, она действительно нацелена на серьезных тяжеловесов javascript, которые могут разобрать исходный код на части, кто знает. Еще раз спасибо, это действительно помогло, и я это очень ценю!
2. Дэн, ты должен рассмотреть, что пытается сделать библиотека Raphael. Это открывает возможности SVG (и VML) браузера для javascript. Таким образом, большая часть того, что он делает, — это прямое отображение базового элемента SVG. Это именно то, что представляет собой элемент преобразования. Если вы ищете информацию о том, как они работают, и, в частности, преобразования матриц, вам нужно поискать в Google «Преобразование матрицы SVG», а не «Преобразование матрицы Рафаэля». Попробуйте commons.oreilly.com/wiki/index.php/SVG_Essentials /… или mecxpert.de/svg/transform.html
3. Fiddle не работает в FF7. После первого перетаскивания взаимное расположение фигуры и мыши не синхронизировано.
4. Оранжевый пес, ты можешь пояснить? Я тестировал на IE8.0.6001, Chrome 15.0.874 и Firefox 8.0, и, похоже, все работает нормально.
5. Оранжевый пес, ты прав, он забыл сбросить начальную позицию перетаскивания в примере со скрипкой, просто замени drag start на это, и это отлично работает: функция drag_start(e) { ox = 0; oy = 0; };
Ответ №2:
Я решил проблему перетаскивания / поворота, повторно применив все преобразования при изменении значения. Я создал для этого плагин.
https://github.com/ElbertF/Raphael .FreeTransform
Демонстрация здесь:
Ответ №3:
Как предполагает амадан, обычно хорошей идеей является создание функций, когда несколько объектов имеют одинаковые (начальные) атрибуты / свойства. Это действительно ответ на ваш первый вопрос. Что касается второго вопроса, то он немного сложнее.
Когда объект Rapheal поворачивается, то же самое происходит и с координатной плоскостью. По какой-то причине Дмитрий и несколько других источников в Интернете, похоже, согласны с тем, что это правильный способ реализации. Я, как и вы, не согласен. Мне не удалось найти универсальное хорошее решение, но я постарался его обойти. Я кратко объясню, а затем покажу код.
- Создайте пользовательский атрибут для хранения текущего состояния поворота
- В зависимости от этого атрибута вы решаете, как обрабатывать перемещение.
При условии, что вы собираетесь поворачивать фигуры только на 90 градусов (в противном случае это становится намного сложнее), вы можете определить, как следует манипулировать координатами.
var R = Raphael("paper", "100%", "100%");
//create the custom attribute which will hold the current rotation of the object {0,1,2,3}
R.customAttributes.rotPos = function (num) {
this.node.rotPos = num;
};
var shape1 = insert_rect(R, 100, 100, 100, 50, { fill: "red", stroke: "none" });
var shape2 = insert_rect(R, 200, 200, 100, 50, { fill: "green", stroke: "none" });
var shape3 = insert_rect(R, 300, 300, 100, 50, { fill: "blue", stroke: "none" });
var shape4 = insert_rect(R, 400, 400, 100, 50, { fill: "black", stroke: "none" });
//Generic insert rectangle function
function insert_rect(paper,x,y, w, h, attr) {
var angle = 0;
var rect = paper.rect(x, y, w, h);
rect.attr(attr);
//on createion of the object set the rotation position to be 0
rect.attr({rotPos: 0});
rect.drag(drag_move(), drag_start, drag_up);
//Each time you dbl click the shape, it gets rotated. So increment its rotated state (looping round 4)
rect.dblclick(function(){
var pos = this.attr("rotPos");
(pos )%4;
this.attr({rotPos: pos});
angle -= 90;
rect.stop().animate({transform: "r" angle}, 1000, "<>");
});
return rect;
}
//ELEMENT/SET Dragger functions.
function drag_start(e) {
this.ox = this.attr("x");
this.oy = this.attr("y");
};
//Now here is the complicated bit
function drag_move() {
return function(dx, dy) {
//default position, treat drag and drop as normal
if (this.attr("rotPos") == 0) {
this.attr({x: this.ox dx, y: this.oy dy});
}
//The shape has now been rotated -90
else if (this.attr("rotPos") == 1) {
this.attr({x:this.ox-dy, y:this.oy dx});
}
else if (this.attr("rotPos") == 2) {
this.attr({x: this.ox - dx, y: this.oy - dy});
}
else if (this.attr("rotPos") == 3) {
this.attr({x:this.ox dy, y:this.oy - dx});
}
}
};
function drag_up(e) {
}
Я не могу придумать четкого сжатого способа объяснить, как работает drag_move. Я думаю, что, вероятно, будет лучше, если вы посмотрите на код и увидите, как он работает. По сути, вам просто нужно выяснить, как теперь обрабатываются переменные x и y из этого нового повернутого состояния. Не уверен, что смог бы нарисовать достаточно много графики, если бы не я. (Я часто поворачивал голову набок, чтобы понять, что она должна делать).
Однако у этого метода есть несколько недостатков:
- Это работает только для поворотов на 90 градусов (для выполнения 45 градусов потребовалось бы гораздо больше вычислений, не обращая внимания на любой заданный градус)
- При начале перетаскивания после поворота происходит небольшое движение. Это происходит потому, что перетаскивание принимает старые значения x и y, которые были повернуты. Это не является серьезной проблемой для фигур такого размера, но при увеличении фигур вы действительно начнете замечать, как фигуры прыгают по холсту.
Я предполагаю, что причина, по которой вы используете transform, заключается в том, что вы можете анимировать вращение. Если в этом нет необходимости, вы могли бы использовать .rotate()
функцию, которая всегда вращается вокруг центра элемента и, таким образом, устранила бы второй недостаток, о котором я упоминал.
Это не полное решение, но оно определенно должно направить вас по правильному пути. Мне было бы интересно увидеть полную рабочую версию.
Я также создал версию этого на jsfiddle, которую вы можете просмотреть здесь: http://jsfiddle.net/QRZMS/3
Удачи.
Комментарии:
1. Я в значительной степени добрался до того же момента, что вы описали выше. Фактически, вы получаете те же результаты, что и я. перемещение без поворота работает, первый поворот работает, но последующие вращения становятся все более некорректными. Однако приятно получить подтверждение. Проголосуйте за.
2. Спасибо, Адам. Мы приближаемся к этому, но с Raphael добиться такого рода результатов, к сожалению, сложно. На самом деле мне интересно, правильный ли я выбрал инструмент для этой работы, поскольку мне нужно выполнить некоторые относительно сложные действия с перетаскиванием объектов. Если этого так сложно добиться, то, я думаю, остальные вещи будут слишком сложными. Но пока я буду упорствовать, так что спасибо за ваши добрые усилия!
Ответ №4:
Обычно я создаю объект для своей фигуры и записываю обработку событий в объект.
function shape(x, y, width, height, a)
{
var that = this;
that.angle = 0;
that.rect = R.rect(x, y, width, height).attr(a);
that.rect.dblclick(function() {
that.angle -= 90;
that.rect.stop().animate({
transform: "r" that.angle }, 1000, "<>");
});
return that;
}
В приведенном выше примере конструктор не только создает прямоугольник, но и настраивает событие двойного щелчка.
Следует отметить, что ссылка на объект хранится в «этом». Это связано с тем, что ссылка «this» изменяется в зависимости от области видимости. В функции dblClick мне нужно ссылаться на значения rect и angle из моего объекта, поэтому я использую сохраненные ссылки that.rect и that.angle
Смотрите этот пример (обновлен по сравнению с немного запутанным предыдущим экземпляром)
Могут быть лучшие способы сделать то, что вам нужно, но это должно сработать для вас.
Надеюсь, это поможет,
Ник
Добавление: Дэн, если ты действительно застрял на этом и можешь обойтись без некоторых функций, которые предоставляет тебе Raphael2, я бы рекомендовал вернуться к Raphael 1.5.x. Преобразования были только что добавлены в Raphael2, код поворота / перевода / масштабирования полностью отличается (и проще) в 1.5.2.
Посмотрите на меня, обновляющего свой пост, надеющегося на карму…
Комментарии:
1. Спасибо, Ник. Я разберусь с этим, но при первоначальном просмотре этой страницы fiddle фигуры по-прежнему ведут себя действительно странно. Кажется, что они вращаются с разным шагом, и перетаскивание происходит повсюду.
2. Я весь в смущении.. В моем скрипте была опечатка. угол должен быть частью объекта, и каждый экземпляр увеличивается отдельно. Я обновил скрипку, и теперь она должна работать корректно. Вините в этом несколько открытых браузеров: (
3. Немного сложно использовать скрипку: ( попробуйте вместо этого эту ссылку jsfiddle.net/vPSuR
4. На самом деле, оказывается, что DnD работает в первый раз, а затем немного сходит с ума. Я отзываю свое ужасное решение и приношу извинения за то, что потратил ваше время впустую.
5. Ха-ха, не беспокойся, Ник — это за попытку!
Ответ №5:
Если вы не хотите использовать библиотеку ElbertF, вы можете преобразовать декартовы координаты в полярные.
После необходимо добавить или удалить угол и снова преобразовать в декартовой координате.
Мы можем видеть этот пример с прямым поворотом в rumble и перемещением.
HTML
<div id="foo">
</div>
JAVASCRIPT
var paper = Raphael(40, 40, 400, 400);
var c = paper.rect(40, 40, 40, 40).attr({
fill: "#CC9910",
stroke: "none",
cursor: "move"
});
c.transform("t0,0r45t0,0");
var start = function () {
this.ox = this.type == "rect" ? this.attr("x") : this.attr("cx");
this.oy = this.type == "rect" ? this.attr("y") : this.attr("cy");
},
move = function (dx, dy) {
var r = Math.sqrt(Math.pow(dx, 2) Math.pow(dy, 2));
var ang = Math.atan2(dy,dx);
ang = ang - Math.PI/4;
dx = r * Math.cos(ang);
dy = r * Math.sin(ang);
var att = this.type == "rect" ? { x: this.ox dx, y: this.oy dy} : { cx: this.ox dx, cy: this.oy dy };
this.attr(att);
},
up = function () {
};
c.drag(move, start, up);?
ДЕМОНСТРАЦИЯ
http://jsfiddle.net/Ef83k/74/
Ответ №6:
моей первой мыслью было использовать getBBox (false) для захвата координат x, y объекта после преобразования, затем removeChild() исходный объект Raphael obj с canvas, затем перерисовать объект, используя данные координат из getBBox ( false ). взлом, но у меня он работает.
однако одно замечание: поскольку объект, возвращаемый getBBox ( false), является УГЛОВЫМИ координатами (x, y) объекта, вам необходимо вычислить центр повторно нарисованного объекта, выполнив следующее … x = box [‘x’] (box[‘width’] / 2); y = box[‘y’] (box[‘height’] / 2);
где box = shapeObj.getBBox( false);
другой способ решить ту же проблему