22 декабря 2015
10 сентября 2010
45
Вращение объектов в jQuery UI с помощью CSS transform
Всем привет!
Сегодня я хочу вам рассказать о плагине для jQuery UI, который я написал в процессе работы над одним проектом. Сразу скажу, что плагин в своем роде уникальный, таких больше нет (ну или я не нашел).
Собственно, плагин добавляем в jQuery UI возможность вращать предметы вокруг их центра, зажав определенный контрол мышкой. Этот плагин дополняет имеющиеся в jQuery UI возможности изменения размера и перемещения.
Требования
Для работы плагина понадобится:
- jquery.js
- jquery.ui.core.js
- jquery.ui.widget.js
- jquery.ui.mouse.js
- jquery.ui.draggable.js
Совместимость
В настоящий момент плагин работает в браузерах, которые поддерживают свойство CSS transform: matrix(...). Это последние сборки Оперы, Фаерфокса, Сафари и Хрома. В ИЕ7 работает, но с глюками, в ИЕ8 не работает.
Реализация
Вращение объектов можно делать за счет свойства CSS transform, у которого есть такие разновидности:
- transform: matrix(0,70710678, 0,70710678, -0,70710678, 0,70710678, 0, 0) — трансформирует элемент по указанной таблице (здесь поворот на 45 градусов);
- transform: rotate(45deg) — поворачивает объект на указанный угол (здесь 45).
Нормальный человек решит, для вращения лучше использовать transform: rotate, но я решил по-другому. И тут дело не в моей ненормальности — дело в том, что transform: rotate умеет только вращать, в то время как с помощью transform: matrix можно делать вертикальные и горизонтальные отображения объектов, скручивать их и так далее. Предел для фантазии существенно шире.
В обоих случаях вращение будет происходить по умолчанию относительно центра объекта. Для смены центра есть свойство transform-origin.
CSS transform: matrix
Свойство transform: matrix имеет шесть аргументов:
transform: matrix(a, b, c, d, transX, transY);
Они отвечают за преобразование элемента. Для любителей математики даю картинку:
Если по-простому: точка с координатами (x, y, 1) после преобразования с помощью указанной матрицы перейдет в точку с координатами (a*x + c*y + transX, b*x + d*y + transY, 1). Тут сразу становится понятным, что transX и transY — сдвиги по осям X и Y, соответственно, и в нашем случае они будут по нулям.
После некоторых размышлений можно так же прийти к выводу, что для нормального положения объекта необходимо следующее:
transform: martix(1, 0, 0, 1, 0, 0);
IE: filter, -ms-filter и -ms-transform
Чисто теоретически вращение можно сделать и для Эксплорера начиная с 6-ой версии, но мне пока что удалось сделать только для IE7, да и то с багами.
Используя различные варианты ослиного свойства filter, можно вращать объект. Но вот беда — вращение происходит не относительно центра, и точно подсчитать отрицательные отступы для компенсации сдвига при вращении мне пока что не удалось.
В любом случае, внутри плагина есть части кода специально для IE и те из вас, кто захочет поизучать проблему, могут найти там мои комментарии.
Алгоритм работы
Сценарий такой: при нажатии и перетаскивании хендлера скрипт считает угол поворота, синус и косинус и подставляет значения в transform: matrix(cos, sin, -sin, cos, 0, 0).
Для начала нужно сделать HTML для объекта, который мы будем вращать. Опытным путем было установлено, что помимо самого объекта вращения (я делал это все с перетаскиванием и ресайзом) нужно еще два врапера:
<div id="draggable-zone">
<div id="draggable-wrapper" style="width: 150px; height: 150px; left: 225px; top: 175px;">
<div id="resizable-wrapper">
<img src="images/earth.png" width="150" height="150" alt="Планет Земля" id="elem-wrapper" />
</div>
</div>
</div>
Теперь нам нужен хендлер, зажав и перетягивая который мы вращаем объект. Он создается динамически внутри плагина. Сделаем стили:
#draggable-zone {
background: #000 url(images/space.jpg) 0 0 no-repeat;
border: 3px solid #000;
height: 500px;
margin: 2em auto;
overflow: hidden;
width: 600px;}
.ui-wrapper {
overflow: visible !important;}
.ui-resizable-handle {
background: #f5dc58;
border: 1px solid #FFF;
z-index: 2;}
.ui-rotatable-handle {
background: #f5dc58;
border: 1px solid #FFF;
border-radius: 5px;
-moz-border-radius: 5px;
-o-border-radius: 5px;
-webkit-border-radius: 5px;
cursor: pointer;
height: 10px;
left: 50%;
margin: 0 0 0 -5px;
position: absolute;
top: -5px;
width: 10px;}
.ui-rotatable-handle.clone {
visibility: hidden;}
Теперь вычисляем угол. Угол вычисляется на основе координат курсора и центра вращаемого объекта (в радианах):
this.getAngle = function(ms, ctr) {
var x = ms.x - ctr.x,
y = - ms.y + ctr.y,
hyp = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)),
angle = Math.acos(x / hyp);
if (y < 0) {
angle = 2 * Math.PI - angle;
}
return angle;
}
Теперь нужно перевести его в градусы и вычесть 90 градусов (геометрия, все дела):
angle = _this.radToDeg(_this.getAngle(mouse_coords, center_coords)) - 90;
Вот это в целом все сложные моменты. После получения угла нужно вычислить синус и косинус и поставить их в transform: matrix (cos, sin, -sin, cos, 0, 0).
Использование
В случае, если вы используете помимо вращение еще и изменение размера и перетаскивание, задавать их следует вот в такой порядке:
В действии
Вот и все. Смотрим пример и качаем сам скрипт.
P.S.
Если у кого-то из читателей есть идеи или готовые решения как сделать все это работающим в IE7-9, пишите на мыло или в комментариях.
Особенно порадовала кнопочка на этой странице http://wiki.github.com/heygrady/transform/demo-2
ну насчет этого вы преувеличили, таких реализаций уже много.
начиная от приведенного Pashkou и заканчивая вот этим http://www.zachstronaut.com/posts/2009/08/07/jquery-animate-css-rotate-scale.html
его я недавно использовал в одном из проектов.
к сожалению тут не предусмотрен ИЕ, хотя даже у вас не получилось сделать чтобы ие нормально вращался четко вокруг центра.
а так же у всех приведенных плагинов проблема с Оперой, которая с 10 версии поддерживает transform ...
чтобы заставить ее работать в выше приведенном плагине достаточно было
добавить вот эту строчку
var properties = [ 'transform', 'WebkitTransform', 'MozTransform', 'OTransform'];
и можно не возиться с матрицами
Это работает как обычный CSS3:
и т.п. Поддерживаются только rotate и scale, судя по содержимомому.
Scriptin, спасибо
Всё, что можно делать матрицей, можно делать упрощёнными функциями.
А для IE есть Matrix Filter
Matrix Filter имеет ряд существенных отличий от простого transform. То есть на статике (просто повернуть картинку без последующих растягиваний) все отлично, но если после этого еще изменять размеры картинки, то начинается веселье.
Т.е. чтоб объект вращался не вокруг z-оси как сейчас а, например, вокруг своей x- или y-оси?
Вроде, далеко ходить не надо или всё-таки сложно?
В этом примере есть skrew:
http://wiki.github.com/heygrady/transform/demo-2
Но это всё 2D, как я понял.
А можете-ли подсказать как контролы сделать видимыми при расположении картинки под другой?
Аналогично функционалу в ui.resizable `autoHide`.
.ui-resizable-autohide .ui-rotatable-handle{display:none;}
Even though the plugin was created almost 3 years ago, it is still unique - I couldn't find a way to support both jQueryUI scaling and rotation at the same time.
However, because it is old, it is not working with the recent versions of jQuery and jQueryUI.
Do you have an update so it will work with the latest versions?
Thank you,