Расставляем элементы по кругу с помощью jQuery и геометрии
Все началось с одной маленькой идеи. Впрочем как всегда.
Что получилось:
Как это сделать?
Для начала, хочу сразу предупредить, что для реализации подобных вещей не помешают базовые знания тригонометрии.
Концептуально, показанный на скриншоте выше зодиакальный круг — это просто сетка ссылок в контейнере.
Что–то вроде:
<div class="signs">
<a href="#" class="signs__item">Лев</a>
<a href="#" class="signs__item">Овен</a>
<!-- ... -->
</div>
Если отключить в браузере JavaScript, то вместо круга мы увидим самую обычную сетку (прим. я не привожу здесь css для сетки, поскольку в проекте использовал kube.css).
Для того, чтобы оживить эту сетку, и превратить в кольцо, нужно сообщить каждой ссылке определенные координаты относительно блока-монитора.
Итак, у нас есть 12 элементов, которые мы должны равномерно распределить на окружности (360 o), т.е. получается 12 секторов с углами в 30o.
Обратимся к базовой тригонометрии и вспомним, что y — это синус угла, а x это его косинус.
Математические функции в Javascript работают с радианами, поэтому перед вычислениями нужно перевести градусы в радианы по формуле:
RAD = DEG * (PI / 180)
RAD - угол в радианах,
DEG - угол в градусах
Отобразим эту формулу в виде простейшей функции–конвертёра:
function toRad(deg) {
return deg * (Math.PI / 180);
}
Теперь нам нужно определить центр координат, а также смещение контейнера, в котором будет строиться наш круг:
// root - элемент-контейнер в котором будет выстраиваться круг
// для вычисления смещения используем стандартные функции jQuery
var x = root.position().left,
var y = root.position().top;
// определяем - требуется ли использовать смещение в расчётах
// если нет - просто обнуляем x и y
if(root.css('position') == 'relative')
x = y = 0;
// вычисляем наш центр координат
// для этого складываем смещение и ширину/высоту и просто делим на 2
var cx = Math.round((root[0].offsetWidth + x) / 2),
cy = Math.round((root[0].offsetHeight + y) / 2);
Смещение нужно, только в том случае, если позиционирование root не является относительным (css position: relative).
В обратном случае все элементы внутри и без этого будут позиционироваться относительно родительского элемента благодаря CSS.
Итак, фундамент готов - осталось только дописать вычисление позиции каждого отдельного элемента:
/*
Вычисляем угол поворота одного сектора в радианах
*/
var sectorAngle = toRad(360 / sectorsCount);
/*
radius - радиус окружности которую мы строим
cx - центр по оси X
radius * cos - получаем абсциссу
sectorAngle * iter - получаем угол поворота для текущего элемента
*/
var toLeft = Math.round(cx + (options.radius * Math.cos(sectorAngle * iter))));
iter - индекс обрабатываемого элемента, с помощью которого делается угловой сдвиг,
radius - радиус окружности заданный в опциях виджета,
cx - вычисленный ранее центр оси координат Ox.
Таким образом мы получили координату x. Таким же нехитрым способом определяется и y:
// ордината
var toTop = Math.round(cy + (options.radius * Math.cos(sectorAngle * iter))));
Ну, а зная требуемые координаты элемента, нам остаётся лишь позиционировать элементы.
Для этого мы можем использовать jQuery метод css, либо, для более эффектной, плавной расстановки элементов методом animate:
/*
Чтобы всё расставлялось плавно - используем jquery метод animate
*/
$elem.animate({
left : toLeft,
top : toTop,
opacity : 1
},options.animationSpeed);
Код целиком:
"use strict"; | |
;(function ($) { | |
// конвертирует градусы в радианы | |
function toRad (deg) { | |
return deg * (Math.PI / 180); | |
} | |
// виджет для расстановки элементов по кругу | |
var DHTMLCircle = function (options) { | |
var root = this; | |
// опции jQuery виджета | |
options = $.extend({ | |
'elementsSelector' : '> a', | |
'radius' : 100, | |
'animationSpeed' : 600, | |
'xOffset' : 0, | |
'yOffset' : 0 | |
}, options || {}); | |
var slides = root.find(options.elementsSelector), // позиционируемые элементы | |
slidesCount = slides.length, | |
x = root.position().left, | |
y = root.position().top; | |
// нам не нужно учитывать смещение родителя при относительном позиционировании | |
if(root.css('position') == 'relative') | |
x = y = 0; | |
// вычисляем центр окружности и угол поворота | |
var cx = Math.round((root[0].offsetWidth+x) / 2), | |
cy = Math.round((root[0].offsetHeight+y) / 2), | |
slideAngle = toRad(360 / slidesCount); | |
// построение | |
this.render = function(offset, angleOffset) { | |
// смещение, если было передано | |
offset = offset || 0; | |
angleOffset = angleOffset || 0; | |
for(var iter = 0; iter < slidesCount; iter++) { | |
var elem = slides[iter], | |
$elem = $(elem), // jQuery элемент для позиционирования | |
slideWidth = parseInt($elem.width()), // ширина | |
slideHeight = parseInt($elem.height()); // высота | |
// вычисление координат элемента | |
// cx - центр оХ, | |
// r * cos() - абсцисса без смещений, | |
// - slideWidth(ибо строится по верх.левому углу), | |
// -offset - ручное смещение, | |
// xOffset - изначальное смещение | |
// абсцисса | |
var toLeft = Math.round(cx + (options.radius * Math.cos(slideAngle*(iter+angleOffset))) - slideWidth - offset - options.xOffset); | |
// ордината | |
var toTop = Math.round(cy + (options.radius * Math.sin(slideAngle*(iter+angleOffset))) - slideHeight - offset - options.yOffset); | |
$elem.animate({ | |
left : toLeft, | |
top : toTop, | |
opacity : 1 | |
},options.animationSpeed); | |
} | |
} | |
this.render(); | |
return this; | |
} | |
/* | |
Пример использования: | |
var container = $('#circle-positioned-container'); | |
container.DHTMLCircle({ | |
radius: container.offset().width | |
}); | |
*/ | |
$.fn.DHTMLCircle = DHTMLCircle; | |
}(window.jQuery)); |