Анимирование по окружности при нажатии
Я пытаюсь написать небольшую логику для моих сфер: сфера, которая имеет угол PI/2, активна и имеет цвет красный. При нажатии на сферу( не активную ) она должна переместиться плавно по окружности на угол PI/2 следующим образом:
если эта сфера слева, то она смещается вправо на угол PI/2 и становится активной; сфера, что была на PI/2 смещается вправо на угол PI/6, а та сфера, что была на PI/6 смещается вправо в ускоренном темпе и становится на место той, которая была слева ( 5PI/6 )
если эта сфера справа, то все наоборот ( смещение влево )
Главная проблема заключается в сравнении X и Y - this.left !== left(this.alpha + Math.PI / 3) && this.top !== topF(this.alpha + Math.PI / 3). Как правильно здесь сравнивать или вычислять координаты для смещения сферы или может я не правильно увеличиваю time?
Мой код:
//Circles position
let circle = document.querySelectorAll('.circle');
let radius = 100;
let direction;
class Circle {
constructor(element) {
this.$el = element.selector;
this.alpha = element.alpha;
this.left = left(this.alpha);
this.top = topF(this.alpha);
this.$el.style.left = this.left + 'px';
this.$el.style.top = this.top + 'px';
this.activated = element.activated;
this.run = false;
this.time = 0;
}
moveIn() {
this.run = true;
this.animate();
}
animation() {
this.run = true;
if ( this.left > left( Math.PI / 2 ) ){
direction = "left";
} else {
direction = "right";
}
this.animate();
}
animate() {
requestAnimationFrame(this.animate.bind(this));
if (this.run) {
if ( direction === "left" ){
this.time += Math.PI / 90;
this.moveLeft();
} else {
this.time += Math.PI / 90;
this.moveRight();
}
}
}
moveLeft(){
if (this.left !== left(this.alpha + Math.PI / 3)
&& this.top !== topF(this.alpha + Math.PI / 3) ) {
this.left = left(this.alpha + this.time);
this.top = topF(this.alpha + this.time);
this.$el.style.left = this.left + 'px';
this.$el.style.top = this.top + 'px';
} else {
this.time = 0;
return cancelAnimationFrame(this.animate);
}
}
moveRight() {
if (this.left !== left(this.alpha - Math.PI / 3)
&& this.top !== topF(this.alpha - Math.PI / 3) ) {
this.left = left(this.alpha - this.time);
this.top = topF(this.alpha - this.time);
this.$el.style.left = this.left + 'px';
this.$el.style.top = this.top + 'px';
} else {
this.time = 0;
return cancelAnimationFrame(this.animate);
}
}
}
let c1 = new Circle({
selector: circle[0],
alpha: Math.PI / 6,
activated: false
})
let c2 = new Circle({
selector: circle[1],
alpha: Math.PI / 2,
activated: true
})
let c3 = new Circle({
selector: circle[2],
alpha: (Math.PI / 6) * 5,
activated: false
})
circle[0].addEventListener('click', clicked);
circle[1].addEventListener('click', clicked);
circle[2].addEventListener('click', clicked);
function clicked(e) {
let id = e.target.id;
if ( id === 'c1' && !c1.activated ){
this.activated = true;
c2.activated = false;
c3.activated = false;
c1.animation();
c2.moveIn();
c3.moveIn();
}
else if ( id === 'c2' && !c2.activated ){
this.activated = true;
}
else if ( id === 'c3' && !c3.activated){
this.activated = true;
}
}
function left(alpha) {
return 90 + radius * Math.cos(alpha);
}
function topF(alpha) {
return 90 + radius * Math.sin(-alpha);
}
.cMain{
position: absolute;
width: 200px;
height: 200px;
border-radius: 50%;
left: 50%;
top: calc(100% + 145px);
transform: translate(-50%, -100%);
background-color: #060505;
border: 5px solid #2D2D2D46;
z-index: 1;
}
.circle{
position: absolute;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: fuchsia;
}
#c1{
background-color: red;
}
#c2{
background-color: blue;
}
#c3{
background-color: red;
}
<div class="cMain">
<div class="circle" id="c1">
</div>
<div class="circle" id="c2">
</div>
<div class="circle" id="c3">
</div>
</div>
Ответы (1 шт):
Вы поворачиваете сами точки, но смысла в этом особого нет. Можно все точки обернуть во врапер, сам врапер сделать по размерам основного круга. Далее при помощи transform: rotate() врапер вращать на нужный угол - это значительно удобней, т.к. не требуется считать фактические координаты.
Далее нужно определить в какой позиции находятся точки (какая левей, какая правей), направление поворота (если эвент произошел правей активной точки, то налево, если слева от активной, значит вправо) и применить угол поворота к точкам.
Скорость вращения можно задать в css transition
В целом, смотрите пример с подробными комментариями ниже.
const initialRadius = 100;
const angles = {
initial: [300, 0, 60], //углы инициализации
//Маска поворота
rotate: {
right: [60, 60, 240], //право
left: [-240, -60, -60] //лево
}
}
//инициализируем клавный круг, присваиваем ему высоту и ширину
const cMain = document.querySelector('.cMain');
cMain.style.height = `${initialRadius*2}px`;
cMain.style.width = `${initialRadius*2}px`;
//Ф-я создания враппера, который будем вращать
const createDotWrapper = () => {
let circle = document.createElement('div');
circle.classList.add('dotWrapper');
//задаем ширину и высоту исходя из базового радиуса.
circle.style.height = `${initialRadius*2}px`;
circle.style.width = `${initialRadius*2}px`;
//позиционируем по центру основного круга
circle.style.left = `calc(50% - ${initialRadius}px)`;
circle.style.bottom = `calc(50% - ${initialRadius}px)`;
circle.appendChild(createDot())
return circle;
}
//ф-я создания точки
const createDot = () => {
let dot = document.createElement('div');
dot.classList.add('dot');
return dot;
}
//ф-я переназначения активной точки.
const setActive = (element) => {
document.querySelector('.dot-active').classList.remove('dot-active');
element.classList.add('dot-active');
}
//ф-я поворота
const rotate = (eventCoordinatesX) => {
//определяем направление поворота
let direction = (document.querySelector('.dot-active').getBoundingClientRect().left - eventCoordinatesX) > 0 ? 'right' : 'left';
//ищем все точки, определяем в массив, сортируем массив слева направо, в зависимости от их положения по оси x (самая левая точка будет первой в массиве и т.д.), это нужно, чтобы наложить маску поворота.
let dots = Array.from(cMain.querySelectorAll('.dot')).sort((a,b)=>(a.getBoundingClientRect().left < b.getBoundingClientRect().left) ? -1 : 1)
//перебираем точки, применяем к врапперу (родитель) маску поворота в порядке очерди, в зависимости от направления.
dots.forEach((dot, index)=>{
//извлекаем текущий угол поворота.
let actualAngle = Number(dot.parentNode.style.getPropertyValue("transform").match('-?[0-9]+'))
//новый угол
let newAngle = actualAngle + angles.rotate[direction][index];
//применяем новый угол к родителю
dot.parentNode.style.transform = `rotate(${newAngle}deg)`
})
}
/*Начинаем рисовать*/
//Создаем точки, учитываем, что поворачиваем мы .circle;
angles.initial.map((angle, index) => {
let circle = createDotWrapper();
circle.style.transform = `rotate(${angle}deg)`;
// установим дефолтный активный элемент
(angle==0) && circle.querySelector('.dot').classList.add('dot-active')
//если надо, то точкам можно айдишники назначить, в работе они не используются.
//circle.querySelector('.dot').setAttribute('id', `dot-${index}`)
cMain.appendChild(circle);
})
//Назначение обработки нажатия
cMain.querySelectorAll('.dot').forEach((dot, index)=>{
dot.onclick = (event) => {
//Скипаем, если кликнули по активному
if (dot.classList.contains('dot-active'))
return
rotate(event.pageX);
setActive(dot);
}
})
.cMain {
position: absolute;
border-radius: 50%;
border: 5px solid #2D2D2D46;
background: #060505;
z-index: 1;
}
.dotWrapper {
position: absolute;
transition: transform .5s linear;
pointer-events: none;
}
.dot {
position: absolute;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: red;
/*Позиционируем на 12 часов*/
left: calc(50% - 10px);
top: -10px;
z-index: 999;
cursor: pointer;
pointer-events: auto;
}
.dot-active {
background: blue;
}
<div class="cMain"></div>